Jump to content

User:DreamRimmer/User not around.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.
// Fork of [[User:Andrybak/Scripts/Not around.js]]
// This script helps you add a "Not around" template to a user's talk page if they haven't edited in the last 6 months

(function() {
    'use strict';

    const config = {
        wikipage: '[[w:User:DreamRimmer/User not around.js|User not around]]',
        version: '3.4'
    };

    const USERSCRIPT_NAME = 'Not around userscript';
    const LOG_PREFIX = `[${USERSCRIPT_NAME}]:`;

    function error(...toLog) {
        console.error(LOG_PREFIX, ...toLog);
    }

    function warn(...toLog) {
        console.warn(LOG_PREFIX, ...toLog);
    }

    function info(...toLog) {
        console.info(LOG_PREFIX, ...toLog);
    }

    function debug(...toLog) {
        console.debug(LOG_PREFIX, ...toLog);
    }

    function notify(notificationMessage) {
        mw.notify(notificationMessage, {
            title: USERSCRIPT_NAME
        });
    }

    function errorAndNotify(errorMessage, rejection) {
        error(errorMessage, rejection);
        notify(errorMessage);
    }

    const ABSENSE_MONTHS_MINIMUM = 6;
    const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    const mw = window.mw;
    const DEBUG = false;

    function constructAd() {
        return `using ${config.wikipage} v${config.version}`;
    }

    function constructEditSummary(username, lastContribDate) {
        const day = lastContribDate.getUTCDate();
        const month = MONTHS[lastContribDate.getUTCMonth()];
        const year = lastContribDate.getUTCFullYear();
        return `/* top */ add [[Template:Not around]] – user ${username} hasn't edited since ${day} ${month} ${year} (${constructAd()})`;
    }

    /**
     * Asynchronously load specified number of contributions of specified username.
     */
    function loadNLastUserContribs(username, n) {
        const api = new mw.Api();
        return api.get({
            action: 'query',
            list: 'usercontribs',
            ucuser: username,
            uclimit: n
        });
    }

    /**
     * Asynchronously load the very last contribution of specified username.
     */
    function loadLastUserContrib(username) {
        return new Promise((resolve, reject) => {
            loadNLastUserContribs(username, 1).then(response => {
                debug(response);
                const lastContrib = response.query.usercontribs[0];
                resolve(lastContrib);
            }, rejection => {
                reject(rejection);
            });
        });
    }

    function isoStringToDate(timestamp) {
        const d = new Date(timestamp);
        return d;
    }

    function loadCurrentWikitext(pagename) {
        return new Promise((resolve, reject) => {
            const api = new mw.Api();
            api.get({
                action: 'query',
                titles: pagename,
                prop: 'revisions',
                rvprop: 'content',
                rvslots: 'main',
                formatversion: 2
            }).then(response => {
                resolve(response.query.pages[0].revisions[0].slots.main.content);
            }, rejection => {
                reject(rejection);
            });
        });
    }

    function addNotAroundTemplateIfAbsent(username, lastContribDate) {
    const day = lastContribDate.getUTCDate();
    const month = MONTHS[lastContribDate.getUTCMonth()];
    const year = lastContribDate.getUTCFullYear();
    info(`${username} hasn't edited since ${day} ${month} ${year}.`);

    const userTalkPageTitle = 'User_talk:' + username;
    loadCurrentWikitext(userTalkPageTitle).then(wikitext => {
        const regex = /\{\{\s*not\s*around\s*(?:\s*\|[^}]+)?\s*\}\}/i;
        if (regex.test(wikitext)) {
            info(userTalkPageTitle + ' already has the template. Showing it to the user and aborting.');
            location.assign('/wiki/' + userTalkPageTitle);
            return;
        }

        const formHtml = `
            <form id="templateChoiceForm">
            <p>Choose the appropriate option:</p>
                <label>
                    <input type="checkbox" id="inactiveCheckbox" />
                    This user is not currently active on Wikipedia
                </label><br>
                <label>
                    <input type="checkbox" id="leftCheckbox" checked />
                    This user may have left Wikipedia (default)
                </label>
            </form>
        `;

        const dialog = $('<div>').html(formHtml).dialog({
            title: 'Not around',
            width: 400,
            buttons: {
                'Preview': function() {
                    const isInactive = $('#inactiveCheckbox').prop('checked');
                    const isLeft = $('#leftCheckbox').prop('checked');

                    let template;
                    if (isInactive) {
                        template = `{{Not around|is not currently active on Wikipedia|date=${day} ${month} ${year}}}`;
                    } else if (isLeft) {
                        template = `{{Not around|date=${day} ${month} ${year}}}`;
                    }

                    const newWikitext = `${template}\n` + wikitext;
                    const editSummary = constructEditSummary(username, lastContribDate);

                    if (DEBUG) {
                        debug(newWikitext.slice(0, 40));
                        debug(editSummary);
                    }

                    const previewWikitext = newWikitext.split("\n").slice(0, 10).join("\n");
                    const previewText = previewWikitext.replace(template, `<span style="background-color: #d4fdd4">${template}</span>`);

                    const previewDialog = $('<div>').html(`
                        <p><strong>Preview of the edit (Wikitext):</strong></p>
                        <pre>${previewText}</pre>
                        <p><strong>Do you want to add the above template to the user talk page?</strong></p>
                    `).dialog({
                        title: 'Preview',
                        buttons: {
                            'Submit': function() {
                                const api = new mw.Api();
                                api.postWithEditToken({
                                    action: 'edit',
                                    title: userTalkPageTitle,
                                    text: newWikitext,
                                    summary: editSummary
                                }).then(response => {
                                    loadLastUserContrib(mw.user.getName()).then(theEdit => {
                                        location.assign('/wiki/Special:Diff/' + theEdit.revid);
                                    }, rejection => {
                                        errorAndNotify(`Cannot load last contribution by ${mw.user.getName()}.`, rejection);
                                    });
                                }, rejection => {
                                    errorAndNotify(`Cannot edit page [[${userTalkPageTitle}]]`, rejection);
                                });

                                $(this).dialog('close');
                            },
                            'Cancel': function() {
                                $(this).dialog('close');
                            }
                        }
                    });

                    $(this).dialog('close');
                },
                'Cancel': function() {
                    $(this).dialog('close');
                }
            }
        });

        $('#inactiveCheckbox').change(function() {
            if ($(this).prop('checked')) {
                $('#leftCheckbox').prop('checked', false);
            }
        });

        $('#leftCheckbox').change(function() {
            if ($(this).prop('checked')) {
                $('#inactiveCheckbox').prop('checked', false);
            }
        });
    });
}

    function runPortlet () {
        const username = mw.config.get('wgRelevantUserName');
        if (!username) {
            errorAndNotify('Cannot find a username', null);
            return;
        }
        loadLastUserContrib(username).then(lastContrib => {
            if (!lastContrib) {
                notify(`User ${username} has zero contributions. Aborting.`);
                return;
            }
            if (lastContrib.user != username) {
                errorAndNotify(`Received wrong user. Actual ${lastContrib.user} ≠ expected ${username}. Aborting.`, null);
                return;
            }
            const lastContribDate = isoStringToDate(lastContrib.timestamp);
            const currentDate = new Date();
            info('Last edit timestamp =', lastContrib.timestamp);

            const daysSinceLastEdit = Math.floor((currentDate - lastContribDate) / (1000 * 60 * 60 * 24));
            if (daysSinceLastEdit >= ABSENSE_MONTHS_MINIMUM * 30) {
                addNotAroundTemplateIfAbsent(username, lastContribDate);
            } else {
                notify(`${username} is still an active user. Last edit was on ${lastContribDate.getUTCDate()} ${MONTHS[lastContribDate.getUTCMonth()]} ${lastContribDate.getUTCFullYear()}. Aborting.`);
            }
        }, rejection => {
            errorAndNotify(`Cannot load contributions of ${username}. Aborting.`, rejection);
        });
    }

    function lazyLoadNotAround() {
        debug('Loading...');
        const namespaceNumber = mw.config.get('wgNamespaceNumber');
        if (namespaceNumber === -1 || namespaceNumber === 2 || namespaceNumber === 3) {
            if (!mw.loader.using) {
                warn('Function mw.loader.using is not loaded yet. Retrying...');
                setTimeout(lazyLoadNotAround, 300);
                return;
            }
            mw.loader.using(
                ['mediawiki.util'],
                () => {
                    const link = mw.util.addPortletLink('p-cactions', '#', 'Not around', 'ca-notaround', 'add template {{Not around}}');
                    if (!link) {
                        info('Cannot create portlet link (mw.util.addPortletLink). Assuming unsupported skin. Aborting.');
                        return;
                    }
                    link.onclick = event => {
                        event.preventDefault();
                        mw.loader.using('mediawiki.api', runPortlet);
                    };
                },
                (e) => {
                    error('Cannot add portlet link', e);
                }
            );
        } else {
            warn('Triggered on a bad namespace =', namespaceNumber);
        }
    }

    if (document.readyState !== 'loading') {
        lazyLoadNotAround();
    } else {
        warn('Cannot load yet. Setting up a listener...');
        document.addEventListener('DOMContentLoaded', lazyLoadNotAround);
    }
})();