// <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>
🔥 Top keywords: Main PageSpecial:SearchPage 3Wikipedia:Featured picturesHouse of the DragonUEFA Euro 2024Bryson DeChambeauJuneteenthInside Out 2Eid al-AdhaCleopatraDeaths in 2024Merrily We Roll Along (musical)Jonathan GroffJude Bellingham.xxx77th Tony AwardsBridgertonGary PlauchéKylian MbappéDaniel RadcliffeUEFA European Championship2024 ICC Men's T20 World CupUnit 731The Boys (TV series)Rory McIlroyN'Golo KantéUEFA Euro 2020YouTubeRomelu LukakuOpinion polling for the 2024 United Kingdom general electionThe Boys season 4Romania national football teamNicola CoughlanStereophonic (play)Gene WilderErin DarkeAntoine GriezmannProject 2025