User:GeneralNotability/mark-locked.js

// <nowiki>// @ts-check// Companion to markblocked - asynchronously marks locked users// Chunks borrowed from [[User:Krinkle/Scripts/CVNSimpleOverlay_wiki.js]],// [[User:GeneralNotability/ip-ext-info.js]], and [[MediaWiki:Gadget-markblocked.js]]/** * Get all userlinks on the page * * @param {JQuery} $content page contents * @return {Map} list of unique users on the page and their corresponding links */ function lockedUsers_getUsers($content) {const userLinks = new Map();// Get all aliases for user: & user_talk: (taken from markblocked)const userNS = [];for (const ns in mw.config.get( 'wgNamespaceIds' ) ) {if (mw.config.get('wgNamespaceIds')[ns] === 2 || mw.config.get('wgNamespaceIds')[ns] === 3) {userNS.push(mw.util.escapeRegExp(ns.replace(/_/g, ' ')) + ':');}}// RegExp for all titles that are  User:| User_talk: | Special:Contributions/ (for userscripts)const userTitleRX = new RegExp('^(' + userNS.join('|') + '|Special:Contrib(?:ution)?s\\/|Special:CentralAuth\\/)+([^\\/#]+)$', 'i');const articleRX = new RegExp(mw.config.get('wgArticlePath').replace('$1', '') + '([^#]+)');const redlinkRX = new RegExp(mw.config.get('wgScript') + '\\?title=([^#&]+)');$('a', $content).each(function () {if (!$(this).attr('href')) {// Ignore if the <a> doesn't have a hrefreturn;}let articleTitleReMatch = articleRX.exec($(this).attr('href').toString());if (!articleTitleReMatch) {// Try the redlink checkarticleTitleReMatch = redlinkRX.exec($(this).attr('href').toString());if (!articleTitleReMatch) {return;}}let pgTitle;try {pgTitle = decodeURIComponent(articleTitleReMatch[1]).replace(/_/g, ' ');} catch (error) {// Happens sometimes on non-username paths, like if there's a slash in the pathreturn;}const userTitleReMatch = userTitleRX.exec(pgTitle);if (!userTitleReMatch) {return;}const username = userTitleReMatch[2];if (!mw.util.isIPAddress(username, true)) {if (!userLinks.get(username)) {userLinks.set(username, []);}userLinks.get(username).push($(this));}});return userLinks;}/** * Check whether a user is locked and if they are, return details about it * * @param {string} user Username to check * * @return {Promise<object>} Whether the user in question is locked and a formatted tooltip about the lock */async function lockedUsers_getLock(user) {let locked = false;let tooltip = '';    // Ensure consistent case conversions with PHP as per https://phabricator.wikimedia.org/T292824user = new mw.Title(user).getMain();const api = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php');// Pre-check whether they're locked at all - if no, return earlytry {const response = await api.get({action: 'query',list: 'globalallusers',agulimit: '1',agufrom: user,aguto: user,aguprop: 'lockinfo'});if (response.query.globalallusers.length === 0) {// If the length is 0, then we couldn't find the global userreturn { locked, tooltip };}// If the 'locked' field is present, then the user is lockedif (!('locked' in response.query.globalallusers[0])) {return { locked, tooltip };}} catch (error) {return { locked, tooltip };}try {const response = await api.get({action: 'query',list: 'logevents',leprop: 'user|timestamp|comment|details',leaction: 'globalauth/setstatus',letitle: `User:${user}@global`});// If the length is 0, then we couldn't find the log eventfor (let logEvent of response.query.logevents) {let isLockEvent = false;// only works for more recent log entries, but most accuratetry {isLockEvent = logEvent.params.added.includes('locked');} catch (error) {}// Older style log entriesif (!isLockEvent) {try {isLockEvent = logEvent.params[0] == 'locked';} catch (error) {}}if (isLockEvent) {const timestamp = new Date(logEvent.timestamp);const prettyTimestamp = lockedUsers_formatTimeSince(timestamp);tooltip = `Locked by ${logEvent.user}: ${logEvent.comment} (${prettyTimestamp} ago)`;locked = true;// Intentionally not breaking - cycle through to find the most recent lock in case there are multiple}}} catch (error) {}return { locked, tooltip };}/** * Formats time since a date. Taken from mark-blocked.js * * @param {targetDate} Date to check the time since for * * @return {string} A prettified string regarding time since the lock occured */function lockedUsers_formatTimeSince(targetDate) {const lockedUsers_padNumber = (number) => number <= 9 ? '0' + number : number;const msSince = new Date() - targetDate;let minutes = Math.floor(msSince / 60000);if (!minutes) {return Math.floor(msSince / 1000) + 's';}let hours = Math.floor(minutes / 60);minutes %= 60;let days = Math.floor(hours / 24);hours %= 24;if (days) {return `${days}${(days < 10 ? '.' + lockedUsers_padNumber(hours) : '' )}d`;}return `${hours}:${lockedUsers_padNumber(minutes)}`;}// On window load, get all the users on the page and check if they're blocked$.when( $.ready, mw.loader.using( 'mediawiki.util' ) ).then( function () {mw.hook('wikipage.content').add(function ($content) {const usersOnPage = lockedUsers_getUsers($content);usersOnPage.forEach(async (val, key, _) => {const { locked, tooltip } = await lockedUsers_getLock(key);if (locked) {val.forEach(($link) => {$link.css({ opacity: 0.4, 'border-bottom-size': 'thick', 'border-bottom-style': 'dashed', 'border-bottom-color': 'red' });$link.attr('title', tooltip);});}});});});// </nowiki>