User:Saftzie/app.nuke.js
ShoutWiki — express yourself and be heard!
Jump to navigation
Jump to search
Note: After saving, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/**
* Block a list of users
* Delete their titles
*
* Version 1.0: 25 Aug 2016
* Original version for ShoutWiki
* Version 1.1: 25 Sep 2016
* Use query allmessages
* Version 1.2: 21 Nov 2016
* Control block vs look-up only
* Version 1.2.1: 1 Mar 2023
* Update obsolete token
*/
(function (mw, $) {
'use strict';
var
PREFIX = 'js-nuke',
urlAPI = mw.config.get('wgScriptPath') + '/api.php',
statusBlock, statusDelete;
// append text to textarea e
// autoscroll by setting selection
// less screen jump by setting scrollTop
function status(e, text) {
var
t;
e.value += text + '\n';
t = e.scrollHeight - e.clientHeight;
e.scrollTop = (t < 0) ? 0 : t;
e.selectionStart = e.selectionEnd = e.value.length;
}
function populateTitles(user) {
var
title = $('#' + PREFIX + '-delete-pages')[0],
count, pages;
// get the parent IDs of selected revisions
// output revision info if there is no parent
function getParents() {
var
MAXREQ = 50;
++count;
status(statusBlock, 'Querying parent revisions for "' + user + '"... ' + count);
$.post(urlAPI, {
format: 'json',
action: 'query',
prop: 'revisions',
rvprop: 'ids',
revids: pages.slice(0, MAXREQ).join('|')
}, function (data) {
var
a = [],
t, i;
if (data.query) {
t = data.query.pages;
// make it an array to sort it
// because the query unsorts it
for (i in t) {
if (t.hasOwnProperty(i)) {
a.push(t[i]);
}
}
a.sort(function (x, y) {
return (x.revisions[0].revid < y.revisions[0].revid) ?
1 : -1;
});
for ( i = 0; i < a.length; ++i ) {
// new pages are pages without a parent
t = a[i].revisions[0];
if (t.parentid === 0) {
title.value += a[i].title + '\n';
}
}
if (pages.length) {
// there's more data to get
getParents();
return;
}
status(statusBlock, 'Query pages complete for "' + user + '"');
} else if (data.error) {
status(statusBlock, 'Error for "' + user + '" parent contributions:: ' +
data.error.code + ': ' + data.error.info);
}
});
pages = pages.slice(MAXREQ);
}
// get all the user contributions
// keep only the earliest contribution for each page
function getPages() {
var
MAXREQ = 500,
o = {
format: 'json',
action: 'query',
list: 'usercontribs',
uclimit: MAXREQ,
ucprop: 'ids',
ucshow: 'new', // mw 1.23+
ucuser: user
};
count = 1;
pages = {};
(function loop() {
status(statusBlock, 'Querying page contributions for "' + user + '"... ' + count);
$.post(urlAPI, o).done(function (data) {
var
a, i;
if (data.query) {
a = data.query.usercontribs;
for ( i = 0 ; i < a.length ; ++i ) {
// keep only the earliest contribution for each page
if ((pages[a[i].pageid] === undefined) ||
(a[i].revid < pages[a[i].pageid])) {
pages[a[i].pageid] = a[i].revid;
}
}
// find the continuation data, if it exists
// continue is a reserved word, so quote it
a = data['continue'] ||
((a = data['query-continue']) && a.usercontribs);
if (a) {
// there's more data to get
$.extend(o, a);
++count;
loop();
return;
}
// make an array of rev IDs for sorting
a = [];
for (i in pages) {
if (pages.hasOwnProperty(i)) {
a.push(pages[i]);
}
}
if (a.length === 0) {
status(statusBlock, 'No contributions found for "' + user + '"');
return;
}
a.sort(function (x, y) {
// descending rev IDs
return (x < y) ? 1 : -1;
});
pages = a;
count = 0;
getParents();
} else if (data.error) {
status(statusBlock, 'Error for "' + user + '" contributions:: ' +
data.error.code + ': ' + data.error.info);
}
});
}());
}
getPages();
}
// handle block "go" click
// get ready to block
function onBlock(button) {
var
expiry = $('#' + PREFIX + '-block-expiry-select').val(),
select = $('#' + PREFIX + '-block-reason-select').val(),
reason = $('#' + PREFIX + '-block-reason-other').val()
.replace(/^\s+/, '').replace(/\s+$/, ''),
user = $('#' + PREFIX + '-block-users').val().split('\n'),
i = 0;
// loop and block as long as there are users
function execBlock() {
var
u = user.shift();
if ($('#' + PREFIX + '-auto-titles').prop('checked')) {
populateTitles(u);
}
if ($('#' + PREFIX + '-block-cb').prop('checked')) {
// action=block requires a POST to "conceal" the token
$.post(urlAPI, {
format: 'json',
action: 'block',
user: u,
expiry: expiry,
reason: reason,
allowusertalk: 1, // default is to block talk, so allow
// anononly: 1, // block only anon users for IP blocks
autoblock: 1, // default is not to block IPs, so block
nocreate: 1, // default is allow acct create, so block
reblock: 1, // overwrite existing block, if any (seems broken sometimes)
token: mw.user.tokens.get('csrfToken')
}, function (data) {
if (data.block) {
data = data.block;
status(statusBlock, 'Blocked: "' + data.user + '" (' + data.userID + ') until "' + data.expiry + '" for "' + data.reason + '" (' + data.id + ')');
} else {
status(statusBlock, 'Error: "' + u + '"; ' +
data.error.code + ': ' + data.error.info);
}
if (user.length) {
execBlock(); // keep going
} else {
button.disabled = false; // re-enable Go
}
});
} else {
button.disabled = false; // re-enable Go
}
}
// parse textbox into a list of users
while (i < user.length) {
user[i] = user[i]
.replace(/^\s+/, '')
.replace(/\s+$/, '')
.replace(/_/g, ' ');
if (user[i]) {
++i;
} else {
user.splice(i, 1);
}
}
$('#' + PREFIX + '-block-users').val(user.join('\n'));
if (user.length) {
if (expiry === '0') {
expiry = $('#' + PREFIX + '-block-expiry-other').val()
.replace(/^\s+/, '').replace(/\s+$/, '');
}
if (select !== '0') {
if (reason) {
reason = select + ': ' + reason;
} else {
reason = select;
}
}
execBlock();
} else {
status(statusBlock, 'No one to block');
button.disabled = false; // re-enable Go
}
}
// handle delete "go" click
// get ready to delete
function onDelete(button) {
var
select = $('#' + PREFIX + '-delete-select').val(),
reason = $('#' + PREFIX + '-delete-other').val()
.replace(/^\s+/, '').replace(/\s+$/, ''),
title = $('#' + PREFIX + '-delete-pages').val().split('\n'),
i = 0;
// loop and delete as long as there are titles
function execDelete() {
var
t = title.shift();
// action=delete requires a POST to "conceal" the token
$.post(urlAPI, {
format: 'json',
action: 'delete',
title: t,
reason: reason,
token: mw.user.tokens.get('csrfToken')
}, function (data) {
if (data['delete']) {
data = data['delete'];
status(statusDelete, 'Deleted: "' + data.title + '" for "' + data.reason + '" (' + data.logid + ')');
} else {
status(statusDelete, 'Error: ' + t + '; ' +
data.error.code + ': ' + data.error.info);
}
if (title.length) {
execDelete(); // keep going
} else {
button.disabled = false; // re-enable Go
}
});
}
// parse textbox into a list of titles
while (i < title.length) {
title[i] = title[i]
.replace(/^\s+/, '')
.replace(/\s+$/, '')
.replace(/_/g, ' ');
if (title[i]) {
++i;
} else {
title.splice(i, 1);
}
}
$('#' + PREFIX + '-delete-pages').val(title.join('\n'));
if (title.length) {
if (select !== '0') {
if (reason) {
reason = select + ': ' + reason;
} else {
reason = select;
}
}
execDelete();
} else {
status(statusDelete, 'Nothing to delete');
button.disabled = false; // re-enable Go
}
}
// parse ipboptions
function makeBlockExpiry(data) {
var
line = data.split(','),
s = '',
i;
for ( i = 0 ; i < line.length ; ++i ) {
s += '<option value="' + line[i].split(':')[1] + '">' + line[i].split(':')[0] + '</option>';
}
$('#' + PREFIX + '-block-expiry-select').append(s);
}
// parse ipbreason-dropdown
function makeBlockReasonSelect(data) {
var
line = data.split('\n'),
s = '',
level, i;
for ( i = 0 ; i < line.length ; ++i ) {
level = line[i].search(/[^\*]/);
line[i] = line[i]
.replace(/^\*+/, '')
.replace(/^\s+/, '')
.replace(/\s+$/, '');
if (level === 1) {
if (s.length) {
s += '</optgroup>';
}
s += '<optgroup label="' + line[i] + '">';
} else { // level === 2
s += '<option value="' + line[i] + '">' + line[i] + '</option>';
}
}
if (s.length) {
s += '</optgroup>';
}
$('#' + PREFIX + '-block-reason-select').append(s);
}
// parse deletereason-dropdown
function makeDeleteSelect(data) {
var
line = data.split('\n'),
s = '',
level, i;
for ( i = 0 ; i < line.length ; ++i ) {
level = line[i].search(/[^\*]/);
line[i] = line[i]
.replace(/^\*+/, '')
.replace(/^\s+/, '')
.replace(/\s+$/, '');
if (level === 1) {
if (s.length) {
s += '</optgroup>';
}
s += '<optgroup label="' + line[i] + '">';
} else { // level === 2
s += '<option value="' + line[i] + '">' + line[i] + '</option>';
}
}
if (s.length) {
s += '</optgroup>';
}
$('#' + PREFIX + '-delete-select').append(s);
}
// check for the signature ID & exit if not found
// otherwise, crank it up
$(function () {
var
s;
if ($('#' + PREFIX).length === 0) {
return; // nothing to do
}
$('#' + PREFIX).empty();
// get system messages
$.post(urlAPI, {
format: 'json',
action: 'query',
meta: 'allmessages',
ammessages: 'ipboptions|ipbreason-dropdown|deletereason-dropdown'
}, function (data) {
var
i, d;
// asked for 3 messages, so should get 3 messages
if ((data = data.query) && (data = data.allmessages) &&
$.isArray(data) && (data.length === 3)) {
for ( i = 0; i < data.length; ++i ) {
d = data[i];
switch (d.normalizedname) {
case 'ipboptions':
if (d['*']) {
makeBlockExpiry(d['*']);
} else {
status(statusBlock, 'ipboptions not found');
}
break;
case 'ipbreason-dropdown':
if (d['*']) {
makeBlockReasonSelect(d['*']);
} else {
status(statusBlock, 'ipbreason-dropdown not found');
}
break;
case 'deletereason-dropdown':
if (d['*']) {
makeDeleteSelect(d['*']);
} else {
status(statusDelete, 'deletereason-dropdown not found');
}
}
}
} else {
status(statusBlock, 'message error');
status(statusDelete, 'message error');
}
});
s = $(String.prototype.concat(
'<fieldset>',
'<legend>Block and Delete</legend>',
'<div style="display: table;">',
'<fieldset>',
'<legend>Block</legend>',
'<table><tbody>',
'<tr>',
'<td><label for="' + PREFIX + '-block-expiry-select">Expiry: </label></td>',
'<td><select id="' + PREFIX + '-block-expiry-select">',
'<option value="0">Other period</option>',
'</select></td>',
'</tr>',
'<tr>',
'<td><label for="' + PREFIX + '-block-expiry-other">Other expiry: </label></td>',
'<td><input type="text" id="' + PREFIX + '-block-expiry-other" maxlength="255" size="60"/></td>',
'</tr>',
'<tr>',
'<td><label for="' + PREFIX + '-block-reason-select">Reason: </label></td>',
'<td><select id="' + PREFIX + '-block-reason-select">',
'<option value="0">Other reason</option>',
'</select></td>',
'</tr>',
'<tr>',
'<td><label for="' + PREFIX + '-block-reason-other">Other/Additional reason: </label></td>',
'<td><input type="text" id="' + PREFIX + '-block-reason-other" maxlength="255" size="60"/></td>',
'</tr>',
'<tr>',
'<td></td><td>',
'<input type="checkbox" id="' + PREFIX + '-block-cb" checked="checked" style="vertical-align: middle;"/>',
'<label for="' + PREFIX + '-block-cb" style="vertical-align: middle;"> Block users</label>',
'<input type="checkbox" id="' + PREFIX + '-auto-titles" checked="checked" style="margin-left: 1em; vertical-align: middle;"/>',
'<label for="' + PREFIX + '-auto-titles" style="vertical-align: middle;"> Auto-populate article titles</label>',
'</td>',
'</tr>',
'</tbody></table>',
'<div style="text-align: right;">',
'<input type="button" id="' + PREFIX + '-block-go" value="Go"/>',
'</div>',
'<div>',
'<span>Users to block:</span>',
'<textarea id="' + PREFIX + '-block-users" rows="8" style="resize: none;"></textarea>',
'</div>',
'<div>',
'<span>Block status:</span>',
'<textarea id="' + PREFIX + '-block-status" rows="8" style="resize: none;" readonly="readonly"></textarea>',
'</div>',
'</fieldset>',
'<fieldset>',
'<legend>Delete</legend>',
'<table><tbody>',
'<tr>',
'<td><label for="' + PREFIX + '-delete-select">Reason: </label></td>',
'<td><select id="' + PREFIX + '-delete-select">',
'<option value="0">Other reason</option>',
'</select></td>',
'</tr>',
'<tr>',
'<td><label for="' + PREFIX + '-delete-other">Other/Additional reason: </label></td>',
'<td><input type="text" id="' + PREFIX + '-delete-other" maxlength="255" size="60"/></td>',
'</tr>',
'</tbody></table>',
'<div style="text-align: right;">',
'<input type="button" id="' + PREFIX + '-delete-go" value="Go"/>',
'</div>',
'<div>',
'<span>Articles to delete:</span>',
'<textarea id="' + PREFIX + '-delete-pages" rows="8" style="resize: none;"></textarea>',
'</div>',
'<div>',
'<span>Delete status:</span>',
'<textarea id="' + PREFIX + '-delete-status" rows="8" style="resize: none;" readonly="readonly"></textarea>',
'</div>',
'</fieldset>',
'</div>',
'<//fieldset>'
));
statusBlock = $('#' + PREFIX + '-block-status', s)[0];
statusDelete = $('#' + PREFIX + '-delete-status', s)[0];
$('#' + PREFIX + '-block-go', s).click(function () {
this.disabled = true; // no double-clicking
onBlock(this);
});
$('#' + PREFIX + '-delete-go', s).click(function () {
this.disabled = true; // no double-clicking
onDelete(this);
});
$('#' + PREFIX).empty().append(s);
});
}(mediaWiki, jQuery));