User:DreamRimmer/massMessageLite.js
Appearance
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 user script seems to have a documentation page at User:DreamRimmer/massMessageLite. |
//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 rawPages = pagesTextarea.getValue().split("\n").map(function(page){return page.trim();}).filter(function(page){return page;}),
uniquePages = [],
seenPages = {},
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 (rawPages.length === 0 || subject === "" || message === "" || summary === "") {
alert("Error: Please fill in all fields");
return;
}
rawPages.forEach(function(page, idx){
if (seenPages[page]) {
addLogEntry('alert', page + ' is a duplicate of a page already processed (' + page + '). Skipping...', 'orange');
} else {
uniquePages.push(page);
seenPages[page] = true;
}
});
var pages = uniquePages;
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>