mic_none

User:DreamRimmer/massMessageLite.js Source: en.wikipedia.org/wiki/User:DreamRimmer/massMessageLite.js

//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');

        $('#mw-content-text').empty()
            .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="https://en.wikipedia.orghttps://demo.azizisearch.com/lite/wikipedia/page/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="https://en.wikipedia.orghttps://demo.azizisearch.com/lite/wikipedia/page/Wikipedia:Blocking_policy" target="_blank">blocked from editing</a>.</span>'),
                $('<div>').css({'margin-bottom': '20px', 'font-size': '1.1em'}).html(
                    'Use the form below to send messages to a specified list. All fields are required. See <a href="https://en.wikipedia.orghttps://demo.azizisearch.com/lite/wikipedia/page/User:DreamRimmer/massMessageLite" target="_blank">User:DreamRimmer/massMessageLite</a> for more details.'
                )
            );

        var frameContainer = $('<div>')
            .addClass('mw-htmlform-ooui-wrapper oo-ui-layout oo-ui-panelLayout oo-ui-panelLayout-padded oo-ui-panelLayout-framed')
            .css({'max-width': '950px', 'margin': '0 auto 2em auto', 'padding': '1em 1.5em'});

        var deliveredPages = [], skippedPages = [], deliveredDiffs = {}, 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
            }),
            followRedirectsCheckbox = new OO.ui.CheckboxInputWidget({
                selected: true, // checked by default
                disabled: typeof window.massMessageLiteEnableRedirectCheckbox === "undefined" ? true : !window.massMessageLiteEnableRedirectCheckbox
            }),
            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': 'none',
                'display': 'table-row',
                'width': 'auto',
                'font-size': 'small',
                'transition': 'background 0.2s',
                'overflow-y': 'auto',
                'max-height': '260px',
                'min-height': '80px'
            }).addClass('mml-log-container').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();

        pagesTextarea.$element.attr('title', 'Enter one page per line (e.g., User talk:Example user).');
        subjectInputField.$element.attr('title', 'Required. Subject line for the message section.');
        messageTextarea.$element.attr('title', 'Required. The body of the message to send.');
        summaryInputField.$element.attr('title', 'Required. Edit summary for the message delivery.');

        skipDuplicateCheckbox.$element.attr('title', 'If checked, skips pages where a section with the same subject already exists.');
        minorEditCheckbox.$element.attr('title', 'If checked, marks all deliveries as minor edits.');
        createIfMissingCheckbox.$element.attr('title', 'If checked, creates the page if it does not exist.');

        var followRedirectsTooltip = followRedirectsCheckbox.isDisabled()
            ? "The script will leave messages on target page if a redirect page. Please check documentation to know how to enable this button."
            : "If checked, the script will leave messages on the redirect target (default). Uncheck to leave messages on the redirect page itself.";
        followRedirectsCheckbox.$element.attr('title', followRedirectsTooltip);

        visualPreviewButton.$element.attr('title', 'Preview how your message will look.');
        startButton.$element.attr('title', 'Send the message to all listed pages.');
        cancelButton.$element.attr('title', 'Cancel and leave this tool.');
        pauseResumeButton.$element.attr('title', 'Pause or resume delivery process.');

        $('<style>').prop('type', 'text/css').html(`
            .mml-log-entry {
                border-radius: 8px;
                margin-bottom: 5px;
                padding: 5px 7px 5px 3px;
                transition: background 0.18s, box-shadow 0.18s;
                background: #fff;
                opacity: 0;
                animation: mml-fadein 0.45s forwards;
            }
            .mml-log-entry:hover {
                background: #e7f1ff;
                box-shadow: 0 1px 7px rgba(120,160,255,0.07);
            }
            @keyframes mml-fadein {
                from { opacity: 0; transform: translateY(10px);}
                to { opacity: 1; transform: none;}
            }
            .mml-log-link {
                color: #2a4dad !important;
                text-decoration: none;
                font-weight: 600;
                cursor: pointer;
            }
            .mml-diff-link {
                color: #0b8200 !important;
                margin-left: 0.8em;
                font-size: 90%;
                vertical-align: middle;
                text-decoration: underline;
                font-weight: 500;
            }
            .mml-log-container {
                scrollbar-width: thin;
                scrollbar-color: #b8c6e5 #f8fafc;
            }
            .mml-log-container::-webkit-scrollbar {
                width: 8px;
                background: #f8fafc;
                opacity: 0;
                transition: opacity 0.3s;
            }
            .mml-log-container:hover::-webkit-scrollbar {
                opacity: 1;
            }
            .mml-log-container::-webkit-scrollbar-thumb {
                background: #b8c6e5;
                border-radius: 6px;
            }
        `).appendTo(document.head);

        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:700px;min-height:120px;max-height:400px;resize:vertical;font-family:monospace;font-size:13px;box-sizing:border-box;" title="Pages where the message was delivered."></textarea>')
        );
        skippedBox = $('<div>').css({'display':'none','margin':'6px 0'}).append(
            $('<textarea readonly style="width:700px;min-height:120px;max-height:400px;resize:vertical;font-family:monospace;font-size:13px;box-sizing:border-box;" title="Pages where the message could not be delivered."></textarea>')
        );
        linksContainer.append(
            $('<a href="#" style="margin-right:15px;font-weight:bold;" id="show-delivered-list" title="Show the list of pages where the message was delivered.">Show delivered pages</a>'),
            $('<a href="#" style="font-weight:bold;" id="show-skipped-list" title="Show the list of pages where the message could not be delivered.">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>')
            .attr('title', 'Shows how many pages you will deliver to.');
        frameContainer.append(
            $('<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(
                followRedirectsCheckbox.$element.css('margin-right', '5px'),
                'Follow redirects'
            ),
            $('<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
        );
        $('#mw-content-text').append(frameContainer);

        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 updatePreviewButtonState() {
            var subject = subjectInputField.getValue().trim();
            var message = messageTextarea.getValue().trim();
            var canPreview = !!subject && !!message;
            visualPreviewButton.setDisabled(!canPreview);
            if (!canPreview) {
                visualPreviewButton.$element.attr('title', 'Please fill required fields (subject and message) to preview.');
            } else {
                visualPreviewButton.$element.attr('title', 'Preview how your message will look.');
            }
        }
        subjectInputField.on('change', updatePreviewButtonState);
        messageTextarea.on('change', updatePreviewButtonState);
        updatePreviewButtonState();

        function makeWikiLink(title) {
            var url = mw.util.getUrl(title.replace(/ /g, '_'));
            return $('<a>')
                .addClass('mml-log-link')
                .attr({href: url, target: '_blank', rel: 'noopener'})
                .text(title);
        }

        function makeDiffLink(revid, label) {
            var url = mw.util.getUrl('Special:Diff/' + revid);
            return $('<a>')
                .addClass('mml-diff-link')
                .attr({href: url, target: '_blank', rel: 'noopener'})
                .text(label || '[diff]');
        }

        function addLogEntry(icon, message, color, pageTitle, revidForDiff, customHtml) {
            var logEntry = $('<li>').addClass('mml-log-entry').css({ 'color': color });
            logEntry.append(new OO.ui.IconWidget({ icon: icon, flags: ['progressive'] }).$element.css({ 'margin-right': '5px', 'vertical-align': 'middle' }));

            if (customHtml) {
                logEntry.append($('<span>').html(customHtml));
            } else if (pageTitle) {
                var pageLink = makeWikiLink(pageTitle)[0].outerHTML;
                var msgHtml = message.replace(
                    new RegExp(pageTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), 
                    pageLink
                );
                logEntry.append($('<span>').html(msgHtml));
                if (revidForDiff) {
                    logEntry.append(makeDiffLink(revidForDiff));
                }
            } else {
                logEntry.append($('<span>').text(message));
            }

            logContainer.append(logEntry).show();
            setTimeout(function() {
                logContainer.scrollTop(logContainer[0].scrollHeight);
            }, 5);

            if (typeof message === 'string' && message.match(/^Sleeping for \d+ seconds to avoid flooding recent changes/i)) {
                setTimeout(function() {
                    logEntry.slideUp(600, function() { $(this).remove(); });
                }, 5000);
            }
        }

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

            var previewBox = $('<div>').css({
                'padding': '20px',
                'margin': '25px 0',
                'overflow': 'auto',
                'width': pagesTextarea.$element.width(),
                'min-height': '350px',
                'max-height': '500px',
                'font-family': 'inherit',
                'font-size': '13px',
                'box-sizing': 'border-box',
                'border': '1px solid #ccc',
                'background': '#fff'
            });

            previewContainer.empty()
                .append($('<h2>').text("Message preview:"))
                .append(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) {
                if (followRedirectsCheckbox && !followRedirectsCheckbox.isSelected()) {
                    resolve(pageTitle);
                    return;
                }
                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 = [];
            deliveredDiffs = {};
            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 && followRedirectsCheckbox.isSelected()) {
                        addLogEntry(
                            'articleRedirect',
                            '',
                            'blue',
                            null,
                            null,
                            makeWikiLink(page)[0].outerHTML + ' is a redirect. Using ' + makeWikiLink(resolvedPage)[0].outerHTML + '.'
                        );
                    }
                    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(editdata) {
                                    var revid = editdata && editdata.edit && editdata.edit.newrevid ? editdata.edit.newrevid : null;
                                    addLogEntry('message', 'Page created and messaged ' + page + '.', 'green', page, revid);
                                    deliveredPages.push(page);
                                    if (revid) deliveredDiffs[page] = revid;
                                    removePageFromTextarea(pages[currentIndex]);
                                    sentMessages++;
                                    currentIndex++;
                                    processedPages++;
                                    afterPageProcessed();
                                }).fail(function() {
                                    addLogEntry('alert', 'Failed to create ' + page + '.', 'red', page);
                                    skippedPages.push(page);
                                    removePageFromTextarea(pages[currentIndex]);
                                    currentIndex++;
                                    processedPages++;
                                    afterPageProcessed();
                                });
                            } else {
                                addLogEntry('alert', 'Page ' + page + ' does not exist.', 'red', page);
                                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', page);
                            skippedPages.push(page);
                            removePageFromTextarea(pages[currentIndex]);
                            currentIndex++;
                            processedPages++;
                            afterPageProcessed();
                            return;
                        }
                        if (skipDuplicate && content.includes('== ' + subject + ' ==')) {
                            addLogEntry('alert', 'Message already exists on ' + page + '.', 'red', page);
                            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(editdata) {
                            var revid = editdata && editdata.edit && editdata.edit.newrevid ? editdata.edit.newrevid : null;
                            addLogEntry('message', 'Message sent to ' + page + '.', 'green', page, revid);
                            deliveredPages.push(page);
                            if (revid) deliveredDiffs[page] = revid;
                            removePageFromTextarea(pages[currentIndex]);
                            sentMessages++;
                            currentIndex++;
                            processedPages++;
                            afterPageProcessed();
                        }).fail(function() {
                            addLogEntry('alert', 'Failed to send message to ' + page + '.', 'red', page);
                            skippedPages.push(page);
                            removePageFromTextarea(pages[currentIndex]);
                            currentIndex++;
                            processedPages++;
                            afterPageProcessed();
                        });
                    }).fail(function() {
                        addLogEntry('alert', 'Failed to retrieve content of ' + page + '.', 'red', page);
                        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>