Jump to content

User:DreamRimmer/massMessageLite.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by DreamRimmer (talk | contribs) at 10:41, 17 June 2025 (update). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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.
//WARNING: You take full responsibility for any actions taken using this script.
//You must read and understand all relevant Wikipedia policies and abide by them
//when using this tool; failure to do so may result in being blocked from editing.
//<nowiki>
$(document).ready(function() {
    var isPaused = false, pauseResumeButton, minorEditCheckbox, createIfMissingCheckbox, talkMessageCheckInterval, lastTalkRevId = null, operatorPausedByNewMessage = false;

    function initializeMassMessage() {
        $('#mw-content-text > p').remove();
        $('#firstHeading').text('massMessageLite');

        var deliveredPages = [], skippedPages = [], linksContainer, deliveredBox, skippedBox;

        var pagesTextarea = new OO.ui.MultilineTextInputWidget({
                placeholder: 'User talk:Example user\nUser talk:Example user2\nWikipedia talk:Example',
                autosize: true,
                rows: 10
            }),
            subjectInputField = new OO.ui.TextInputWidget({
                placeholder: 'Subject of the message'
            }),
            messageTextarea = new OO.ui.MultilineTextInputWidget({
                placeholder: 'Body of the message',
                autosize: true,
                rows: 10
            }),
            summaryInputField = new OO.ui.TextInputWidget({
                placeholder: 'Edit summary'
            }),
            visualPreviewButton = new OO.ui.ButtonWidget({
                label: 'Preview',
                flags: ['primary']
            }),
            startButton = new OO.ui.ButtonWidget({
                label: 'Send Message',
                icon: 'alert',
                flags: ['primary', 'progressive'],
                disabled: true
            }),
            cancelButton = new OO.ui.ButtonWidget({
                label: 'Cancel',
                flags: ['primary', 'destructive'],
                href: 'https:' + mw.config.get('wgServer')
            }),
            skipDuplicateCheckbox = new OO.ui.CheckboxInputWidget({
                selected: false
            }),
            minorEditCheckbox = new OO.ui.CheckboxInputWidget({
                selected: false
            }),
            createIfMissingCheckbox = new OO.ui.CheckboxInputWidget({
                selected: false
            }),
            pauseResumeButton = new OO.ui.ButtonWidget({
                label: 'Pause',
                icon: new OO.ui.IconWidget({icon: 'pause', flags: ['progressive']}),
                flags: ['progressive'],
                disabled: true
            }),
            logContainer = $("<ul>").css({
                'padding': '10px',
                'margin-top': '10px',
                'border': '1px solid #ccc',
                'list-style-type': 'none',
                'display': 'table-row',
                'width': '70%'
            }).hide(),
            previewContainer = $('<div>').css({
                'padding': '10px',
                'margin': '10px',
                'overflow': 'auto',
                'min-height': '200px',
                'max-height': '400px',
                'border': '1px solid #ccc',
                'width': '65%',
                'display': 'table-row'
            }).hide();

        linksContainer = $('<div>').css({'margin':'10px 0 0 0','padding':'5px 0 0 0','display':'none'});
        deliveredBox = $('<div>').css({'display':'none','margin':'6px 0'}).append(
            $('<textarea readonly style="width:100%;min-height:120px;max-height:400px;resize:vertical;font-family:monospace;font-size:13px;box-sizing:border-box;"></textarea>')
        );
        skippedBox = $('<div>').css({'display':'none','margin':'6px 0'}).append(
            $('<textarea readonly style="width:100%;min-height:120px;max-height:400px;resize:vertical;font-family:monospace;font-size:13px;box-sizing:border-box;"></textarea>')
        );
        linksContainer.append(
            $('<a href="#" style="margin-right:15px;font-weight:bold;" id="show-delivered-list">Show delivered pages</a>'),
            $('<a href="#" style="font-weight:bold;" id="show-skipped-list">Show skipped pages</a>'),
            deliveredBox,
            skippedBox
        );

        var pagesCountInfo = $('<div id="pages-count-info" style="margin-bottom:10px;margin-top:10px;padding:5px 0 5px 0;font-weight:bold;color:blue;display:none;"></div>');
        $('#mw-content-text').append(
            $('<p>').html('<span style="font-weight: bold; color: red;">Warning:</span> <span style="font-weight: bold; color: black;">You take full responsibility for any actions taken using this script. You must read and understand all relevant <a href="/wiki/Wikipedia:Policies_and_guidelines" target="_blank">Wikipedia policies</a> and abide by them when using this tool; failure to do so may result in being <a href="/wiki/Wikipedia:Blocking_policy" target="_blank">blocked from editing</a>.</span>'),
            $('<p>').text('Enter list of pages (one per line):').css('font-weight', 'bold'),
            pagesCountInfo,
            pagesTextarea.$element.css({'margin-bottom': '15px'}),
            $('<p>').text('Subject:').css('font-weight', 'bold'),
            subjectInputField.$element,
            $('<p>').text('Message:').css('font-weight', 'bold'),
            messageTextarea.$element,
            $('<p>').text('Edit summary:').css('font-weight', 'bold'),
            summaryInputField.$element,
            $('<div>').css('padding', '10px'),
            $('<label>').append(skipDuplicateCheckbox.$element, ' Skip pages with the same section name already present'),
            $('<div>').css('padding', '10px'),
            $('<label>').append(minorEditCheckbox.$element, ' Mark edits as minor'),
            $('<div>').css('padding', '10px'),
            $('<label>').append(createIfMissingCheckbox.$element, ' Create page if it does not exist'),
            $('<div>').css('padding', '10px'),
            $('<div>').append(visualPreviewButton.$element, startButton.$element, pauseResumeButton.$element, cancelButton.$element),
            previewContainer,
            '<br/>',
            logContainer,
            linksContainer
        );

        function updatePagesCount() {
            var lines = pagesTextarea.getValue().split("\n"), count = 0;
            lines.forEach(function(page){ if(page.trim()) count++; });
            if (count > 0) {
                pagesCountInfo.text('You are about to send a message to ' + count + ' page(s).').show();
            } else {
                pagesCountInfo.hide();
            }
        }
        pagesTextarea.on('change', updatePagesCount);
        updatePagesCount();

        function addLogEntry(icon, message, color) {
            var logEntry = $('<li>').css({ 'margin-bottom': '5px', 'color': color }).append(
                new OO.ui.IconWidget({ icon: icon, flags: ['progressive'] }).$element.css({ 'margin-right': '5px' }),
                $('<span>').text(message)
            );
            logContainer.append(logEntry).show();
        }

        function previewMessage() {
            var subject = subjectInputField.getValue().trim(),
                message = messageTextarea.getValue().trim(),
                wikitext = '== ' + subject + ' ==\n' + message;

            var previewBox = $('<div>').css({
                'padding': '10px',
                'margin': '10px',
                'overflow': 'auto',
                'width': '70%',
                'height': '400px'
            });

            previewContainer.empty().append($('<h2>').text("Message preview:"), previewBox).show();
            new mw.Api().post({
                action: 'parse',
                text: wikitext,
                title: 'Preview',
                contentmodel: 'wikitext',
                pst: true,
                format: 'json'
            }).done(function(data) {
                previewBox.html(data.parse.text['*']);
                $('html, body').animate({
                    scrollTop: previewContainer.offset().top
                }, 500);
            }).fail(function() {
                previewBox.html('<p>Error loading preview</p>');
            });

            startButton.setDisabled(false);
        }

        function setPauseResumeState(paused) {
            if(paused) {
                pauseResumeButton.setLabel('Resume')
                    .setIcon(new OO.ui.IconWidget({icon: 'play', flags: ['progressive']}))
                    .setFlags(['progressive']);
            } else {
                pauseResumeButton.setLabel('Pause')
                    .setIcon(new OO.ui.IconWidget({icon: 'pause', flags: ['progressive']}))
                    .setFlags(['progressive']);
            }
        }

        pauseResumeButton.on('click', function() {
            isPaused = !isPaused;
            setPauseResumeState(isPaused);
            if (!isPaused && !operatorPausedByNewMessage && typeof window.currentProcessNextPage === "function") window.currentProcessNextPage();
        });

        function resolveRedirect(pageTitle) {
            return new Promise(function(resolve, reject) {
                new mw.Api().get({
                    action: 'query',
                    titles: pageTitle,
                    redirects: 1
                }).done(function(data) {
                    if (data.query && data.query.redirects && data.query.redirects.length > 0) {
                        resolve(data.query.redirects[0].to);
                    } else {
                        var pageId = Object.keys(data.query.pages)[0];
                        if (pageId === '-1') return resolve(pageTitle);
                        resolve(data.query.pages[pageId].title);
                    }
                }).fail(function() {
                    resolve(pageTitle);
                });
            });
        }

        function getLastTalkPageRevId(callback) {
            var userTalkPage = 'User talk:' + mw.config.get('wgUserName');
            new mw.Api().get({
                action: 'query',
                prop: 'revisions',
                titles: userTalkPage,
                rvprop: 'ids|timestamp',
                rvlimit: 1
            }).done(function(data) {
                var pageId = Object.keys(data.query.pages)[0];
                if (pageId === '-1') return callback(null);
                var rev = data.query.pages[pageId].revisions[0];
                callback(rev.revid);
            });
        }

        function checkTalkPageAndPauseIfNeeded(callback) {
            var userTalkPage = 'User talk:' + mw.config.get('wgUserName');
            new mw.Api().get({
                action: 'query',
                prop: 'revisions',
                titles: userTalkPage,
                rvprop: 'ids|timestamp',
                rvlimit: 1
            }).done(function(data) {
                var pageId = Object.keys(data.query.pages)[0];
                if (pageId === '-1') return callback(false);
                var rev = data.query.pages[pageId].revisions[0];
                if (lastTalkRevId === null) {
                    lastTalkRevId = rev.revid;
                    callback(false);
                } else if (rev.revid !== lastTalkRevId) {
                    addLogEntry('alert', 'You have a new message on your talk page! Pausing delivery.', 'orange');
                    operatorPausedByNewMessage = true;
                    isPaused = true;
                    setPauseResumeState(true);
                    lastTalkRevId = rev.revid;
                    window.setTimeout(function() {
                        window.alert('There is a new message on your talk page. Please check it before continuing.');
                    }, 100);
                    callback(true);
                } else {
                    callback(false);
                }
            });
        }

        function removePageFromTextarea(page) {
            var lines = pagesTextarea.getValue().split("\n");
            var filtered = lines.filter(function(line) { return line.trim() !== page; });
            pagesTextarea.setValue(filtered.join("\n"));
            updatePagesCount();
        }

        // Expand/collapse logic
        linksContainer.on('click', '#show-delivered-list', function(e){
            e.preventDefault();
            if(deliveredBox.is(':visible')) {
                deliveredBox.slideUp();
            } else {
                deliveredBox.find('textarea').val(deliveredPages.join('\n'));
                deliveredBox.slideDown();
                skippedBox.slideUp();
            }
        });
        linksContainer.on('click', '#show-skipped-list', function(e){
            e.preventDefault();
            if(skippedBox.is(':visible')) {
                skippedBox.slideUp();
            } else {
                skippedBox.find('textarea').val(skippedPages.join('\n'));
                skippedBox.slideDown();
                deliveredBox.slideUp();
            }
        });

        function sendMessage() {
            var pages = pagesTextarea.getValue().split("\n").map(function(page){return page.trim();}).filter(function(page){return page;}),
                subject = subjectInputField.getValue().trim(),
                message = messageTextarea.getValue().trim(),
                summary = summaryInputField.getValue().trim() + " (using [[User:DreamRimmer/massMessageLite|massMessageLite]])",
                wikitext = '== ' + subject + ' ==\n' + message,
                skipDuplicate = skipDuplicateCheckbox.isSelected(),
                minorEdit = minorEditCheckbox.isSelected(),
                createIfMissing = createIfMissingCheckbox.isSelected(),
                userGroups = mw.config.get('wgUserGroups'),
                isSysop = userGroups.includes('sysop'),
                maxMessagesPerMinute = isSysop ? 25 : 15,
                sentMessages = 0,
                startTime = new Date().getTime();

            deliveredPages = [];
            skippedPages = [];
            linksContainer.hide();
            deliveredBox.hide();
            skippedBox.hide();

            if (pages.length === 0 || subject === "" || message === "" || summary === "") {
                alert("Error: Please fill in all fields");
                return;
            }

            logContainer.empty().append($('<h2>').text("Delivery logs:")).css('margin-top', '5px');
            logContainer.show();

            var currentIndex = 0, processedPages = 0;

            window.currentProcessNextPage = processNextPage;

            function processNextPage() {
                if (isPaused || operatorPausedByNewMessage) return;
                if (currentIndex >= pages.length) {
                    pauseResumeButton.setDisabled(true);
                    showLinksIfAny();
                    return;
                }
                pauseResumeButton.setDisabled(false);

                var page = pages[currentIndex];
                resolveRedirect(page).then(function(resolvedPage) {
                    if (resolvedPage !== page) {
                        addLogEntry('articleRedirect', page + ' is a redirect. Using ' + resolvedPage + '.', 'blue');
                    }
                    page = resolvedPage;
                    new mw.Api().get({
                        action: 'query',
                        prop: 'revisions',
                        titles: page,
                        rvprop: 'content',
                        rvslots: '*'
                    }).done(function(data) {
                        var pageId = Object.keys(data.query.pages)[0];
                        if (pageId === '-1') {
                            if (createIfMissing) {
                                new mw.Api().postWithToken('csrf', {
                                    action: 'edit',
                                    title: page,
                                    text: wikitext,
                                    summary: summary,
                                    minor: minorEdit ? true : undefined
                                }).done(function() {
                                    addLogEntry('message', 'Page created and messaged ' + page + '.', 'green');
                                    deliveredPages.push(page);
                                    removePageFromTextarea(pages[currentIndex]);
                                    sentMessages++;
                                    currentIndex++;
                                    processedPages++;
                                    afterPageProcessed();
                                }).fail(function() {
                                    addLogEntry('alert', 'Failed to create ' + page + '.', 'red');
                                    skippedPages.push(page);
                                    removePageFromTextarea(pages[currentIndex]);
                                    currentIndex++;
                                    processedPages++;
                                    afterPageProcessed();
                                });
                            } else {
                                addLogEntry('alert', 'Page ' + page + ' does not exist.', 'red');
                                skippedPages.push(page);
                                removePageFromTextarea(pages[currentIndex]);
                                currentIndex++;
                                processedPages++;
                                afterPageProcessed();
                            }
                            return;
                        }
                        var content = data.query.pages[pageId].revisions[0].slots.main['*'];
                        if (content.includes("{{User:DreamRimmer/NoMassMessage}}")) {
                            addLogEntry('alert', page + ' was skipped; (opted-out of message delivery)', 'red');
                            skippedPages.push(page);
                            removePageFromTextarea(pages[currentIndex]);
                            currentIndex++;
                            processedPages++;
                            afterPageProcessed();
                            return;
                        }
                        if (skipDuplicate && content.includes('== ' + subject + ' ==')) {
                            addLogEntry('alert', 'Message already exists on ' + page + '.', 'red');
                            skippedPages.push(page);
                            removePageFromTextarea(pages[currentIndex]);
                            currentIndex++;
                            processedPages++;
                            afterPageProcessed();
                            return;
                        }
                        var newContent = content + "\n\n" + wikitext;
                        new mw.Api().postWithToken('csrf', {
                            action: 'edit',
                            title: page,
                            text: newContent,
                            summary: summary,
                            minor: minorEdit ? true : undefined
                        }).done(function() {
                            addLogEntry('message', 'Message sent to ' + page + '.', 'green');
                            deliveredPages.push(page);
                            removePageFromTextarea(pages[currentIndex]);
                            sentMessages++;
                            currentIndex++;
                            processedPages++;
                            afterPageProcessed();
                        }).fail(function() {
                            addLogEntry('alert', 'Failed to send message to ' + page + '.', 'red');
                            skippedPages.push(page);
                            removePageFromTextarea(pages[currentIndex]);
                            currentIndex++;
                            processedPages++;
                            afterPageProcessed();
                        });
                    }).fail(function() {
                        addLogEntry('alert', 'Failed to retrieve content of ' + page + '.', 'red');
                        skippedPages.push(page);
                        removePageFromTextarea(pages[currentIndex]);
                        currentIndex++;
                        processedPages++;
                        afterPageProcessed();
                    });
                });
            }

            function afterPageProcessed() {
                if (processedPages % 5 === 0 && processedPages > 0) {
                    checkTalkPageAndPauseIfNeeded(function(paused){
                        if (!paused) checkRateLimitAndContinue();
                    });
                } else {
                    checkRateLimitAndContinue();
                }
            }

            function checkRateLimitAndContinue() {
                var currentTime = new Date().getTime(),
                    elapsedTime = (currentTime - startTime) / 1000,
                    maxMessages = maxMessagesPerMinute,
                    sleepTime = (60 / maxMessages) * sentMessages - elapsedTime;
                if (sleepTime > 0) {
                    addLogEntry('clock', 'Sleeping for ' + Math.ceil(sleepTime) + ' seconds to avoid flooding recent changes...', 'blue');
                    setTimeout(processNextPage, sleepTime * 1000);
                } else {
                    processNextPage();
                }
            }

            function showLinksIfAny() {
                if (deliveredPages.length > 0 || skippedPages.length > 0) {
                    linksContainer.show();
                    deliveredBox.hide();
                    skippedBox.hide();
                }
            }

            getLastTalkPageRevId(function(revId){
                lastTalkRevId = revId;
                processNextPage();
            });

            $('html, body').animate({
                scrollTop: logContainer.offset().top
            }, 500);
        }

        visualPreviewButton.on('click', previewMessage);
        startButton.on('click', function() {
            isPaused = false;
            operatorPausedByNewMessage = false;
            setPauseResumeState(false);
            pauseResumeButton.setDisabled(false);
            sendMessage();
        });
    }

    function checkUserAccess() {
        var username = mw.config.get('wgUserName');
        new mw.Api().get({
            action: 'query',
            titles: 'User:DreamRimmer/massmessage.json',
            prop: 'revisions',
            rvprop: 'content'
        }).done(function(data) {
            var pageId = Object.keys(data.query.pages)[0];
            if (pageId === '-1') {
                alert("Error: Cannot retrieve access control list");
                window.location.href = mw.config.get('wgServer');
                return;
            }

            var content = data.query.pages[pageId].revisions[0]['*'],
                accessControl = JSON.parse(content);

            if (accessControl.blockedUsers.includes(username)) {
                alert("You are blocked from using this script. Please contact User:DreamRimmer for more details.");
                window.location.href = mw.config.get('wgServer');
                return;
            }

            if (accessControl.allowedUsers.includes(username)) {
                initializeMassMessage();
            } else {
                var userGroups = mw.config.get('wgUserGroups');
                if (userGroups.includes('extendedconfirmed') || userGroups.includes('sysop')) {
                    initializeMassMessage();
                } else {
                    alert("You do not have permission to use this script. Please contact User:DreamRimmer.");
                    window.location.href = mw.config.get('wgServer');
                }
            }
        }).fail(function() {
            alert("Error: Cannot retrieve access control list. Please contact User:DreamRimmer");
            window.location.href = mw.config.get('wgServer');
        });
    }

    $.when(mw.loader.using('mediawiki.util'), $.ready).then(function() {
        mw.util.addPortletLink(
            'p-tb',
            mw.util.getUrl('Special:BlankPage/massMessageLite'),
            'massMessageLite'
        );
    });

    if (mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' && mw.config.get('wgTitle').split('/', 2)[1] === 'massMessageLite') {
        $.when(mw.loader.using('oojs-ui-core'), $.ready).then(function() {
            checkUserAccess();
        });
    }
});
//</nowiki>