MediaWiki:Gadget-MarkBLocked.js
if (ignoredClasses.test(a.className) ||
(pr = a.parentElement) && ignoredClassesPr.test(pr.className) ||
// cur/prev revision links
pr && (pr2 = pr.parentElement) && pr2.classList.contains('mw-history-histlinks') && pr2.classList.contains('mw-changeslist-links')
) {
return;
}
var href = a.href;
if (!href) return;
if (href[0] === '#') return;
var m, pagetitle;
if ((m = regex.article.exec(href))) {
pagetitle = m[1];
} else if ((m = regex.script.exec(href))) {
pagetitle = m[1];
} else {
return;
}
pagetitle = decodeURIComponent(pagetitle).replace(/ /g, '_');
// Extract a username from the page title
if (!(m = regex.user.exec(pagetitle))) return;
var username = m[1].replace(/_/g, ' ');
if (mw.util.isIPAddress(username, true)) {
username = username.toUpperCase(); // IPv6 addresses are case-insensitive
if (ips.indexOf(username) === -1) ips.push(username);
} else {
// Ensure the username doesn't contain characters that can't be used for usernames (do this here or block status query might fail)
if (/[/@#<>[\]|{}:]|^(\d{1,3}\.){3}\d{1,3}$/.test(username)) {
return;
} else {
username = username.slice(0, 1).toUpperCase() + username.slice(1); // Capitalize 1st letter: required for links like Special:Contribs/user
if (users.indexOf(username) === -1) users.push(username);
}
}
// Add a class to this anchor and save the anchor into an array
a.classList.add('gmbl-userlink');
if (!MarkBLocked.userLinks[username]) {
MarkBLocked.userLinks[username] = [a];
} else {
MarkBLocked.userLinks[username].push(a);
}
});
if ($.isEmptyObject(MarkBLocked.userLinks)) return;
// Check (b)lock status and do markup if needed
var allUsers = users.concat(ips);
MarkBLocked.markBlockedUsers(allUsers);
if (MarkBLocked.prefs.localips) MarkBLocked.markIpsInBlockedRanges(ips);
if (MarkBLocked.prefs.globalusers) MarkBLocked.markLockedUsers(users);
if (MarkBLocked.prefs.globalips) MarkBLocked.markGloballyBlockedIps(ips);
},
/**
* Add a class to all anchors associated with a certain username
* @param {string} userName
* @param {string} className
*/
addClass: function(userName, className) {
var links = MarkBLocked.userLinks[userName]; // Get all links related to the user
for (var i = 0; links && i < links.length; i++) {
links[i].classList.add(className);
}
},
/**
* Mark up locally blocked registered users and single IPs (this can't detect single IPs included in blocked IP ranges)
* @param {Array} usersArr
*/
markBlockedUsers: function(usersArr) {
usersArr = usersArr.slice(); // Deep copy just in case; this array will be spliced (not quite needed actually)
var bklimit = MarkBLocked.hasApiHighlimits ? 500 : 50; // Better performance for users with 'apihighlimits'
/**
* @param {Array} arr
*/
var query = function(arr) {
api.post({ // This MUST be a POST request because the parameters can exceed the word count limit of URI
action: 'query',
list: 'blocks',
bklimit: bklimit,
bkusers: arr.join('|'),
bkprop: 'user|expiry|restrictions',
formatversion: '2'
}).then(function(res){
var resBlk;
if (!res || !res.query || !(resBlk = res.query.blocks) || !resBlk.length) return;
resBlk.forEach(function(obj) {
var partialBlk = obj.restrictions && !Array.isArray(obj.restrictions); // Boolean: True if partial block
var clss;
if (/^in/.test(obj.expiry)) {
clss = partialBlk ? 'gmbl-blocked-partial' : 'gmbl-blocked-indef';
} else {
clss = partialBlk ? 'gmbl-blocked-partial' : 'gmbl-blocked-temp';
}
MarkBLocked.addClass(obj.user, clss);
});
}).catch(function(code, err) {
mw.log.error(err);
});
};
// API calls
while (usersArr.length) {
query(usersArr.splice(0, bklimit));
}
},
/**
* Mark up all locally blocked IPs including single IPs in blocked IP ranges
* @param {Array} ipsArr
*/
markIpsInBlockedRanges: function(ipsArr) {
/**
* @param {string} ip
*/
var query = function(ip) {
api.get({
action: 'query',
list: 'blocks',
bklimit: '1', // Only one IP can be checked in one API call, which means it's neccesary to send as many API requests as the
bkip: ip,     // length of the array. You can see why we need the personal preferences: This can lead to performance issues.
bkprop: 'user|expiry|restrictions',
formatversion: '2'
}).then(function(res){
var resBlk;
if (!res || !res.query || !(resBlk = res.query.blocks) || !resBlk.length) return;
resBlk = resBlk[0];
var partialBlk = resBlk.restrictions && !Array.isArray(resBlk.restrictions);
var clss;
if (/^in/.test(resBlk.expiry)) {
clss = partialBlk ? 'gmbl-blocked-partial' : 'gmbl-blocked-indef';
} else {
clss = partialBlk ? 'gmbl-blocked-partial' : 'gmbl-blocked-temp';
}
MarkBLocked.addClass(ip, clss);
}).catch(function(code, err) {
mw.log.error(err);
});
};
// API calls
ipsArr.forEach(query);
},
/**
* Mark up globally locked users
* @param {Array} regUsersArr
*/
markLockedUsers: function(regUsersArr) {
/**
* @param {string} regUser
*/
var query = function(regUser) {
api.get({
action: 'query',
list: 'globalallusers',
agulimit: '1',
agufrom: regUser,
aguto: regUser,
aguprop: 'lockinfo',
formatversion: '2'
}).then(function(res) {
var resLck;
if (!res || !res.query || !(resLck = res.query.globalallusers) || !resLck.length) return;
var locked = resLck[0].locked === ;
if (locked) MarkBLocked.addClass(regUser, 'gmbl-globally-locked');
}).catch(function(code, err) {
mw.log.error(err);
});
};
// API calls
regUsersArr.forEach(query);
},
/**
* Mark up (all) globally blocked IPs
* @param {Array} ipsArr
*/
markGloballyBlockedIps: function(ipsArr) {
/**
* @param {string} ip
*/
var query = function(ip) {
api.get({
action: 'query',
list: 'globalblocks',
bgip: ip,
bglimit: '1',
bgprop: 'address|expiry',
formatversion: '2'
}).then(function(res){
var resBlk;
if (!res || !res.query || !(resBlk = res.query.globalblocks) || !resBlk.length) return;
resBlk = resBlk[0];
var clss = /^in/.test(resBlk.expiry) ? 'gmbl-globally-blocked-indef' : 'gmbl-globally-blocked-temp';
MarkBLocked.addClass(ip, clss);
}).catch(function(code, err) {
mw.log.error(err);
});
};
// API calls
ipsArr.forEach(query);
}
};
$.when(mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.user']), $.ready).then(MarkBLocked.init);
// *******************************************************************************************************************
// @ts-ignore "Cannot find name 'mediaWiki'."
})(mediaWiki, jQuery);
//