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:Widgetkid/RecordingCover.
// ==UserScript==
// @name Dialog Upload Script — Recording Cover Version
// @description Upload an recording cover 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 Recording Cover'
);
$(link).on('click', e => {
e.preventDefault();
openUploadDialogRecording();
});
});
});
// ============================================================
// BUILDER: Create the non-free file description text
// ============================================================
function buildDescriptionText(article, author, source, date) {
const descriptionText = "==Summary==\n" +
"{{Non-free use rationale 2\n" +
"|Description = Official cover of the release [[" + article + "]]\n" +
"|Source = " + (source || "") + "\n" +
"|Author = " + (author || "") + "\n" +
"|Article = " + (article || "") + "\n" +
"|Date = "+ (date || "") + "\n" +
"|Purpose = to serve as the primary means of visual identification at the top of the article dedicated to the work in question.\n" +
"|Replaceability = Any derivative work based upon the cover art would be a copyright violation, so creation of a free image is not possible.\n" +
"|Minimality = This image is being used only once in the infobox of the named article and for purposes of identification only.\n" +
"|Commercial = The use of a low resolution image will not impact the commercial viability of the work.\n" +
"}}\n" +
"==Licensing==\n" +
"{{Non-free album cover|image has rationale=yes}}\n";
// console.log(descriptionText);
return (descriptionText);
}
// ============================================================
// LOGIC: Update |cover= inside an Infobox
// ============================================================
function updateInfoboxCover(pageText, filename) {
const startAlbumIndex = pageText.search(/\{\{\s*Infobox album\b/i);
const startSongIndex = pageText.search(/\{\{\s*Infobox song\b/i);
// console.log("startAlbumIndex = " + startAlbumIndex);
// console.log("startSongIndex = " + startSongIndex);
// If Infobox album not found, look for Infobox song
if (startAlbumIndex === -1) {
// If Infobox song not found, return pageText as is.
if (startSongIndex === -1) {
return pageText;
// If Infobox song is found, find and replace |cover=
} else {
const songFinalText = updateSongInfoboxCover(pageText, filename);
// console.log("songFinalText = " + songFinalText);
return songFinalText;
}
// If Infobox album is found, find and replace |cover=
} else {
const albumFinalText = updateAlbumInfoboxCover(pageText, filename);
// console.log("albumFinalText = " + albumFinalText);
return albumFinalText;
}
}
// ============================================================
// Insert or update |cover= inside an album Infobox
// ============================================================
function updateAlbumInfoboxCover(pageText, filename) {
const coverLine = `| cover = ${filename}`;
// -----------------------------------------------------
// 1) Find Infobox album start
// -----------------------------------------------------
const startIndex = pageText.search(/\{\{\s*Infobox album\b/i);
if (startIndex === -1) {
return 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 album template.");
return pageText; // Fail safe
}
const infobox = pageText.substring(startIndex, endIndex);
let updatedInfobox = infobox;
// -----------------------------------------------------
// 3) Replace or insert |cover=
// -----------------------------------------------------
if (/^\s*\|\s*cover\s*=.*$/mi.test(infobox)) {
updatedInfobox = infobox.replace(
/^\s*\|\s*cover\s*=.*$/mi,
coverLine
);
} else {
updatedInfobox = infobox.replace(
/(\{\{\s*Infobox album[^\n]*\n)/i,
`$1${coverLine}\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;
}
// ============================================================
// LOGIC: Update |cover= inside a Song Infobox
// ============================================================
function updateSongInfoboxCover(pageText, filename) {
const coverLine = `| cover = ${filename}`;
// -----------------------------------------------------
// 1) Find Infobox song start
// -----------------------------------------------------
const startIndex = pageText.search(/\{\{\s*Infobox song\b/i);
if (startIndex === -1) {
return 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 song template.");
return pageText; // Fail safe
}
const infobox = pageText.substring(startIndex, endIndex);
let updatedInfobox = infobox;
// -----------------------------------------------------
// 3) Replace or insert |cover=
// -----------------------------------------------------
if (/^\s*\|\s*cover\s*=.*$/mi.test(infobox)) {
updatedInfobox = infobox.replace(
/^\s*\|\s*cover\s*=.*$/mi,
coverLine
);
} else {
updatedInfobox = infobox.replace(
/(\{\{\s*Infobox song[^\n]*\n)/i,
`$1${coverLine}\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:Widgetkid/RecordingCover.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: Update infobox cover (if checkbox is checked)
if (addInfobox) {
const updatedText = updateInfoboxCover(pageText, filename);
// console.log("updatedText = " + updatedText);
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 recording cover to infobox (script upload via [[User:Widgetkid/RecordingCover.js]])"
}));
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(removeImageNeeded) {
const articleTitle = mw.config.get('wgPageName'); // Get the article's title
// console.log("articleTitle = " + articleTitle);
const talkPageTitle = 'Talk:' + articleTitle; // Construct the talk page title
// console.log("talkPageTitle = " + talkPageTitle);
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;
// console.log("pageText = " + pageText);
// Step 2: Remove the flags from the WikiProject Albums template
let updatedPageText = pageText;
// Remove the imageneeded flag if it exists
if (removeImageNeeded)
updatedPageText = updatedPageText.replace(/\|imageneeded=[^|}]*\s*/g, '');
// console.log("updatedPageText 1= " + updatedPageText);
updatedPageText = updatedPageText.replace(/\| imageneeded=[^|}]*\s*/g, '');
// console.log("updatedPageText 2= " + updatedPageText);
const flagCounter = pageText.indexOf("| imageneeded=")
// console.log("flagCounter= " + flagCounter);
// Step 3: Save the updated content back to the talk page
if (updatedPageText !== pageText && flagCounter > 0) {
try {
await api.edit(talkPageTitle, () => ({
text: updatedPageText,
summary: "Removed unnecessary flags from banner (script upload via [[User:Widgetkid/RecordingCover.js]])"
}));
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 openUploadDialogRecording() {
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 Recording Cover">
<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 / copyright holder (Artist or Label):</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="text" style="width:100%;">
<p style="margin-top:1em;"><b>Select file:</b></p>
<input id="upload-file" type="file">
<p style="margin-top:1em;" id="new_file_name" type="text"></p>
<p style="margin-top:1em;">
<input type="checkbox" id="add-infobox-recording" checked>
<label for="add-infobox-recording">Add image to the album or song Infobox?</label>
</p>
<p style="margin-top:1em;">
<input type="checkbox" id="cb_imageneeded-flag" checked>
<label for="cb_imageneeded-flag">Remove 'imageneeded' flag (albums only)?</label>
</p>
<p style="margin-top:1em; text-align: right;">
<small>v1.0.8</small>
</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-recording").is(":checked");
const removeImageNeeded = $("#cb_imageneeded-flag").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();
// console.log("extension = " + extension);
const filename = `${article} (Recording Cover).${extension}`.replace(/[#<>\[\]|:{}/]+|~{3,}/g, ' -');
// console.log("filename = " + filename);
const api = new mw.Api();
const descriptionText = buildDescriptionText(article, author, source, date);
// console.log("descriptionText = " + descriptionText);
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);
} catch (err) {
console.error(err);
// 2026-03-07: Supressing error message. Unclear why there's an error to catch when upload works.
// Added infobox update to a separate try/catch clause.
// mw.notify("Error during upload.", { type: "error" });
}
try {
// Update article (only if checkbox is checked)
await updateInfobox(api, article, filename, addInfobox);
if (removeImageNeeded)
await removeProjectFlags(removeImageNeeded);
} catch (err) {
console.error(err);
mw.notify("Error during article update.", { type: "error" });
}
},
Cancel: function () {
$(this).dialog("close");
}
}
});
});
}
// Commenting out date_warning function. Was creating error messages and isn't critical.
// 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.");}
// }
})();