Jump to content

User:Kingsacrificer/BookCover.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// ==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.");}
}