User:Ahecht/Scripts/watchlistcleaner.js

//jshint maxerr:512 // Watchlist cleaner function cleanWatchlist { var millisDay = 24*60*60*1000; var cleanMiss = confirm("Remove redlinked (missing) pages from Watchlist?\n\n(OK for yes, Cancel for no)"); if (cleanMiss) { var keepMissTalk = confirm("Skip removing redlinked pages if talk page exists?\n\n(OK for yes, Cancel for no)"); }	var cleanRedir = confirm("Remove redirects from Watchlist?\n\n(OK for yes, Cancel for no)"); var cleanOld = confirm("Remove pages from Watchlist you haven't recently edited (slow)?\n\n(OK for yes, Cancel for no)"); if (cleanOld) { cleanOld = prompt("Minimum number of days since your last edit:"); cleanOld = Number(cleanOld) ? new Date(new Date - (Number(cleanOld)*millisDay)) : false; }	var cleanNever = confirm("Remove pages from Watchlist you have never edited (slow)?\n\n(OK for yes, Cancel for no)"); var keepCreations = confirm("Skip removing pages you created (slow)?\n\n(OK for yes, Cancel for no)"); var potentialUnwatch = [], unwatchPages = [], unwatchPagesCount = 0; var potentiallyStale = [], potentiallyStaleCount = 0, potentiallyStalePercent = -1; var statusText = "Fetching watchlist...";

