Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// This, that and the other's mass tagging script
// July 2011

// To make it work, use a JavaScript console to call masstag() or massremove()
// Script depends on the following lines having been included in skin.js previously:
// importStylesheet("MediaWiki:Gadget-morebits.css");
// importScript("MediaWiki:Gadget-morebits.js");

function masstag() {
	if (mw.config.get("wgUserName") !== "TTObot") {
		alert("The masstag script may only be run under the User:TTObot account. If you wish to use this script to perform mass tagging tasks, please contact User:This, that and the other.");
		return;
	}
	mw.loader.using(['jquery.ui'], function() {
		var dialog = new Morebits.simpleWindow(500, 400);
		dialog.setTitle("Mass tagging");
		var form = new Morebits.quickForm(masstag.callback.initialTag);
		form.append({ type: 'div', label: [ Morebits.htmlNode("strong", "Notice: BAG approval applies to mass XfD tagging operations only.", "red") ]});
		form.append({ type: 'textarea', name: 'list', label: 'List of pages (one per line)' });
		form.append({ type: 'input', name: 'tag', label: 'Text to prepend: ', size: 60 });
		form.append({ type: 'input', name: 'summary', label: 'Edit summary: ', size: 60 });
		form.append({ type: 'checkbox', list: [
			{ name: 'skipMissing', label: 'Skip non-existent pages', checked: true },
			{ name: 'skipCsd', label: 'Skip speedy deletion candidates', checked: true },
			{ name: 'skipEdited', label: 'Skip pages with more than one revision' },
			{ name: 'skipBig', label: 'Skip pages bigger than the size threshold below' }
		]});
		form.append({ type: 'input', name: 'bigThreshold', label: 'Size threshold (bytes): ', size: 5 });
		form.append({ type: 'submit' });
		dialog.setContent(form.render());
		dialog.display(); 
	});
}

function massremove() {
	if (mw.config.get("wgUserName") !== "TTObot") {
		alert("The masstag script may only be run under the User:TTObot account. If you wish to use this script to perform mass tagging tasks, please contact User:This, that and the other.");
		return;
	}
	mw.loader.using(['jquery.ui'], function() {
		var dialog = new Morebits.simpleWindow(500, 400);
		dialog.setTitle("Mass tag removal");
		var form = new Morebits.quickForm(masstag.callback.initialRemove);
		form.append({ type: 'div', label: [ Morebits.htmlNode("strong", "Notice: BAG approval applies to mass removal of XfD tags only.", "red") ]});
		form.append({ type: 'textarea', name: 'list', label: 'List of pages (one per line)' });
		form.append({ type: 'input', name: 'regex', label: 'Regex to remove (case insens.): ', size: 60 });
		form.append({ type: 'div', label: "^(<noinclude>)?\\s*\\{\\{tfd[^\\}]*\\}\\}\\s*(</noinclude>)?\\s*" });
		form.append({ type: 'input', name: 'summary', label: 'Edit summary: ', size: 60 });
		form.append({ type: 'checkbox', list: [
			{ name: 'skipCsd', label: 'Skip speedy deletion candidates', checked: true }
		]});
		form.append({ type: 'submit' });
		dialog.setContent(form.render());
		dialog.display(); 
	});
}

masstag.callback = {};

masstag.callback.initialTag = function(e) {
	var form = e.target;
	if (!form.tag.value || !form.summary.value) {
		alert("Tag text and edit summary are compulsory");
		return;
	}
	var params = {
		massremove: false,
		tag: form.tag.value,
		list: form.list.value.trimRight().split("\n"),
		summary: form.summary.value,
		skipMissing: form.skipMissing.checked,
		skipCsd: form.skipCsd.checked,
		skipEdited: form.skipEdited.checked,
		skipBig: form.skipBig.checked,
		bigThreshold: parseInt(form.bigThreshold.value, 10),
		resultPages: [],
		resultInitialContributors: [],
		form: form
	};
	Morebits.simpleWindow.setButtonsEnabled(false);
	form.textContent = "";
	var div = document.createElement("div");
	div.style.height = "8em";
	div.style.overflow = "auto";
	div.style.padding = "4px";
	div.style.border = "1px black solid";
	div.style.background = "white";
	form.appendChild(div);
	Morebits.status.init(div);
	masstag.callback.beginTag(params);
};

masstag.callback.initialRemove = function(e) {
	var form = e.target;
	if (!form.regex.value || !form.summary.value) {
		alert("Removal regex and edit summary are compulsory");
		return;
	}
	var params = {
		massremove: true,
		regex: form.regex.value,
		list: form.list.value.trimRight().split("\n"),
		summary: form.summary.value,
		skipMissing: true,
		skipCsd: form.skipCsd.checked,
		skipEdited: false,
		skipBig: false,
		resultPages: [],
		resultInitialContributors: [],
		form: form
	};
	Morebits.simpleWindow.setButtonsEnabled(false);
	form.textContent = "";
	var div = document.createElement("div");
	div.style.height = "8em";
	div.style.overflow = "auto";
	div.style.padding = "4px";
	div.style.border = "1px black solid";
	div.style.background = "white";
	form.appendChild(div);
	Morebits.status.init(div);
	masstag.callback.beginTag(params);
};

