Difference between revisions of "MediaWiki:Gadget-BulkUpload/Runtime.js"
From Nookipedia, the Animal Crossing wiki
SuperHamster (talk | contribs) (Moving default description into its own variable for easier changes) |
PanchamBro (talk | contribs) m (fixing licensing template name) |
||
(27 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
/** | /** | ||
* Turns Special:BulkUpload into a bulk upload form. Filenames are kept as-is and all files will have the same information. | * Turns Special:BulkUpload into a bulk upload form. Filenames are kept as-is and all files will have the same information. | ||
− | |||
* Created by SuperHamster on Nookipedia (https://nookipedia.com/wiki/User:SuperHamster) | * Created by SuperHamster on Nookipedia (https://nookipedia.com/wiki/User:SuperHamster) | ||
* Licensed under CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/) | * Licensed under CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/) | ||
**/ | **/ | ||
+ | |||
(function() { | (function() { | ||
− | + | "use strict"; | |
− | + | // Set title of page as it appears in the browser tab:: | |
− | + | const PAGETITLE = "Bulk upload form - Animal Crossing Wiki - Nookipedia"; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | == | + | // List of warnings that always result in upload failure, even if ignorewarnings is checked: |
− | + | const FATALWARNINGS = ["fileexists-no-change", "verification-error", "abusefilter-disallowed"]; | |
+ | |||
+ | // Milliseconds to wait between uploads, to avoid rate limiting: | ||
+ | const SLEEPTIME = 500; | ||
+ | // Milliseconds to give each file to upload before moving to the next file: | ||
+ | const TIMEOUT = 30000; | ||
+ | |||
+ | // Default file description that is loaded in the form: | ||
+ | const DEFAULTDESCRIPTION = `== Summary == | ||
+ | {{File Info | ||
+ | |description = Describe the file (THIS IS NOT OPTIONAL) | ||
+ | |source = Where you found the file (THIS IS NOT OPTIONAL) | ||
+ | }} | ||
− | + | == Licensing == | |
− | + | {{Fairuse}}`; | |
− | + | ||
− | + | document.title = PAGETITLE; | |
− | + | $('#firstHeading').html('Bulk upload form'); | |
+ | $('#bodyContent').empty(); | ||
+ | $('#bodyContent').append('<p>Use this form to upload multiple files at once. Filenames are kept as-is and all files will have the same information.</p><p>Uploads through this tool are marked as bot edits in recent changes. To cancel an upload that is in progress, simply navigate away from this page.</p>'); | ||
+ | var htmlUploadForm = `<fieldset> | ||
<legend>Files and info</legend> | <legend>Files and info</legend> | ||
<label for="bulkUploadFiles">Select files:</label><br> | <label for="bulkUploadFiles">Select files:</label><br> | ||
<input type="file" id="bulkUploadFiles" multiple/><br><br> | <input type="file" id="bulkUploadFiles" multiple/><br><br> | ||
<label for="bulkUploadInfo">File description</legend><br> | <label for="bulkUploadInfo">File description</legend><br> | ||
− | <textarea id="bulkUploadInfo" cols="80" rows="12">` + | + | <textarea id="bulkUploadInfo" cols="80" rows="12">` + DEFAULTDESCRIPTION + `</textarea><br><br> |
<input type="checkbox" id="bulkUploadIgnoreWarnings"><label for="bulkUploadIgnoreWarnings">Ignore warnings (check to override existing files)</label><br><br> | <input type="checkbox" id="bulkUploadIgnoreWarnings"><label for="bulkUploadIgnoreWarnings">Ignore warnings (check to override existing files)</label><br><br> | ||
<button id="bulkUploadButton" type="button">Upload files</button> | <button id="bulkUploadButton" type="button">Upload files</button> | ||
Line 39: | Line 48: | ||
</fieldset> | </fieldset> | ||
`; | `; | ||
− | + | $('#bodyContent').append(htmlUploadForm); | |
− | + | document.getElementById('bulkUploadButton').addEventListener("click", function () { | |
− | + | const api = new mw.Api(); | |
− | + | var started = 0; | |
− | + | var completed = 0; | |
− | + | var timedOut = []; | |
+ | document.getElementById('bulkUploadButton').disabled = true; | ||
+ | $('#bulkUploadStatus').empty(); | ||
+ | |||
+ | function timeout(ms) { | ||
+ | return new Promise(resolve => { setTimeout(() => resolve('timed out'), ms)}); | ||
+ | } | ||
+ | |||
+ | async function uploadFile(file, uploadParams) { | ||
+ | started++; | ||
+ | $('#bulkUploadStatus').append('Starting upload for ' + file.name + '...<br>'); | ||
+ | let uploadPromise = api.upload(file, uploadParams).always(response => { | ||
+ | completed++; | ||
+ | var status = ""; | ||
+ | if (timedOut.includes(file.name)) { | ||
+ | timedOut = timedOut.filter(item => item !== file.name); | ||
+ | } | ||
+ | if (typeof response === 'object') { | ||
+ | if (response.hasOwnProperty('upload') && upload.result == 'Success') { | ||
+ | status = 'Upload succeeded for ' + file.name; | ||
+ | } else { | ||
+ | status = 'Upload <span style="color: red;">failed</span> for ' + file.name + ' due to unknown error'; | ||
+ | } | ||
+ | } else if (typeof response === 'string') { | ||
+ | if (FATALWARNINGS.includes(response)) { | ||
+ | status = 'Upload <span style="color: red;">failed</span> for ' + file.name + ' with error "' + response + '"'; | ||
+ | } else { | ||
+ | if ($('#bulkUploadIgnoreWarnings').is(':checked')) { | ||
+ | status = 'Upload succeeded for ' + file.name + ' with warning "' + response + '"'; | ||
+ | } else { | ||
+ | status = 'Upload <span style="color: red;">failed</span> for ' + file.name + ' with error "' + response + '"'; | ||
+ | } | ||
+ | } | ||
+ | } else { | ||
+ | status = 'Upload <span style="color: red;">failed</span> for ' + file.name + ' due to unknown error'; | ||
+ | } | ||
+ | $('#bulkUploadStatus').append(status + ' (' + completed + ' out of ' + files.length + ')<br>'); | ||
+ | if (completed == files.length) { | ||
+ | $('#bulkUploadStatus').append('<span style="color: green;">Done!</span><br>'); | ||
+ | document.getElementById('bulkUploadButton').disabled = false; | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | try { | ||
+ | var onTimeout = timeout(TIMEOUT); | ||
+ | await Promise.race([uploadPromise, onTimeout]).then((value) => { | ||
+ | if (value === 'timed out') { | ||
+ | timedOut.push(file.name); | ||
+ | $('#bulkUploadStatus').append(file.name + ' is taking a long time to upload; moving to the next file.<br>'); | ||
+ | } | ||
+ | if (started == files.length && timedOut.length > 0) { | ||
+ | $('#bulkUploadStatus').append('The following uploads are still running in the background and may be stuck; recommend retrying: ' + timedOut + '<br>') | ||
+ | } | ||
+ | }).catch((value) => { | ||
+ | if (value === 'timed out') { | ||
+ | timedOut.push(file.name); | ||
+ | $('#bulkUploadStatus').append(file.name + ' is taking a long time to upload; moving to the next file.<br>'); | ||
+ | } | ||
+ | if (started == files.length && timedOut.length > 0) { | ||
+ | $('#bulkUploadStatus').append('The following uploads are still running in the background and may be stuck; recommend retrying: ' + timedOut + '<br>') | ||
+ | } | ||
+ | }) | ||
+ | } catch (e) { | ||
+ | console.error(e); | ||
+ | } finally { | ||
+ | await timeout(SLEEPTIME); | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | async function iterateFiles() { | ||
+ | // Remove duplicates: | ||
+ | const uniqueFiles = [...new Set(files)]; | ||
+ | |||
+ | // Iterate through files and set upload params: | ||
+ | for (const file of uniqueFiles) { | ||
+ | var uploadParams = {}; | ||
+ | if($('#bulkUploadIgnoreWarnings').is(':checked')) { | ||
+ | uploadParams = { | ||
+ | filename: file.name, | ||
+ | text: $('#bulkUploadInfo').val(), | ||
+ | comment: "Uploaded via bulk upload gadget", | ||
+ | ignorewarnings: "yes", | ||
+ | format: "json" | ||
+ | }; | ||
+ | } else { | ||
+ | uploadParams = { | ||
+ | filename: file.name, | ||
+ | text: $('#bulkUploadInfo').val(), | ||
+ | comment: "Uploaded via bulk upload gadget", | ||
+ | format: "json" | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | // Call upload func, wait until it completes or times out before going to next file | ||
+ | const response = await uploadFile(file, uploadParams); | ||
+ | } | ||
+ | } | ||
− | + | var files = $('#bulkUploadFiles')[0].files; | |
− | + | iterateFiles(); | |
− | + | }); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
})(); | })(); |
Latest revision as of 15:40, May 30, 2022
/**
* Turns Special:BulkUpload into a bulk upload form. Filenames are kept as-is and all files will have the same information.
* Created by SuperHamster on Nookipedia (https://nookipedia.com/wiki/User:SuperHamster)
* Licensed under CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/)
**/
(function() {
"use strict";
// Set title of page as it appears in the browser tab::
const PAGETITLE = "Bulk upload form - Animal Crossing Wiki - Nookipedia";
// List of warnings that always result in upload failure, even if ignorewarnings is checked:
const FATALWARNINGS = ["fileexists-no-change", "verification-error", "abusefilter-disallowed"];
// Milliseconds to wait between uploads, to avoid rate limiting:
const SLEEPTIME = 500;
// Milliseconds to give each file to upload before moving to the next file:
const TIMEOUT = 30000;
// Default file description that is loaded in the form:
const DEFAULTDESCRIPTION = `== Summary ==
{{File Info
|description = Describe the file (THIS IS NOT OPTIONAL)
|source = Where you found the file (THIS IS NOT OPTIONAL)
}}
== Licensing ==
{{Fairuse}}`;
document.title = PAGETITLE;
$('#firstHeading').html('Bulk upload form');
$('#bodyContent').empty();
$('#bodyContent').append('<p>Use this form to upload multiple files at once. Filenames are kept as-is and all files will have the same information.</p><p>Uploads through this tool are marked as bot edits in recent changes. To cancel an upload that is in progress, simply navigate away from this page.</p>');
var htmlUploadForm = `<fieldset>
<legend>Files and info</legend>
<label for="bulkUploadFiles">Select files:</label><br>
<input type="file" id="bulkUploadFiles" multiple/><br><br>
<label for="bulkUploadInfo">File description</legend><br>
<textarea id="bulkUploadInfo" cols="80" rows="12">` + DEFAULTDESCRIPTION + `</textarea><br><br>
<input type="checkbox" id="bulkUploadIgnoreWarnings"><label for="bulkUploadIgnoreWarnings">Ignore warnings (check to override existing files)</label><br><br>
<button id="bulkUploadButton" type="button">Upload files</button>
</fieldset>
<br>
<fieldset>
<legend>Upload status</legend>
<span id="bulkUploadStatus">Waiting for user to start upload.</span>
</fieldset>
`;
$('#bodyContent').append(htmlUploadForm);
document.getElementById('bulkUploadButton').addEventListener("click", function () {
const api = new mw.Api();
var started = 0;
var completed = 0;
var timedOut = [];
document.getElementById('bulkUploadButton').disabled = true;
$('#bulkUploadStatus').empty();
function timeout(ms) {
return new Promise(resolve => { setTimeout(() => resolve('timed out'), ms)});
}
async function uploadFile(file, uploadParams) {
started++;
$('#bulkUploadStatus').append('Starting upload for ' + file.name + '...<br>');
let uploadPromise = api.upload(file, uploadParams).always(response => {
completed++;
var status = "";
if (timedOut.includes(file.name)) {
timedOut = timedOut.filter(item => item !== file.name);
}
if (typeof response === 'object') {
if (response.hasOwnProperty('upload') && upload.result == 'Success') {
status = 'Upload succeeded for ' + file.name;
} else {
status = 'Upload <span style="color: red;">failed</span> for ' + file.name + ' due to unknown error';
}
} else if (typeof response === 'string') {
if (FATALWARNINGS.includes(response)) {
status = 'Upload <span style="color: red;">failed</span> for ' + file.name + ' with error "' + response + '"';
} else {
if ($('#bulkUploadIgnoreWarnings').is(':checked')) {
status = 'Upload succeeded for ' + file.name + ' with warning "' + response + '"';
} else {
status = 'Upload <span style="color: red;">failed</span> for ' + file.name + ' with error "' + response + '"';
}
}
} else {
status = 'Upload <span style="color: red;">failed</span> for ' + file.name + ' due to unknown error';
}
$('#bulkUploadStatus').append(status + ' (' + completed + ' out of ' + files.length + ')<br>');
if (completed == files.length) {
$('#bulkUploadStatus').append('<span style="color: green;">Done!</span><br>');
document.getElementById('bulkUploadButton').disabled = false;
}
});
try {
var onTimeout = timeout(TIMEOUT);
await Promise.race([uploadPromise, onTimeout]).then((value) => {
if (value === 'timed out') {
timedOut.push(file.name);
$('#bulkUploadStatus').append(file.name + ' is taking a long time to upload; moving to the next file.<br>');
}
if (started == files.length && timedOut.length > 0) {
$('#bulkUploadStatus').append('The following uploads are still running in the background and may be stuck; recommend retrying: ' + timedOut + '<br>')
}
}).catch((value) => {
if (value === 'timed out') {
timedOut.push(file.name);
$('#bulkUploadStatus').append(file.name + ' is taking a long time to upload; moving to the next file.<br>');
}
if (started == files.length && timedOut.length > 0) {
$('#bulkUploadStatus').append('The following uploads are still running in the background and may be stuck; recommend retrying: ' + timedOut + '<br>')
}
})
} catch (e) {
console.error(e);
} finally {
await timeout(SLEEPTIME);
return true;
}
}
async function iterateFiles() {
// Remove duplicates:
const uniqueFiles = [...new Set(files)];
// Iterate through files and set upload params:
for (const file of uniqueFiles) {
var uploadParams = {};
if($('#bulkUploadIgnoreWarnings').is(':checked')) {
uploadParams = {
filename: file.name,
text: $('#bulkUploadInfo').val(),
comment: "Uploaded via bulk upload gadget",
ignorewarnings: "yes",
format: "json"
};
} else {
uploadParams = {
filename: file.name,
text: $('#bulkUploadInfo').val(),
comment: "Uploaded via bulk upload gadget",
format: "json"
};
}
// Call upload func, wait until it completes or times out before going to next file
const response = await uploadFile(file, uploadParams);
}
}
var files = $('#bulkUploadFiles')[0].files;
iterateFiles();
});
})();