User:Qwerfjkl/scripts/massXFD.js: Difference between revisions

Content deleted Content added
Update - fix redirect target notifications, RFD: check titles are redirects, hopefully fix major bug with options being global [Factotum]
Fix issue with batches not being sync, cleanup code [Factotum]
Line 1:
// <nowiki>
 
// todo (ignore): make counter inline, remove progresss and progressElement from editPage(), more dynamic reatelimit wait.
// todo: make counter inline, remove progresss and progressElement from editPAge(), more dynamic reatelimit wait.
// counter semi inline; adjust align in createProgressBar()
// Function to wipe the text content of the page inside #bodyContent
 
// Batches aren't quite sync
// update normalise function for CfD - use mw.Title()
 
Line 761 ⟶ 762:
}
 
function editPage(options) {
const localOptions = deepCopy(options);
console.log(localOptions);
localOptions.text = localOptions.textToModify;
const api = new mw.Api();
const messageElement = createMessageElement();
 
messageElement.setLabel((localOptions.retry)
console.log(options)
? $('<span>').text('Retrying ').append($(makeLink(localOptions.title)))
options.text = options.textToModify;
: $('<span>').text('Editing ').append($(makeLink(localOptions.title))));
var api = new mw.Api();
 
localOptions.progressElement.$element.append(messageElement.$element);
var messageElement = createMessageElement();
const container = $('.sticky-container');
container.scrollTop(container.prop("scrollHeight"));
 
if (localOptions.retry) {
 
 
messageElement.setLabel((options.retry) ? $('<span>').text('Retrying ').append($(makeLink(options.title))) : $('<span>').text('Editing ').append($(makeLink(options.title))));
options.progressElement.$element.append(messageElement.$element);
var container = $('.sticky-container');
container.scrollTop(container.prop("scrollHeight"));
if (options.retry) {
sleep(1000);
}
 
varconst requestData = {
action: 'edit',
// title: window.debuggingMode ? 'User:Qwerfjkl/sandbox/51': localOptions.title,
titlesummary: optionslocalOptions.titlesummary,
summary: options.summary,
format: 'json'
};
 
if (optionslocalOptions.type === 'prepend') { // tagging
requestData.nocreate = 1; // don't create new page when tagging
//const parsetargets = localOptions.titlesDict[localOptions.title];
var targets = options.titlesDict[options.title];
 
for (let i = 0; i < targets.length; i++) {
const placeholder = '$' + (i + 1);
// we add 1 to i in the replace function because placeholders start from $1 not $0
let placeholderlocalOptions.text = '$'localOptions.text.replace(placeholder, + (targets[i + 1]);
options.text = options.text.replace(placeholder, targets[i]);
}
optionslocalOptions.text = optionslocalOptions.text.replace(/\$\d/g, ''); // remove unmatched |$x
requestData.prependtext = optionslocalOptions.text.trim() + '\n\n';
} else if (localOptions.type === 'append') {
requestData.appendtext = '\n\n' + localOptions.text.trim();
} else if (localOptions.type === 'text') {
requestData.text = localOptions.text;
}
 
console.log(requestData);
 
return new Promise((resolve, reject) => {
} else if (options.type === 'append') { // user
requestData.appendtext = '\n\n' + options.text.trim();
} else if (options.type === 'text') {
requestData.text = options.text;
}
console.log(requestData)
return new Promise(function (resolve, reject) {
if (window.abortEdits) {
// hide message and return
messageElement.toggle(false);
resolve();
return;
}
api.postWithEditToken(requestData).then(function (data) {
if (data.edit && data.edit.result === 'Success') {
messageElement.setType('success');
messageElement.setLabel($('<span>' + makeLink(options.title) + ' edited successfully</span><span class="massxfdundo" data-revid="' + data.edit.newrevid + '" data-title="' + options.title + '"></span>'));
 
resolveapi.postWithEditToken(requestData);
}.then((data) else=> {
if (data.edit && data.edit.result === 'Success') {
messageElement.setType('success');
messageElement.setLabel($('<span>' + makeLink(localOptions.title) + ' edited successfully</span><span class="massxfdundo" data-revid="' + data.edit.newrevid + '" data-title="' + localOptions.title + '"></span>'));
resolve();
} else {
handleError('Error occurred while editing', data, localOptions, messageElement, resolve, reject);
}
})
.catch((error) => handleError('Error occurred while editing', error, localOptions, messageElement, resolve, reject));
});
}
 
function handleError(msg, error, options, messageElement, resolve, reject) {
messageElement.setType('error');
messageElement.setType('error');
messageElement.setLabel($('<span>Error' occurred+ whilemsg editing+ ' ' + makeLink(options.title) + ': ' + dataerror + '</span>'));
console.error('Errormsg occurred+ while prepending text to' page:', dataerror);
reject();
}
}).catch(function (error) {
messageElement.setType('error');
messageElement.setLabel($('<span>Error occurred while editing ' + makeLink(options.title) + ': ' + error + '</span>'));
console.error('Error occurred while prepending text to page:', error); // handle: editconflict, ratelimit (retry)
 
if (error === 'editconflict') {
editPage(deepCopy(options)).then(function (resolve) {;
} else if (error === 'ratelimited') {
resolve();
}options.progress.setDisabled(true);
handleRateLimitError(options.ratelimitMessage).then(() => {
} else if (error == 'ratelimited') {
options.progress.setDisabled(truefalse);
editPage(deepCopy(options)).then(resolve);
 
handleRateLimitError(options.ratelimitMessage).then(function () {
options.progress.setDisabled(false);
editPage(options).then(function () {
resolve();
});
});
}
else {
reject();
}
});
}); else {
reject();
}
}
 
 
// global scope - needed to syncronise ratelimits
Line 924 ⟶ 917:
mw.loader.using('oojs-ui').done(function () {
wipePageContent();
if (!window.debuggingMode) { // annoying when reloading for debugging
onbeforeunload = function() {
return "Closing this tab will cause you to lose all progress.";
};
}
elementsToDisable = [];
var bodyContent = $('#bodyContent');
 
mw.util.addCSS(`.sticky-container {
bottom: 0;
width: 100%;
max-height: 600px;
overflow-y: auto;
}`); // should probably be styled directly on the element than via the stylesheet
}`);
var nominationToggleObj = createNominationToggle();
var nominationToggle = nominationToggleObj.toggle;
Line 1,353 ⟶ 1,348:
if (nonredirects.length) {
let nonredirectsWarningMessage = createWarningMessage();
nonredirectsWarningMessage.$element.css({'max-height': '20em', 'overflow-y': 'auto'}) // normally shouldn't be needed
let nonRedirectsHTML = $('<div>').append($('<span>').text('The following pages were ignored because they are not redirects:'))
let $listElement = $('<ul>')
Line 1,389 ⟶ 1,385:
 
 
async function processContent(options) {
function getKeyByValue(object, value) {
return Object.keys(object).find(key => object[key] === value);
}
 
// let {content, titles, textToModify, summary, type, doneMessage, headingLabel} = console.log(options);
console.log(options.titles);
console.log(options.titles)
if (!Array.isArray(options.titles)) {
options.titlesDict = options.titles;
options.titles = Object.keys(options.titles);
} else {
options.titlesDict = {};
var}

const fieldset = createFieldset(options.headingLabel);
 
bodyContent.append(fieldset.$element);
 
options.progressElement = createProgressElement();
fieldset.addItems([options.progressElement]);
 
options.ratelimitMessage = createWarningMessage();
options.ratelimitMessage.toggle(false);
fieldset.addItems([options.ratelimitMessage]);
 
varconst progressObj = createProgressBar(`(0 / ${options.titles.length}, 0 errors)`); // with label
options.progress = progressObj.progressBar;
varconst progressContainer = progressObj.fieldlayout;
// Add margin or padding to the progress bar widget
options.progress.$element.css('margin-top', '5px');
options.progress.pushPending();
fieldset.addItems([progressContainer]);
 
let resolvedCount = 0;
let rejectedCount = 0;
 
function updateCounter() {
progressContainer.setLabel(`(${resolvedCount} / ${options.titles.length}, ${rejectedCount} errors)`);
}
function updateProgress() {
varconst percentage = (resolvedCount + rejectedCount) / options.titles.length * 100;
options.progress.setProgress(percentage);
 
}
 
function trackPromise(promise) {
return new Promise((resolve, reject) => {
promise
.then(value => {
Line 1,442 ⟶ 1,439:
})
.catch(error => {
rejectedCount++;
updateCounter();
updateProgress();
resolve(error);
})
.error(error => {
rejectedCount++;
updateCounter();
Line 1,455 ⟶ 1,446:
});
}
 
returnconst newpromises Promise= options.titles.map(async function (resolvetitle) => {
varlet promisesdata = []deepCopy(options);
forif (constXFD title=== of'RFD' options&& data.titlestype === 'prepend') {
//const RfDtext needs= specialawait handling here, because it wraps around the whole page contentgetWikitext(title);
let data.textToModify = deepCopydata.textToModify.replace(options'${pageText}', text);
if (XFD === 'RFD' && data.type === 'prependtext') { // prepend implicitly means page tagging, not actually prepend in this case;
text = await getWikitext(title);
console.log(text)
console.log(data.textToModify)
data.textToModify = data.textToModify.replace('${pageText}', text);
data.type = 'text';
}
if (data.id && data.id=== 'rfd-notify-target') {
data.textToModify = data.textToModify.replace('${redirectTitle}',getKeyByValue(redirectTargets, new mw.Title(title).getSubjectPage().getPrefixedText()) )
}
 
data.title = title
console.log('Data:')
console.log(data)
var promise = editPage(data);
promises.push(trackPromise(promise));
if (!window.abortEdits) await sleep(100); // space out calls - not needed if they're being rejected
await massXFDratelimitPromise; // stop if ratelimit reached (global variable)
 
}
 
Promise.allSettledif (promisesdata.id === 'rfd-notify-target') {
data.textToModify = data.textToModify.replace('${redirectTitle}', getKeyByValue(redirectTargets, new mw.Title(title).getSubjectPage().getPrefixedText()));
.then(function () {
options.progress.toggle(false);}
if (window.abortEdits) {
var abortMessagedata.title = createAbortMessage()title;
let revertEditsLink = $console.log('<aData:', id="massxfdrevertlink">Revert?</a>'data);
revertEditsLink.on('click', revertEdits)
const promise = abortMessage.setLabel($('<span>').append('Edits manually aborted. ').appendeditPage(revertEditsLink)data);
return trackPromise(promise);
 
bodyContent.append(abortMessage.$element);
} else {
var completedElement = createCompletedElement();
completedElement.setLabel(options.doneMessage);
completedElement.$element.css('margin-bottom', '16px');
bodyContent.append(completedElement.$element);
}
resolve()
// alert('Resolved: done');
})
.catch(function (error) {
console.error("Error occurred during title processing:", error);
resolve();
// alert('Resolved: error')
});
});
await Promise.allSettled(promises);
options.progress.toggle(false);
if (window.abortEdits) {
const abortMessage = createAbortMessage();
const revertEditsLink = $('<a id="massxfdrevertlink">Revert?</a>');
revertEditsLink.on('click', revertEdits);
abortMessage.setLabel($('<span>').append('Edits manually aborted. ').append(revertEditsLink));
bodyContent.append(abortMessage.$element);
} else {
const completedElement = createCompletedElement();
completedElement.setLabel(options.doneMessage);
completedElement.$element.css('margin-bottom', '16px');
bodyContent.append(completedElement.$element);
}
}
 
const date = new Date();