function doUnwatch { // Recursively unwatch pages in batches of 50 if (unwatchPages.length > 0) { // Still have pages to unwatch console.log("Pages to unwatch: "); console.log(unwatchPages); statusText = "Removing " + unwatchPages.length + " pages from watchlist..."; mw.notify(statusText, {type: 'info', tag: 'status', autoHide: false}); var uwTitles = unwatchPages.splice(0,50).join("|"); // Remove 50 items from top of list var params = { action: "watch", unwatch: "true", titles: uwTitles };			new mw.Api.postWithToken("watch", params ).done( function(reslt) {				console.log("Unwatch successful: ");				console.log(reslt);				doUnwatch;			} ).fail( function(code, reslt) {				console.error("API error when unwatching pages: ");				console.error(reslt);				statusText = "API error when unwatching pages: " + code;				mw.notify(statusText, {type: 'error', tag: 'error'});				return;			} ); } else { // No more pages to unwatch statusText = "Done. Removed " + unwatchPagesCount + " pages from watchlist"; mw.notify(statusText, {type: 'success', tag: 'status', autoHide: true}); }	}	function doWlBackup { unwatchPagesCount = unwatchPages.length; var foundText = "Found " + unwatchPagesCount + " pages to remove."; if (unwatchPagesCount == 0) { mw.notify(foundText, {type: 'success', tag: 'found'}); return; } else if ( !confirm("Remove " + unwatchPagesCount + " pages from watchlist?") ) { mw.notify("Watchlist cleaner cancelled.", {type: 'error', tag: 'status', autoHide: true}); return; } else if ( confirm("Backup removed pages?\n\n(OK for yes, Cancel for no)") ) { var wlBackupLocation = mw.config.get('wgFormattedNamespaces')[2] + ":" + mw.config.get('wgUserName') + "/Watchlist_backup"; var params = { action: 'edit', title: wlBackupLocation, section: 'new', sectiontitle: new Date.toISOString, text: '* ' + unwatchPages.join("\n* ") + '', summary: 'Backup pages removed from watchlist (Watchlist cleaner)' };			new mw.Api.postWithToken("csrf", params ).done( function(reslt) {				console.log(wlBackupLocation + " updated:");				console.log(reslt);				statusText = unwatchPagesCount + " pages saved to "					+ wlBackupLocation + ".";				mw.notify(statusText, {type: 'success', tag: 'status', autoHide: true});				doUnwatch;			} ).fail( function(code, error) {				console.error("API error when saving backup: ");				console.error(error);				statusText = "API error when saving backup: " + code;				mw.notify(statusText, {type: 'error', tag: 'error'});				return;			} ); } else { mw.notify(foundText, {type: 'warn', tag: 'found'}); doUnwatch; }	}	function removeCreations(potentialUnwatchCount = 0, potentialUnwatchPercent = -1) { if (!keepCreations) { // Don't filter page creations unwatchPages = potentialUnwatch; doWlBackup; } else if (potentialUnwatch.length == 0) { // Done filtering doWlBackup; } else { // Filter page creations if(!potentialUnwatchCount) { potentialUnwatchCount = potentialUnwatch.length; }			var tempPUPercent = 100 - Math.ceil(100 * potentialUnwatch.length / potentialUnwatchCount); if (tempPUPercent != potentialUnwatchPercent) { potentialUnwatchPercent = tempPUPercent; var statusText = "Checking for your pages you created... ("+ tempPUPercent + "%)"; mw.notify(statusText, {type: 'info', tag: 'status', autoHide: false}); }			var query = { prop: 'revisions', titles: potentialUnwatch.shift, rvprop: 'user', rvlimit: '1', rvdir: 'newer', formatversion: "2" };			new mw.Api.get( query ) .done (function (d) {					if(d && d.query && d.query.pages && d.query.pages[0] && d.query.pages[0].revisions && d.query.pages[0].revisions[0]) { // Page found						d=d.query.pages[0].revisions[0];						if(d.user && d.user == mw.config.get('wgUserName')) {							console.log("Keeping page " + query.titles + ", which you created.");							foundText = "Keeping page " + query.titles + ", which you created.";							mw.notify(foundText, {type: 'warn', tag: 'found'});						} else {							unwatchPages.push(query.titles);						}					} else {						unwatchPages.push(query.titles);					}					removeCreations(potentialUnwatchCount, potentialUnwatchPercent);				} ).fail (function(code, error) {					console.error("API error when fetching page creator: ");					console.error(error);					statusText = "API error fetching page creator: " + code;					mw.notify(statusText, {type: 'error', tag: 'error'});					unwatchPages.push(query.titles);					removeCreations(potentialUnwatchCount, potentialUnwatchPercent); } );		}	}	function isPageStale(checkPage, checkAssoc, pageStatus = {exists: false, newEdit: false, everEdit: false}) {		var query = {			prop: 'revisions',			titles: checkPage,			rvprop: 'timestamp',			rvlimit: '1',			rvuser: mw.config.get('wgUserName'),			formatversion: "2"		};

new mw.Api.get( query ) .done (function (d) {				if (d && d.query && d.query.pages && d.query.pages[0]) { //API query returned pages					pageStatus.exists = true;					if (d.query.pages[0].revisions && d.query.pages[0].revisions[0].timestamp) { //User edit found						pageStatus.everEdit = true;						if (cleanOld) {							var revDate = new Date(d.query.pages[0].revisions[0].timestamp);							if ( revDate > cleanOld ) { // New revision found								if (!checkAssoc) {									console.log ("User edit on " + checkPage + " is new enough.");								}								pageStatus.newEdit = true;							} else { // Last revision exists but is too old								console.log ("Old user edit found on " + checkPage + " from " + revDate);							}						} else if ( (pageStatus.everEdit === false) && !checkAssoc ) {							console.log("User edit found on " + checkPage);						}					} else { // No user edits found						console.log ("No user edits found on " + checkPage); }				} // No page returned by API if ( (cleanOld && pageStatus.newEdit === false) ||					(cleanNever && pageStatus.everEdit === false) ) { if (checkAssoc) { // Talk page exists to check console.log("Checking talk page..."); isPageStale(checkAssoc, false, pageStatus); } else { //already on talk page checkStalePages(pageStatus); }				} else { // Page passed checkStalePages(pageStatus); }			} ).fail (function(code, error) { console.error("API error when fetching revisions: "); console.error(error); statusText = "API error fetching revisions: " + code; mw.notify(statusText, {type: 'error', tag: 'error'}); removeCreations; } );	}	function checkStalePages(pageStatus) {		var tempPSPercent = 100 - Math.ceil(100 * potentiallyStale.length / potentiallyStaleCount);		if (tempPSPercent != potentiallyStalePercent) {			potentiallyStalePercent = tempPSPercent;			var statusText = "Checking for your last edit... ("+ tempPSPercent + "%)";			mw.notify(statusText, {type: 'info', tag: 'status', autoHide: false});		}

var currentPage = potentiallyStale.shift; if(currentPage) { if(pageStatus.exists) { // Page exists if (cleanNever && pageStatus.everEdit === false) { // No user edits found foundText = "" + currentPage[0] + " has not been edited by you ever."; mw.notify(foundText, {type: 'warn', tag: 'found'}); potentialUnwatch.push(currentPage[0]); } else if (cleanOld && pageStatus.everEdit === true && pageStatus.newEdit === false) { // No new edits found foundText = "" + currentPage[0] + " has not been edited by you recently."; mw.notify(foundText, {type: 'warn', tag: 'found'}); potentialUnwatch.push(currentPage[0]); } // Page is okay } // Page doesn't exist if (potentiallyStale[0]) { isPageStale(potentiallyStale[0][0], potentiallyStale[0][1]); } else { // No more pages in list console.log("Finished checking for old and unedited pages"); removeCreations; }		} else { // No more pages in list console.log("Finished checking for old and unedited pages"); removeCreations; }	}	function fetchWatchlist(cont) { // Recursively fetch watchlist var query = { action: "query", prop: "info", inprop: "associatedpage|talkid", generator: "watchlistraw", gwrlimit: "max", formatversion: "2" };		if (cont) { query = Object.assign(query, cont); }		mw.notify(statusText, {type: 'info', tag: 'status', autoHide: false}); statusText = statusText + "."; new mw.Api.get( query ) .done (function (d) {				if (d && d.query && d.query.pages) { //API query returned pages					d.query.pages.forEach( function(i) { if(i.ns % 2 == 0) { // Page isn't a talk page if(cleanMiss && i.missing){ // Add missing page to list mw.notify("Found missing page " + i.title + ".", {type: 'warn', tag: 'found'}); if (keepMissTalk && !i.talkid) { mw.notify("Talk page of " + i.title + " exists, skipping.", {type: 'warn', tag: 'found'}); } else { potentialUnwatch.push(i.title); }							} else if (cleanRedir && i.redirect) { // Add redirect to list mw.notify("Found redirect " + i.title + ".", {type: 'warn', tag: 'found'}); potentialUnwatch.push(i.title); } else if (cleanOld || cleanNever) { // Add pages to check revisions potentiallyStale.push([i.title, i.associatedpage]); }						}					} );				}				if (d && d.continue) { // More results are available					fetchWatchlist(d.continue);				} else if (potentiallyStale[0] && (cleanOld || cleanNever)) {					// No more results, check stale and missing					potentiallyStaleCount = potentiallyStale.length;					isPageStale(potentiallyStale[0][0], potentiallyStale[0][1]);				} else { // No more results, no potentially stale pages or not checking					removeCreations;				}			} ).fail (function(code, error) {				console.error("API error when fetching watchlist: ");				console.error(error);				statusText = "API error fetching watchlist: " + code;				mw.notify(statusText, {type: 'error', tag: 'error'});			} ); return; }	if (cleanMiss || cleanRedir || cleanOld || cleanNever) { // Cancel wasn't selected for all options fetchWatchlist; } }

$(document).ready( function { // Add "Clean" link to toolbar	if( /Watchlist$/.test(mw.config.get('wgCanonicalSpecialPageName')) ) {		var cleanLink = 'Clean the watchlist';		if ($('.mw-watchlist-toollinks').length > 0) { //Most older skins			$('.mw-watchlist-toollinks a').last.after(' | ' + cleanLink);		} else if ($("#p-associated-pages").length > 0) { //Vector-2022 or Minerva			var lastLi = $("#p-associated-pages li").last;			lastLi.clone.				attr("id", (lastLi.attr("id") || "").replace(/(\d+)$/, function{return arguments[1]*1+1;}))				.html(cleanLink).insertAfter(lastLi);		} else { //Fallback to "Tools" menu			mw.util.addPortletLink( 'p-tb', '#', 'Clean the watchlist', 'clean-watchlist-link', 'Run cleanwatchlist.js');		}		$("#clean-watchlist-link").on("click", cleanWatchlist );	} } );