Jump to content

User:Ahecht/Scripts/watchlistcleaner.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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.
//jshint maxerr:512
// Watchlist cleaner
function cleanWatchlist() {
	var api = new mw.Api( { userAgent: 'WatchlistCleaner/0.0.1' } );
	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
			};
			
			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 ([[User:Ahecht/Scripts/watchlistcleaner|Watchlist cleaner]])'
			};
			
			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"
			};
			
			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"
		};

		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 + ".";
		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 = '<a href="#" title="Run cleanwatchlist.js" id="clean-watchlist-link" rel data-event-name="tabs.">Clean the watchlist</a>';
		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 );
	}
} );