User:Kingsacrificer/BookCover.js
Appearance
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 code will be executed when previewing this page.
This user script seems to have a documentation page at User:Kingsacrificer/BookCover.
// ==UserScript==
// @name Dialog Upload Script (Refactored + Auto Infobox Insert)
// @description Upload a file using a dialog and automatically insert it into the article's infobox
// ==/UserScript==
(() => {
mw.loader.using(['mediawiki.util']).then(() => {
$(document).ready(() => {
const link = mw.util.addPortletLink(
'p-cactions',
'#',
'Upload Book Cover'
);
$(link).on('click', e => {
e.preventDefault();
openUploadDialog();
});
});
});
// ============================================================
// BUILDER: Create the non-free file description text
// ============================================================
function buildDescriptionText(article, author, source, date) {
return (
"==Summary==\n" +
"{{Non-free use rationale 2" +
"|Description = Official cover of the book [[" + article + "]]" +
"|Source = " + (source || "") +
"|Author = " + (author || "") +
"|Article = " + (article || "") +
"|Date = "+ (date || "") +
"|Purpose = to serve as the primary means of visual identification at the top of the article dedicated to the work in question." +
"|Replaceability = Any derivative work based upon the cover art would be a copyright violation, so creation of a free image is not possible." +
"|Minimality = Single use in the infobox" +
"|Commercial = The use of a low resolution image will not impact the commercial viability of the work." +
"}}\n" +
"==Licensing==\n" +
"{{Non-free book cover|image has rationale=yes}}"
);
}
// ============================================================
// LOGIC: Insert or update |image= inside an Infobox
// ============================================================
function insertOrUpdateInfoboxImage(pageText, filename) {
const imageLine = `| image = ${filename}`;
// -----------------------------------------------------
// 1) Find Infobox book start
// -----------------------------------------------------
const startIndex = pageText.search(/\{\{\s*Infobox book\b/i);
if (startIndex === -1) {
const newInfobox =
"{{Infobox book\n" +
imageLine + "\n" +
"}}\n\n";
return newInfobox + pageText;
}
// -----------------------------------------------------
// 2) Extract the whole template safely
// -----------------------------------------------------
let braceCount = 0;
let endIndex = -1;
for (let i = startIndex; i < pageText.length; i++) {
if (pageText[i] === '{' && pageText[i+1] === '{') braceCount++;
if (pageText[i] === '}' && pageText[i+1] === '}') braceCount--;
if (braceCount === 0) {
endIndex = i + 2;
break;
}
}
if (endIndex === -1) {
console.log("ERROR: Could not find end of Infobox book template.");
return pageText; // Fail safe
}
const infobox = pageText.substring(startIndex, endIndex);
let updatedInfobox = infobox;
// -----------------------------------------------------
// 3) Replace or insert |image=
// -----------------------------------------------------
if (/^\s*\|\s*image\s*=.*$/mi.test(infobox)) {
updatedInfobox = infobox.replace(
/^\s*\|\s*image\s*=.*$/mi,
imageLine
);
} else {
updatedInfobox = infobox.replace(
/(\{\{\s*Infobox book[^\n]*\n)/i,
`$1${imageLine}\n`
);
}
// -----------------------------------------------------
// 4) Put updated infobox back in page text
// -----------------------------------------------------
const before = pageText.substring(0, startIndex);
const after = pageText.substring(endIndex);
const finalText = before + updatedInfobox + after;
return finalText;
}
// ============================================================
// NETWORK: Upload file using MediaWiki API
// ============================================================
async function uploadFile(api, fileInput, filename, descriptionText) {
return api.upload(fileInput, {
filename: filename,
comment: "Uploaded via userscript User:Kingsacrificer/BookCover.js",
text: descriptionText,
ignorewarnings: 1
});
}
// ============================================================
// NETWORK: After upload, fetch wikitext, insert image, save
// ============================================================
async function updateInfobox(api, article, filename, addInfobox) {
let pageText;
// Step 1: Fetch page text
try {
const result = await api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: article,
formatversion: 2
});
pageText = result.query.pages[0].revisions[0].content;
} catch (e) {
mw.notify("Failed to fetch article text.", { type: "error" });
throw e;
}
// Step 2: Insert/update infobox image (if checkbox is checked)
if (addInfobox) {
const updatedText = insertOrUpdateInfoboxImage(pageText, filename);
if (!updatedText) {
mw.notify("No infobox found — skipping article edit.", { type: "warn" });
return;
}
// Step 3: Save wikitext
try {
await api.edit(article, () => ({
text: updatedText,
summary: "Add book cover to infobox (script upload)"
}));
mw.notify("Infobox updated successfully! Reloading…");
// 👇 RELOAD HERE
setTimeout(() => {
location.reload();
}, 4000);
} catch (e) {
mw.notify("Failed to update article text.", { type: "error" });
throw e;
}
} else {
mw.notify("Infobox update skipped.", { type: "info" });
}
}
// ============================================================
// REMOVE FLAGS
// ============================================================
async function removeProjectFlags(removeNeedsInfobox,removeNeedsInfoboxCover) {
const articleTitle = mw.config.get('wgPageName'); // Get the article's title
const talkPageTitle = 'Talk:' + articleTitle; // Construct the talk page title
const api = new mw.Api();
try {
// Step 1: Fetch the talk page content
const result = await api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: talkPageTitle, // Make sure to fetch the talk page
formatversion: 2
});
// Check if we got valid data back
if (!result.query || !result.query.pages) {
mw.notify('Talk page content not found.', { type: 'error' });
return;
}
const pageText = result.query.pages[0].revisions[0].content;
// Step 2: Remove the flags from the WikiProject Books template
let updatedPageText = pageText;
// Remove the needs-infobox-cover flag if it exists
if (removeNeedsInfoboxCover)
updatedPageText = updatedPageText.replace(/\|needs-infobox-cover=[^|}]*\s*/g, '');
// Remove the needs-info flag if it exists
if (removeNeedsInfobox)
updatedPageText = updatedPageText.replace(/\|needs-infobox=[^|}]*\s*/g, '');
// Step 3: Save the updated content back to the talk page
if (updatedPageText !== pageText) {
try {
await api.edit(talkPageTitle, () => ({
text: updatedPageText,
summary: "Removed unnecessary flags from banner (script upload)"
}));
mw.notify("Flags removed successfully! Reloading…");
}
// Debug: Log the edit result
catch (e) {
mw.notify('Error editing the talk page.', { type: 'error' });
throw e;
}
} else {
mw.notify('No flags found to remove.', { type: 'info' });
}
} catch (error) {
console.error('Error:', error);
mw.notify('Error removing flags from talk page.', { type: 'error' });
}
}
// ============================================================
// UI: DIALOG + UPLOAD HANDLER
// ============================================================
function openUploadDialog() {
mw.loader.using(['jquery.ui', 'mediawiki.api']).then(() => {
const articleTitle =
mw.config.get('wgNamespaceNumber') === 0
? mw.config.get('wgPageName').replace(/_/g, ' ')
: '';
const $dialog = $(`
<div title="Upload File">
<p style="margin-top:1em;"><b>Article Title:</b></p>
<input id="upload-article" type="text" style="width:100%;" value="${articleTitle}">
<p style="margin-top:1em;"><b>Author:</b></p>
<input id="upload-author" type="text" style="width:100%;">
<p style="margin-top:1em;"><b>Source of the file:</b></p>
<input id="upload-source" type="text" style="width:100%;">
<p style="margin-top:1em;"><b>Date of Publication:</b></p>
<input id="date" type="number" style="width:100%;" onchange = "date_warning();">
<p style="margin-top:1em;"><b>Select file:</b></p>
<input id="upload-file" type="file">
<p style="margin-top:1em;" id="date_warning" type="text" style="color:red"></p>
<p style="margin-top:1em;" id="new_file_name" type="text"></p>
<p style="margin-top:1em;">
<input type="checkbox" id="add-infobox-book" checked>
<label for="add-infobox-book">Add image to the Book Infobox?</label>
</p>
<p style="margin-top:1em;">
<input type="checkbox" id="cb_infobox" checked>
<label for="add-infobox-book">Remove 'needs-infobox' flag?</label>
</p>
<p style="margin-top:1em;">
<input type="checkbox" id="cb_cover" checked>
<label for="add-infobox-book">Remove 'needs-infobox-cover' flag?</label>
</p>
</div>
`);
$('body').append($dialog);
$dialog.dialog({
width: 500,
modal: true,
closeOnEscape: true,
autoOpen: true,
close: () => $dialog.remove(),
buttons: {
"Upload": async function () {
const fileInput = document.getElementById("upload-file");
const file = fileInput.files[0];
const article = $("#upload-article").val().trim();
const author = $("#upload-author").val().trim();
const source = $("#upload-source").val().trim();
const date = $("#date").val().trim();
const addInfobox = $("#add-infobox-book").is(":checked");
const removeNeedsInfobox = $("#cb_infobox").is(":checked");
const removeNeedsInfoboxCover = $("#cb_cover").is(":checked");
if (!file) {
mw.notify("No file selected!", { type: "error" });
return;
}
// Build filename
const extension =
(file.type.includes('image/')
? file.type.split('/').pop()
: file.name.split('.').pop()).toLowerCase();
const filename = `${article} (Book Cover).${extension}`.replace(/[#<>\[\]|:{}/]+|~{3,}/g, ' -');
const api = new mw.Api();
const descriptionText = buildDescriptionText(article, author, source, date);
try {
// Upload file
const uploadResult = await uploadFile(api, fileInput, filename, descriptionText);
if (uploadResult.warnings) {
mw.notify("Upload succeeded with warnings: " + JSON.stringify(uploadResult.warnings), { type: "warn" });
}
mw.notify("File uploaded: " + uploadResult.upload.filename);
$('#new_file_name').text("Uploaded File: "+filename);
// Update article (only if checkbox is checked)
await updateInfobox(api, article, filename, addInfobox);
if (removeNeedsInfobox || removeNeedsInfoboxCover)
await removeProjectFlags(removeNeedsInfobox,removeNeedsInfoboxCover);
} catch (err) {
console.error(err);
mw.notify("Error during upload or article update.", { type: "error" });
}
},
Cancel: function () {
$(this).dialog("close");
}
}
});
});
}
})();
function date_warning(){
const date = $("#date").val().trim();
if (date<1930)
{$('#date_warning').text("As the cover is published before 1930, are you sure that the file is not in public domain, and hence, cannot be uploaded to Wikipedia Commons? Please proceed with caution.");}
}