Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>
// @ts-check
// Extended info on IPs - gives a popup with their range, ASN, and ISP
// Parts cribbed from [[User:Krinkle/Scripts/CVNSimpleOverlay_wiki.js]] and [[MediaWiki:Gadget-markblocked.js]]

/* global mw, $  */

const ipExtIcon = 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Gnome-fs-web.svg/20px-Gnome-fs-web.svg.png'

/**
 * 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 ipExtGetIPs ($content) {
  const userLinks = new Map()

  // Get all aliases for user: (taken from markblocked)
  const userNS = []
  for (const ns in mw.config.get('wgNamespaceIds')) {
    if (mw.config.get('wgNamespaceIds')[ns] === 2) {
      userNS.push(mw.util.escapeRegExp(ns.replace(/_/g, ' '))   ':')
    }
  }

  // RegExp for all titles that are User:
  const userTitleRX = new RegExp('^('   userNS.join('|')   'Special:Block/|Special:Contribs/|Special:Contributions/) ([^\\/#] )$', 'i')
  const articleRX = new RegExp(mw.config.get('wgArticlePath').replace('$1', '')   '([^#] )')
  $('a', $content).each(function () {
    if (!$(this).attr('href')) {
      // Ignore if the <a> doesn't have a href
      return
    }
    const articleTitleReMatch = articleRX.exec($(this).attr('href').toString())
    if (!articleTitleReMatch) {
      return
    }
    let pgTitle
    try {
    	pgTitle = decodeURIComponent(articleTitleReMatch[1]).replace(/_/g, ' ')
    } catch (error) {
    	return
    }
    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
}

/**
 * Get the WHOIS summary for an IP
 *
 * @param {string} ip IP address to check
 *
 * @return {Promise<string>} Summary of interesting parts of the IP's WHOIS
 */
async function ipExtWHOISInline (ip) {
  const whoisResult = await fetch(`https://whois.toolforge.org/w/${ip}/lookup/json`)
  const whoisJson = await whoisResult.json() // Why is json() async?
  let providers = ''
  whoisJson.nets.forEach((net) => {
    providers  = net.name   ' '
  })
  return `${ip}:\n  ASN: ${whoisJson.asn}\n  ASN range: ${whoisJson.asn_cidr}\n  ASN country: ${whoisJson.asn_country_code}\n  ISP: ${providers}`
}

async function annotate (ipsOnPage) {
  // Swiped from [[User:Suffusion_of_Yellow/batchtest-plus.js]]
  const pending = []
  const MAX_CONCURRENT_REQUESTS = 5
  for (const [ip, val] of ipsOnPage) {
    const idx = pending.length < MAX_CONCURRENT_REQUESTS ? pending.length : await Promise.race(pending)
    if (idx === undefined) {
      break // Something went wrong with the last request
    }
    pending[idx] = ipExtWHOISInline(ip).then((whoisText) => {
      val.forEach(($link) => {
        $link.after($('<a>').attr('href', `https://bullseye.toolforge.org/ip/${ip}`).attr('target', 'blank').attr('rel', 'noopener noreferrer')
          .append($('<img>').attr('src', ipExtIcon).attr('title', mw.html.escape(whoisText))))
      })
      return idx
    })
  }
  await Promise.all(pending)
}

// On window load, get all the IPs on the page and WHOIS them asynchronously
$.when($.ready, mw.loader.using(['mediawiki.util'])).then(function () {
  mw.hook('wikipage.content').add(function ($content) {
    const ipsOnPage = ipExtGetIPs($content)
    annotate(ipsOnPage)
  })
})