User:R'n'B/dplupdate.js

/* dplupdate.js : Script to update monthly Disambiguation Pages with Links page // Version 0.13 /*global console, mw, jQuery, importScript, OO */ /*jshint multistr: true */ (function ($) {	var	api, old_wikitext, wikitext, timestamp,		donetext = [], notdonetext = [], nolongertext = [],		continued = {},		DABCOUNT = 1000,		LOWCOUNT = 1,	// mark Done if less than this many links		HIGHCOUNT = 10,	// mark not Done if more than this many links		progress = 0,		pages_done = 0,		nolonger_dabs = 0,		editsummary = "Update count", // default		pagedata = {			'todo': {}, 'done': {},			'todocount': 0, 'donecount': 0,			'todolinks': 0, 'donelinks': 0		},		pattern = /^# (?:\\])*\[\[([^\]]+)\]\]: ([0-9,]+) (\[\[Special:[^\n]+)$/gm,		pageid = mw.config.get('wgArticleId'),		fmt = function (num) {			while (num.match(/\d{4}/) !== null) {				num = num.replace(/(\d)(\d\d\d)(,|$)/, "$1,$2$3");			}			return num;		},		load_page = function {			// retrieve the wikitext of this page			api.request({ 'action': 'query', 'pageids': pageid, 'prop': 'revisions', 'rvprop': 'content|timestamp' },				read_page );		},		read_page = function (response) {			// create an internal representation of the page lists			var todomark, donemark, match, title, links, fmtnum;			mw.RnB.pagedata = pagedata;			mw.RnB.wikitext = wikitext = response.query.pages[pageid].revisions[0]['*'];			mw.RnB.continued = continued;			timestamp = response.query.pages[pageid].revisions[0].timestamp;			old_wikitext = wikitext;			todomark = wikitext.indexOf("===To do===");			donemark = wikitext.indexOf("===Done===");			while ((match = pattern.exec(wikitext)) !== null) {				title = match[1].trim;				links = parseInt(match[2].replace(/,/g, ''), 10);				if (match.index > todomark && match.index < donemark) {					if (pagedata.todo[title]) {						OO.ui.alert('Page "' + title + '" appears twice in the "To do" section.', {title: 'Duplicate page error'} );					} else {						pagedata.todo[title] = links;						pagedata.todocount += 1; pagedata.todolinks += links; }				} else if (match.index > donemark) { if (pagedata.done[title]) { OO.ui.alert('Page "' + title + '" appears twice in the "Done" section.', {title: 'Duplicate page error'} ); } else if (pagedata.todo[title]) { OO.ui.alert('Page "' + title + '" appears in both the "To do" and "Done" sections.', {title: 'Duplicate page error'} ); } else { pagedata.done[title] = links; pagedata.donecount += 1; pagedata.donelinks += links; }				}			}			if (pagedata.todocount + pagedata.donecount !== DABCOUNT) { fmtnum = fmt((pagedata.todocount + pagedata.donecount).toString); OO.ui.alert('Parsed link counts for ' + fmtnum + ' pages.', {title: 'Page count error'} ); return; }		},		update_totals = function { var progress1 = /out of a total of ([0-9,]+) links, approximately ([0-9,]+) have currently been fixed/, progress2 = /\{\{Progress bar\|([0-9]+)\|total=([0-9]+)\|width=60%\}\}/, m1 = progress1.exec(wikitext), m2 = progress2.exec(wikitext), p1, p2, num1, num2; mw.log(pagedata.donelinks, "out of", pagedata.todolinks + pagedata.donelinks); if (m1 === null || m2 === null) { OO.ui.alert('Unable to parse link counts from "Progress" section of page.', {title: 'Format error'} ); return; }			if (m2[1] === pagedata.donelinks.toString) { // no change, so don't update return; }			num1 = (pagedata.todolinks + pagedata.donelinks).toString; num2 = pagedata.donelinks.toString; p1 = ("out of a total of " + fmt(num1) +				 " links, approximately " + fmt(num2) +				  " have currently been fixed"); p2 = (""); wikitext = wikitext.replace(progress1, p1).replace(progress2, p2); // ask server to generate the diff api.request({				action: "compare",				fromid: pageid,				toslots: "main",				"totext-main": wikitext,				"tocontentformat-main": "text/x-wiki",				prop: "diff"			}, showdiff); },		showdiff = function (response) { var original = mw.util.$content.html, diff = response.compare['*'], diffhtml = ( ' '); if (! diff) { return; }			mw.util.$content.html(diffhtml); OO.ui.prompt('Accept these changes?', {textInput: {value: editsummary}}).done(				function (result) {					if (result !== null) {						// save the page						api.request({ action: 'edit', title: mw.config.get("wgPageName"), text: wikitext, summary: result, token: mw.user.tokens.get("csrfToken"), basetimestamp: timestamp }, page_saved);					} else {						mw.util.$content.html(original);						wikitext = old_wikitext;					}				}			); },		page_saved = function (response) { // load edited page if (response.edit.result === "Success") { window.location.reload; return; }			OO.ui.alert("Error saving page: '" + response.edit.toString + "'", {title: "API error"} ); mw.log(response); },		minmax = function (min, val, max) { if (val < min) return min; if (val > max) return max; return val; },		lines = 2.5, progressDialog, update_pages = function { // dispatch backlinks requests for all linked pages var titlelist = [], t;
 * Copyright (c) 2012-22 en:User:R'n'B
 * Creative Commons Attribution-ShareAlike License applies
 * Requires wrappi.js, MediaWiki 1.17, and jQuery 1.7 (included with MediaWiki)

// this is going to take a while, so set up progress dialog function ProgressDialog( config ) { ProgressDialog.super.call( this, config ); }			OO.inheritClass( ProgressDialog, OO.ui.Dialog ); ProgressDialog.static.name = 'progressDialog'; ProgressDialog.prototype.initialize = function { ProgressDialog.super.prototype.initialize.call( this ); this.content = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ); this.content.$element.append( ' Now updating backlink counts to disambig pages. Press Escape key to cancel. '); this.progbar = new OO.ui.ProgressBarWidget({ id: 'dplprogress', progress: 0 }); this.progbar.toggle(true); this.content.$element.append(this.progbar.$element); this.content.$element.append($(' ')); this.$body.append( this.content.$element ); };			ProgressDialog.prototype.getBodyHeight = function { return this.content.$element.outerHeight( true ) * minmax(3, lines, 12); };			progressDialog = new ProgressDialog( {size: 'larger'} ); // Create and append a window manager, which opens and closes the window. var windowManager = new OO.ui.WindowManager; $( document.body ).append( windowManager.$element ); windowManager.addWindows( [ progressDialog ] ); windowManager.openWindow( progressDialog ); for (t in pagedata.todo) { if (pagedata.todo.hasOwnProperty(t)) { titlelist.push(t); }			}			for (t in pagedata.done) { if (pagedata.done.hasOwnProperty(t)) { titlelist.push(t); }			}			while (titlelist.length) { t = titlelist.shift; // retrieve backlinks, also categories and page info to				// confirm whether this is a redirect and/or no longer a dab api.request({					action: 'query',					prop: 'info|categories',					titles: t,					indexpageids: "",					redirects: "",					list: 'backlinks',					bltitle: t,					blnamespace: 0,					blredirect: "",					bllimit: "max"				}, process_count); }		},		process_count = function (response, query) { // count the eligible backlinks from the API response var item, redir, len, redirlen, ititle, rd, donemark, page = response.query.pages[response.query.pageids[0]], bl = response.query.backlinks, count = 0, dabtitle = query.bltitle, isredirect = false, hasdabcat = false, conceptdab = false, p = new RegExp('# ((?:\\\\])*)\\[\\[' + 				              mw.util.escapeRegExp(dabtitle) + 							   '\\]\\]: ([0-9,]+) (\\[\\[Special:[^\\n]+)\\n'), m = p.exec(wikitext); if (! continued[dabtitle]) { continued[dabtitle] = {}; }			// first check that this is still a dab if (response.query.redirects) { isredirect = true; }			if (page.categories) { len = page.categories.length; for (item = 0; item < len; item += 1) { if (page.categories[item].title === 							"Category:All article disambiguation pages") { hasdabcat = true; }					if (page.categories[item].title ===							"Category:Disambiguation pages to be converted to broad concept articles") { conceptdab = true; }				}			}			if (! hasdabcat) { // no need to count backlinks if no longer a disambig page if (pagedata.todo[dabtitle]) { // move to done section wikitext = wikitext.replace(p, ).concat(						'\n# ' + m[1] +  + dabtitle + ': ' + m[2]	+ " " + m[3] + 						" - no longer a disambig page"); if (dabtitle === "State") { mw.log(wikitext); } pagedata.done[dabtitle] = pagedata.todo[dabtitle]; pagedata.todocount -= 1; pagedata.donecount += 1; pagedata.todolinks -= pagedata.todo[dabtitle]; pagedata.donelinks += pagedata.todo[dabtitle]; delete pagedata.todo[dabtitle]; $(" ").text(							"" + dabtitle + " is no longer a dab, moved to 'done'") .appendTo($('#dpllog')); lines += 0.9; progressDialog.updateSize; nolongertext.push("" + dabtitle + ""); nolonger_dabs += 1; }				progress += 1; progressDialog.progbar.setProgress(progress * 100 / DABCOUNT); mw.log("Progress = " + progress); if (progress === DABCOUNT) { check_done; }				return; }			// save results len = bl.length; for (item = 0; item < len; item += 1) { ititle = bl[item].title; if (bl[item].redirlinks) { rd = bl[item].redirlinks; redirlen = rd.length; if (! continued[dabtitle][ititle]) { continued[dabtitle][ititle] = {}; }					for (redir = 0; redir < redirlen; redir += 1) { continued[dabtitle][ititle][rd[redir].title] = true; }				} else { if (! bl[item].hasOwnProperty('redirect')) { continued[dabtitle][ititle] = true; }				}			}			if (response['query-continue'] && response['query-continue'].blcontinue) { // continue query query.blcontinue = response['query-continue'].blcontinue; query.rawcontinue = ""; api.request(query, process_count); return; }			if (conceptdab) { // mark, if not already marked if (m[3].indexOf("DABCONCEPT") === -1) { wikitext = wikitext.replace(p, $.trim(m[0].slice(0, -1)) +						" – tagged as WP:DABCONCEPT\n"); }			}			// count the eligible links bl = continued[dabtitle]; for (item in bl) { if (bl.hasOwnProperty(item)) { if (bl[item] === true) { if (item !== dabtitle && !dabtitle.endswith("(disambiguation)")) { count += 1; }					} else { // item is a redirect with its own backlinks if (! item.endswith("(disambiguation)")) { for (rd in bl[item]) { if (bl[item].hasOwnProperty(rd)) { count += 1; }							}						}					}				}			}			// see if dabtitle needs to be moved to the other section if (pagedata.todo[dabtitle] && count < LOWCOUNT) { // this is done wikitext = wikitext.replace(p, ).concat(					'\n# ' + m[1] +  + dabtitle + ': ' + m[2]	+ " " + m[3]); pagedata.done[dabtitle] = pagedata.todo[dabtitle]; pagedata.todocount -= 1; pagedata.donecount += 1; pagedata.todolinks -= pagedata.todo[dabtitle]; pagedata.donelinks += pagedata.todo[dabtitle]; delete pagedata.todo[dabtitle]; $(" ").text(						"" + dabtitle + " (" + count + " links) moved to 'done'") .appendTo($('#dpllog')); lines += 0.9; progressDialog.updateSize; donetext.push("" + dabtitle + ""); pages_done += 1; } else if (pagedata.done[dabtitle] && count > HIGHCOUNT) { // this is un-done wikitext = wikitext.replace(p, ''); donemark = wikitext.indexOf("===Done==="); wikitext = wikitext.slice(0, donemark-1) + ('# ' + m[1] + '' + dabtitle + ': ' + m[2] + " " + m[3] + '\n') + wikitext.slice(donemark-1); pagedata.todo[dabtitle] = pagedata.done[dabtitle]; pagedata.donecount -= 1; pagedata.todocount += 1; pagedata.donelinks -= pagedata.done[dabtitle]; pagedata.todolinks += pagedata.done[dabtitle]; delete pagedata.done[dabtitle]; mw.log("Info:", dabtitle, "moved back to 'to do'"); $(" ").text(						"" + dabtitle + " (" + count + " links) moved back to 'To do'") .appendTo($('#dpllog')); lines += 0.9; progressDialog.updateSize; notdonetext.push("" + dabtitle + ""); }			if (pagedata.todo[dabtitle] && isredirect) { $(" ").text(						"" + dabtitle + " is now a redirect to " + 						response.query.redirects[0].to + ""					).appendTo($('#dpllog')); lines += 0.9; }			progress += 1; progressDialog.progbar.setProgress(progress * 100 / DABCOUNT); mw.log("Progress = " + progress); if (progress === DABCOUNT) { progressDialog.close; check_done; }		},		check_done = function { // this should be called by the progress bar when it hits 100% var es1 = , es2 = , es3 = '', jointhem = function (s1, s2) { return (s1 && s2) ? (s1 + "; " + s2) : (s1 || s2); };			mw.log("check_done called"); $('#progressdiv').dialog('destroy'); editsummary = ""; if (donetext.length > 0) { es1 = donetext.join(", ") + " done"; }			if (nolongertext.length > 0) { es2 = nolongertext.join(", ") + " no longer " + ((nolongertext.length > 1) ? "dabs" : "a dab"); }			if (notdonetext.length > 0) { es3 = notdonetext.join(", ") + " still " + ((notdonetext.length > 1) ? "have" : "has") + " links to fix"; }			// create tentative editsummary editsummary = jointhem(jointhem(es1, es2), es3); if (editsummary.length > 480 && nolonger_dabs > 0) { es2 = nolonger_dabs.toString + " no longer " + ((nolonger_dabs > 1) ? "dabs" : "a dab"); editsummary = jointhem(jointhem(es1, es2), es3); }			if (editsummary.length > 480 && pages_done > 0) { es1 = pages_done.toString + " page" + ((pages_done > 1) ? "s" : "") + " done"; editsummary = jointhem(jointhem(es1, es2), es3); }			if (editsummary === "") { editsummary = "Update count"; } else { editsummary += "; update count"; }			update_totals; };	String.prototype.endswith = function (substring) { // return true if substring matches the end of this return (this.slice(-substring.length) === substring); };	$(function { // on document ready:		var startup = function  {			if (mw.RnB && mw.RnB.Wiki) {				mw.loader.using([ 'oojs-ui-core', 'oojs-ui-windows', 'mediawiki.diff.styles' ],					function { mw.util.addPortletLink("p-tb", "#",							"Update totals only", "tb-totals"); mw.util.addPortletLink("p-tb", "#", 							"Update pages and totals", "tb-update"); $('#tb-totals').click(function (e) {							update_totals; 							e.preventDefault;						}); $('#tb-update').click(function (e) {							update_pages; 							e.preventDefault;						}); api = new mw.RnB.Wiki; load_page; });			} else {				setTimeout(startup, 100);			}		};		startup;	}); }(jQuery) );