masstag.callback.beginTag = function(params) {
	if (params.list.length === 0) {
		masstag.callback.finishUp(params);
		return;
	}
	var query = {
		action: 'query',
		prop: 'info|revisions',
		rvprop: 'user|timestamp|flags|content',
		titles: params.list[0]
	};
	var api = new Morebits.wiki.api("Fetching page data", query, masstag.callback.processTag, new Status("Loading " + params.list[0]));
	api.params = params;
	window.setTimeout(function() { api.post(); }, 6000);  // throttling to prevent overly frequent editing
};

masstag.callback.processTag = function(apiobj) {
	var params = apiobj.params;
	var $xml = $(apiobj.responseXML);
	var $page = $xml.find("page");
	if ($page.attr("missing") === "" && params.skipMissing) {
		params.resultPages.push({ title: params.list[0], result: "Page does not exist", red: true });
	} else {
		if ($page.attr("redirect") === "") {
			params.resultPages.push({ title: params.list[0], result: "Page is a redirect", red: true });
		} else {
			if ($xml.find("rev").text().indexOf("{{db") !== -1 && params.skipCsd) {
				params.resultPages.push({ title: params.list[0], result: "Page may be a speedy deletion candidate", red: true });
			} else {
				if ($page.attr("new") !== "" && params.skipEdited) {
					params.resultPages.push({ title: params.list[0], result: "Page has more than one revision", red: true });
				} else {
					if (params.skipBig && $page.attr("length") > params.bigThreshold) {
						params.resultPages.push({ title: params.list[0], result: "Page is too long (" + $page.attr("length") + " bytes)", red: true });
					} else {
						// do it
						var page = new Morebits.wiki.page(params.list[0]);
						var editsuccess = function(apiresult) {
							page.getStatusElement().info("Done");
							params.resultPages.push({ title: params.oldtitle, result: "Success" });
							masstag.callback.beginTag(params);
						};
						var editerror = function(apiresult) {
							page.getStatusElement().error(apiresult.errorText);
							params.resultPages.push({ title: params.oldtitle, result: "API error (" + apiresult.errorText + ")", red: true });
							masstag.callback.beginTag(params);
						};
						if (params.massremove) {
							page.load(function(pageresult) {
								var text = pageresult.getPageText();
								var newText = text.replace(new RegExp(params.regex, "i"), "");
								if (text === newText) {
									page.getStatusElement().warn("Regex did not match");
									params.resultPages.push({ title: params.list[0], result: "Regex did not match; page skipped" });
									params.list = params.list.slice(1);
									masstag.callback.beginTag(params);
									return;
								}
								page.setPageText(newText);
								page.setEditSummary(params.summary);
								page.setCreateOption("nocreate");
								params.oldtitle = params.list[0];
								params.list = params.list.slice(1);
								page.save(editsuccess, editerror);
							});
						} else {
							page.setPrependText(params.tag);
							page.setEditSummary(params.summary);
							page.setCreateOption(params.skipMissing ? "nocreate" : "recreate");
							params.oldtitle = params.list[0];
							params.list = params.list.slice(1);
							page.prepend(editsuccess, editerror);
							if ($page.attr("missing") !== "") {
								page.lookupCreator(function(pageobj) {
									if (params.resultInitialContributors.indexOf(pageobj.getCreator()) === -1) {
										params.resultInitialContributors.push(pageobj.getCreator());
									}
								});
							}
						}
						return;
					}
				}
			}
		}
	}
	params.list = params.list.slice(1);
	masstag.callback.beginTag(apiobj.params);
};

masstag.callback.finishUp = function(params) {
	var success = [], failed = [];
	params.form.appendChild(Morebits.htmlNode("h3", "1. Results"));
	$.each(params.resultPages, function(k, v) {
		var div = Morebits.htmlNode("div", "", v.red ? "red" : null);
		var a = Morebits.htmlNode("a", v.title);
		a.href = "/wiki/" + v.title;
		div.appendChild(a);
		div.appendChild(document.createTextNode(": " + v.result));
		params.form.appendChild(div);
		(v.red ? failed : success).push(v.title);
	});
	if (!params.massremove) {
		params.form.appendChild(Morebits.htmlNode("h3", "2. Initial contributors"));
		$.each(params.resultInitialContributors, function(k, v) {
			params.form.appendChild(Morebits.htmlNode("div", v));
		});
	}
	params.form.appendChild(Morebits.htmlNode("h3", (params.massremove ? "2" : "3") + "a. Summary: successful pages (" + success.length + " total)"));
	$.each(success, function(k, v) {
		params.form.appendChild(Morebits.htmlNode("div", v));
	});
	params.form.appendChild(Morebits.htmlNode("h3", (params.massremove ? "2" : "3") + "b. Summary: failed pages (" + failed.length + " total)"));
	$.each(failed, function(k, v) {
		params.form.appendChild(Morebits.htmlNode("div", v));
	});
};