User:Ahecht/Scripts/watchlistcleaner.js
Appearance
< User:Ahecht | Scripts
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Ahecht/Scripts/watchlistcleaner. |
//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 ([[User:Ahecht/Scripts/watchlistcleaner|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 = '<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 );
}
} );