<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://moasspedia.org/w/index.php?action=history&amp;feed=atom&amp;title=Module%3AIPblock</id>
	<title>Module:IPblock - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://moasspedia.org/w/index.php?action=history&amp;feed=atom&amp;title=Module%3AIPblock"/>
	<link rel="alternate" type="text/html" href="https://moasspedia.org/w/index.php?title=Module:IPblock&amp;action=history"/>
	<updated>2026-04-17T08:00:12Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.37.2</generator>
	<entry>
		<id>https://moasspedia.org/w/index.php?title=Module:IPblock&amp;diff=1464&amp;oldid=prev</id>
		<title>Wikipedia&gt;Johnuniq: fix so inputs &quot;User:1.2.3.4&quot; and &quot;User talk:1.2.3.4&quot; work</title>
		<link rel="alternate" type="text/html" href="https://moasspedia.org/w/index.php?title=Module:IPblock&amp;diff=1464&amp;oldid=prev"/>
		<updated>2021-06-15T07:52:48Z</updated>

		<summary type="html">&lt;p&gt;fix so inputs &amp;quot;User:1.2.3.4&amp;quot; and &amp;quot;User talk:1.2.3.4&amp;quot; work&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- Calculate the minimum-sized blocks of IP addresses that cover each&lt;br /&gt;
-- IPv4 or IPv6 address entered in the arguments.&lt;br /&gt;
&lt;br /&gt;
local bit32 = require(&amp;#039;bit32&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
local Collection  -- a table to hold items&lt;br /&gt;
Collection = {&lt;br /&gt;
	add = function (self, item)&lt;br /&gt;
		if item ~= nil then&lt;br /&gt;
			self.n = self.n + 1&lt;br /&gt;
			self[self.n] = item&lt;br /&gt;
		end&lt;br /&gt;
	end,&lt;br /&gt;
	join = function (self, sep)&lt;br /&gt;
		return table.concat(self, sep)&lt;br /&gt;
	end,&lt;br /&gt;
	remove = function (self, pos)&lt;br /&gt;
		if self.n &amp;gt; 0 and (pos == nil or (0 &amp;lt; pos and pos &amp;lt;= self.n)) then&lt;br /&gt;
			self.n = self.n - 1&lt;br /&gt;
			return table.remove(self, pos)&lt;br /&gt;
		end&lt;br /&gt;
	end,&lt;br /&gt;
	sort = function (self, comp)&lt;br /&gt;
		table.sort(self, comp)&lt;br /&gt;
	end,&lt;br /&gt;
	new = function ()&lt;br /&gt;
		return setmetatable({n = 0}, Collection)&lt;br /&gt;
	end&lt;br /&gt;
}&lt;br /&gt;
Collection.__index = Collection&lt;br /&gt;
&lt;br /&gt;
local function empty(text)&lt;br /&gt;
	-- Return true if text is nil or empty (assuming a string).&lt;br /&gt;
	return text == nil or text == &amp;#039;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local timestamps = {}  -- cache&lt;br /&gt;
local function start_date(code, months)&lt;br /&gt;
	-- Return a timestamp string for a URL to list user contributions&lt;br /&gt;
	-- on and after the returned date.&lt;br /&gt;
	-- The code specifies the wanted format.&lt;br /&gt;
	-- For this module, only recent contributions are wanted, so the&lt;br /&gt;
	-- timestamp is today&amp;#039;s date less the given number of months (1 to 12).&lt;br /&gt;
	local key = code .. months&lt;br /&gt;
	if not timestamps[key] then&lt;br /&gt;
		local date = os.date(&amp;#039;!*t&amp;#039;)  -- today&amp;#039;s UTC date&lt;br /&gt;
		local y, m, d = date.year, date.month, date.day  -- full year, month (1-12), day (1-31)&lt;br /&gt;
		m = m - months&lt;br /&gt;
		if m &amp;lt;= 0 then&lt;br /&gt;
			m = m + 12&lt;br /&gt;
			y = y - 1&lt;br /&gt;
		end&lt;br /&gt;
		local limit = m == 2 and 28 or 30&lt;br /&gt;
		if d &amp;gt; limit then&lt;br /&gt;
			d = limit  -- good enough to ensure date is valid&lt;br /&gt;
		end&lt;br /&gt;
		timestamps[&amp;#039;y-m-d&amp;#039; .. months] = string.format(&amp;#039;%d-%02d-%02d&amp;#039;, y, m, d)&lt;br /&gt;
		timestamps[&amp;#039;ymdHMS&amp;#039; .. months] = string.format(&amp;#039;%d%02d%02d000000&amp;#039;, y, m, d)&lt;br /&gt;
	end&lt;br /&gt;
	return timestamps[key] or &amp;#039;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local note_text = {&lt;br /&gt;
	range = &amp;#039;*Links for ranges show the contributions in the previous %s.&amp;#039;,&lt;br /&gt;
	gadget = [=[&lt;br /&gt;
*&amp;lt;span id=&amp;quot;need-gadget&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;Contributions links for IPv6 ranges need the &amp;quot;&amp;lt;span style=&amp;quot;color:green;&amp;quot;&amp;gt;Allow /16, /24 and /27 – /32 CIDR ranges on Special:Contributions forms&amp;lt;/span&amp;gt;&amp;quot; gadget enabled in [[Special:Preferences#mw-prefsection-gadgets|Special:Preferences]], and scripting enabled in the browser.]=],&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local function make_note(strings, key)&lt;br /&gt;
	-- Record the fact that a particular note is needed, and return&lt;br /&gt;
	-- wikitext for a link to the note or &amp;#039;&amp;#039; if no link needed.&lt;br /&gt;
	if not strings.nonote then&lt;br /&gt;
		strings.notes = strings.notes or {}&lt;br /&gt;
		if not strings.notes[key] then&lt;br /&gt;
			if key == &amp;#039;gadget&amp;#039; then&lt;br /&gt;
				strings.notes[key] = note_text[key]&lt;br /&gt;
			elseif key == &amp;#039;range&amp;#039; then&lt;br /&gt;
				local when = &amp;#039;month&amp;#039;&lt;br /&gt;
				if strings.months &amp;gt; 1 then&lt;br /&gt;
					when = strings.months .. &amp;#039; months&amp;#039;&lt;br /&gt;
				end&lt;br /&gt;
				strings.notes[key] = string.format(note_text.range, when)&lt;br /&gt;
			else&lt;br /&gt;
				error(&amp;#039;make_note: unexpected key&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if key == &amp;#039;gadget&amp;#039; then&lt;br /&gt;
			return &amp;#039; [[#need-gadget|&amp;lt;sup&amp;gt;[note]&amp;lt;/sup&amp;gt;]]&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function describe_total(total, isalloc)&lt;br /&gt;
	-- Return text describing given number of addresses or /64 allocations.&lt;br /&gt;
	if total &amp;lt;= 9999 then&lt;br /&gt;
		-- Can have fractions if total is the number of /64 allocations.&lt;br /&gt;
		if total &amp;lt; 9 then&lt;br /&gt;
			return (string.format(&amp;#039;%.1f&amp;#039;, total):gsub(&amp;#039;%.0$&amp;#039;, &amp;#039;&amp;#039;))&lt;br /&gt;
		end&lt;br /&gt;
		return string.format(&amp;#039;%.0f&amp;#039;, total)&lt;br /&gt;
	end&lt;br /&gt;
	if not isalloc then&lt;br /&gt;
		local alloc = 2^64&lt;br /&gt;
		if total &amp;gt;= alloc then&lt;br /&gt;
			return describe_total(total / alloc, true) .. &amp;#039; /64&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	total = total/1024&lt;br /&gt;
	local suffix = &amp;#039;K&amp;#039;&lt;br /&gt;
	if total &amp;gt;= 1024 then&lt;br /&gt;
		total = total/1024&lt;br /&gt;
		suffix = &amp;#039;M&amp;#039;&lt;br /&gt;
		if total &amp;gt;= 1024 then&lt;br /&gt;
			total = total/1024&lt;br /&gt;
			suffix = &amp;#039;G&amp;#039;&lt;br /&gt;
			if total &amp;gt; 64 then&lt;br /&gt;
				return &amp;#039;&amp;gt;64G&amp;#039;&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return string.format(&amp;#039;%.0f&amp;#039;, total) .. suffix&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function describe_size(ipsize, size)&lt;br /&gt;
	-- Return text describing how many IPs are in a range with size = prefix length.&lt;br /&gt;
	local function numtext(n)&lt;br /&gt;
		if n &amp;lt;= 16 then&lt;br /&gt;
			return tostring(2^n)&lt;br /&gt;
		end&lt;br /&gt;
		if n &amp;lt;= 19 then&lt;br /&gt;
			return tostring(2^(n - 10)) .. &amp;#039;K&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		if n &amp;lt;= 29 then&lt;br /&gt;
			return tostring(2^(n - 20)) .. &amp;#039;M&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		if n &amp;lt;= 36 then&lt;br /&gt;
			return tostring(2^(n - 30)) .. &amp;#039;G&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		return &amp;#039;&amp;gt;64G&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local host = ipsize - size&lt;br /&gt;
	if host &amp;lt;= 32 then&lt;br /&gt;
		-- IPv4 or IPv6.&lt;br /&gt;
		return numtext(host)&lt;br /&gt;
	end&lt;br /&gt;
	-- Must be IPv6.&lt;br /&gt;
	if host &amp;lt;= 64 then&lt;br /&gt;
		local s = ({&lt;br /&gt;
			[64] = &amp;#039;1&amp;#039;,  [63] = &amp;#039;50%&amp;#039;, [62] = &amp;#039;25%&amp;#039;, [61] = &amp;#039;12%&amp;#039;,&lt;br /&gt;
			[60] = &amp;#039;6%&amp;#039;, [59] = &amp;#039;3%&amp;#039;,  [58] = &amp;#039;2%&amp;#039;&lt;br /&gt;
		})[host] or &amp;#039;&amp;lt;1%&amp;#039;&lt;br /&gt;
		return s .. &amp;#039; /64&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	-- IPv6 with size &amp;lt; 64.&lt;br /&gt;
	return numtext(host - 64) .. &amp;#039; /64&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ipv6_string(ip)&lt;br /&gt;
	-- Return a string equivalent to the given IPv6 address.&lt;br /&gt;
	local z1, z2  -- indices of run of zeros to be displayed as &amp;quot;::&amp;quot;&lt;br /&gt;
	local zstart, zcount&lt;br /&gt;
	for i = 1, 9 do&lt;br /&gt;
		-- Find left-most occurrence of longest run of two or more zeros.&lt;br /&gt;
		if i &amp;lt; 9 and ip[i] == 0 then&lt;br /&gt;
			if zstart then&lt;br /&gt;
				zcount = zcount + 1&lt;br /&gt;
			else&lt;br /&gt;
				zstart = i&lt;br /&gt;
				zcount = 1&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			if zcount and zcount &amp;gt; 1 then&lt;br /&gt;
				if not z1 or zcount &amp;gt; z2 - z1 + 1 then&lt;br /&gt;
					z1 = zstart&lt;br /&gt;
					z2 = zstart + zcount - 1&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			zstart = nil&lt;br /&gt;
			zcount = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local parts = Collection.new()&lt;br /&gt;
	for i = 1, 8 do&lt;br /&gt;
		if z1 and z1 &amp;lt;= i and i &amp;lt;= z2 then&lt;br /&gt;
			if i == z1 then&lt;br /&gt;
				if z1 == 1 or z2 == 8 then&lt;br /&gt;
					if z1 == 1 and z2 == 8 then&lt;br /&gt;
						return &amp;#039;::&amp;#039;&lt;br /&gt;
					end&lt;br /&gt;
					parts:add(&amp;#039;:&amp;#039;)&lt;br /&gt;
				else&lt;br /&gt;
					parts:add(&amp;#039;&amp;#039;)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			parts:add(string.format(&amp;#039;%x&amp;#039;, ip[i]))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return parts:join(&amp;#039;:&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ip_string(ip)&lt;br /&gt;
	-- Return a string equivalent to given IP address (IPv4 or IPv6).&lt;br /&gt;
	if ip.n == 2 then&lt;br /&gt;
		-- IPv4.&lt;br /&gt;
		local parts = {}&lt;br /&gt;
		for i = 1, 2 do&lt;br /&gt;
			local w = ip[i]&lt;br /&gt;
			local q = i == 1 and 1 or 3&lt;br /&gt;
			parts[q] = math.floor(w / 256)&lt;br /&gt;
			parts[q+1] = w % 256&lt;br /&gt;
		end&lt;br /&gt;
		return table.concat(parts, &amp;#039;.&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	return ipv6_string(ip)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Metatable for some operations on IP addresses.&lt;br /&gt;
local ipmt = {&lt;br /&gt;
	__eq = function (lhs, rhs)&lt;br /&gt;
		-- Return true if values in numbered tables match.&lt;br /&gt;
		if lhs.n == rhs.n then&lt;br /&gt;
			for i = 1, lhs.n do&lt;br /&gt;
				if lhs[i] ~= rhs[i] then&lt;br /&gt;
					return false&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
		return false&lt;br /&gt;
	end,&lt;br /&gt;
	__lt = function (lhs, rhs)&lt;br /&gt;
		-- Return true if lhs &amp;lt; rhs; for sort.&lt;br /&gt;
		if lhs.n == rhs.n then&lt;br /&gt;
			for i = 1, lhs.n do&lt;br /&gt;
				if lhs[i] ~= rhs[i] then&lt;br /&gt;
					return lhs[i] &amp;lt; rhs[i]&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			return false&lt;br /&gt;
		end&lt;br /&gt;
		return lhs.n &amp;lt; rhs.n  -- sort IPv4 before IPv6, although not needed&lt;br /&gt;
	end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local function ipv4_address(ip_str)&lt;br /&gt;
	-- Return a collection of two 16-bit words (numbers) equivalent&lt;br /&gt;
	-- to the IPv4 address given as a quad-dotted string, or&lt;br /&gt;
	-- return nil if invalid.&lt;br /&gt;
	-- This representation is for compatibility with IPv6 addresses.&lt;br /&gt;
	local parts = Collection.new()&lt;br /&gt;
	local s = ip_str:match(&amp;#039;^%s*(.-)%s*$&amp;#039;) .. &amp;#039;.&amp;#039;&lt;br /&gt;
	for item in s:gmatch(&amp;#039;(.-)%.&amp;#039;) do&lt;br /&gt;
		parts:add(item)&lt;br /&gt;
	end&lt;br /&gt;
	if parts.n == 4 then&lt;br /&gt;
		for i, s in ipairs(parts) do&lt;br /&gt;
			if s:match(&amp;#039;^%d+$&amp;#039;) then&lt;br /&gt;
				local num = tonumber(s)&lt;br /&gt;
				if 0 &amp;lt;= num and num &amp;lt;= 255 then&lt;br /&gt;
					if num &amp;gt; 0 and s:match(&amp;#039;^0&amp;#039;) then&lt;br /&gt;
						-- A redundant leading zero is an error because it is for an IP in octal.&lt;br /&gt;
						return nil&lt;br /&gt;
					end&lt;br /&gt;
					parts[i] = num&lt;br /&gt;
				else&lt;br /&gt;
					return nil&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				return nil&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		local result = Collection.new()&lt;br /&gt;
		for i = 1, 3, 2 do&lt;br /&gt;
			result:add(parts[i] * 256 + parts[i+1])&lt;br /&gt;
		end&lt;br /&gt;
		return setmetatable(result, ipmt)&lt;br /&gt;
	end&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ipv6_address(ip_str)&lt;br /&gt;
	-- Return a collection of eight 16-bit words (numbers) equivalent&lt;br /&gt;
	-- to the IPv6 address given as a colon-delimited string, or&lt;br /&gt;
	-- return nil if invalid.&lt;br /&gt;
	local _, n = ip_str:gsub(&amp;#039;:&amp;#039;, &amp;#039;:&amp;#039;)&lt;br /&gt;
	if n &amp;lt; 7 then&lt;br /&gt;
		ip_str, n = ip_str:gsub(&amp;#039;::&amp;#039;, string.rep(&amp;#039;:&amp;#039;, 9 - n))&lt;br /&gt;
	end&lt;br /&gt;
	local parts = Collection.new()&lt;br /&gt;
	for item in (ip_str .. &amp;#039;:&amp;#039;):gmatch(&amp;#039;(.-):&amp;#039;) do&lt;br /&gt;
		parts:add(item)&lt;br /&gt;
	end&lt;br /&gt;
	if parts.n == 8 then&lt;br /&gt;
		for i, s in ipairs(parts) do&lt;br /&gt;
			if s == &amp;#039;&amp;#039; then&lt;br /&gt;
				parts[i] = 0&lt;br /&gt;
			else&lt;br /&gt;
				local num = tonumber(&amp;#039;0x&amp;#039; .. s)&lt;br /&gt;
				if num and 0 &amp;lt;= num and num &amp;lt;= 65535 then&lt;br /&gt;
					parts[i] = num&lt;br /&gt;
				else&lt;br /&gt;
					return nil&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return setmetatable(parts, ipmt)&lt;br /&gt;
	end&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function common_length(num1, num2, nr_bits)&lt;br /&gt;
	-- Return number of prefix bits that two integers have in common.&lt;br /&gt;
	-- Number of bits in each number is nr_bits = 16, 8, 4, 2 or 1.&lt;br /&gt;
	if nr_bits &amp;lt;= 1 then&lt;br /&gt;
		return num1 == num2 and 1 or 0&lt;br /&gt;
	end&lt;br /&gt;
	local half = nr_bits / 2&lt;br /&gt;
	local splitter = 2^half&lt;br /&gt;
	local upper1, lower1 = math.modf(num1 / splitter)&lt;br /&gt;
	local upper2, lower2 = math.modf(num2 / splitter)&lt;br /&gt;
	if upper1 == upper2 then&lt;br /&gt;
		lower1 = math.floor(lower1 * splitter + 0.5)&lt;br /&gt;
		lower2 = math.floor(lower2 * splitter + 0.5)&lt;br /&gt;
		return half + common_length(lower1, lower2, half)&lt;br /&gt;
	end&lt;br /&gt;
	return common_length(upper1, upper2, half)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function common_prefix_length(ip1, ip2)&lt;br /&gt;
	-- Return number of prefix bits that two IPs have in common.&lt;br /&gt;
	-- Caller ensures that both IPs are IPv4 or both are IPv6.&lt;br /&gt;
	local size = 0&lt;br /&gt;
	for i = 1, ip1.n do&lt;br /&gt;
		local w1, w2 = ip1[i], ip2[i]&lt;br /&gt;
		if w1 == w2 then&lt;br /&gt;
			size = size + 16&lt;br /&gt;
		else&lt;br /&gt;
			return size + common_length(w1, w2, 16)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return size&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ip_prefix(ip, length)&lt;br /&gt;
	-- Return a copy of ip masked to contain only the prefix of given length.&lt;br /&gt;
	local result = { n = ip.n }&lt;br /&gt;
	for i = 1, ip.n do&lt;br /&gt;
		if length &amp;gt; 0 then&lt;br /&gt;
			if length &amp;gt;= 16 then&lt;br /&gt;
				result[i] = ip[i]&lt;br /&gt;
				length = length - 16&lt;br /&gt;
			else&lt;br /&gt;
				result[i] = bit32.band(ip[i], bit32.arshift(0xffff8000, length - 1))&lt;br /&gt;
				length = 0&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			result[i] = 0&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return setmetatable(result, ipmt)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ip_incremented(ip)&lt;br /&gt;
	-- Return a new IP equal to ip + 1.&lt;br /&gt;
	-- Will wraparound (255.255.255.255 + 1 = 0.0.0.0)!&lt;br /&gt;
	local result = { n = ip.n }&lt;br /&gt;
	local carry = 1&lt;br /&gt;
	for i = ip.n, 1, -1 do&lt;br /&gt;
		local sum = ip[i] + carry&lt;br /&gt;
		if sum &amp;gt;= 0x10000 then&lt;br /&gt;
			carry = 1&lt;br /&gt;
			sum = sum - 0x10000&lt;br /&gt;
		else&lt;br /&gt;
			carry = 0&lt;br /&gt;
		end&lt;br /&gt;
		result[i] = sum&lt;br /&gt;
	end&lt;br /&gt;
	return setmetatable(result, ipmt)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function is_next_ip(ip1, ip2)&lt;br /&gt;
	-- Return true if ip2 is the next IP after ip1 (ip2 == ip1 + 1).&lt;br /&gt;
	-- IPs are sorted and unique so ip1 &amp;lt; ip2 and can ignore wrapping to zero.&lt;br /&gt;
	-- This is lower overhead than making a new incremented IP then comparing.&lt;br /&gt;
	if ip1 and ip2 then&lt;br /&gt;
		local carry = 1&lt;br /&gt;
		for i = ip1.n, 1, -1 do&lt;br /&gt;
			local sum = ip1[i] + carry&lt;br /&gt;
			if sum &amp;gt;= 0x10000 then&lt;br /&gt;
				carry = 1&lt;br /&gt;
				sum = sum - 0x10000&lt;br /&gt;
			else&lt;br /&gt;
				carry = 0&lt;br /&gt;
			end&lt;br /&gt;
			if sum ~= ip2[i] then&lt;br /&gt;
				return false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Each IP in a range except for the last IP has a &amp;#039;common&amp;#039; field which is&lt;br /&gt;
-- a number specifying how many bits are common between the prefixes of this&lt;br /&gt;
-- IP and the next IP (0 if this IP starts with 0 and the next starts with 1).&lt;br /&gt;
-- Each non-empty range has exactly one &amp;quot;minimum common&amp;quot;, that is, its value&lt;br /&gt;
-- of common is smaller than all others. That there is only one minimum common&lt;br /&gt;
-- follows from the fact that the IPs are unique and sorted.&lt;br /&gt;
local function make_range(iplist, ifirst, ilast)&lt;br /&gt;
	-- Return a table for the range of IPs from iplist[ifirst] to iplist[ilast] inclusive.&lt;br /&gt;
	local imin, vmin, done&lt;br /&gt;
	if ifirst &amp;lt; ilast then&lt;br /&gt;
		for i = ifirst, ilast - 1 do&lt;br /&gt;
			-- Find the (unique) minimum of common lengths.&lt;br /&gt;
			local common = iplist[i].common&lt;br /&gt;
			if vmin then&lt;br /&gt;
				if vmin &amp;gt; common then&lt;br /&gt;
					vmin = common&lt;br /&gt;
					imin = i&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				vmin = common&lt;br /&gt;
				imin = i&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		vmin = iplist.ipsize&lt;br /&gt;
		imin = ifirst&lt;br /&gt;
		done = true&lt;br /&gt;
	end&lt;br /&gt;
	if vmin &amp;gt; iplist.allocation then&lt;br /&gt;
		-- For IPv6, the default allocation is /64 and there is no point having&lt;br /&gt;
		-- more precise ranges as they add unnecessary complexity.&lt;br /&gt;
		-- However, using results=all sets allocation = 128 so vmin is not changed.&lt;br /&gt;
		vmin = iplist.allocation&lt;br /&gt;
	end&lt;br /&gt;
	return {&lt;br /&gt;
		ifirst = ifirst,  -- index of first IP&lt;br /&gt;
		ilast = ilast,    -- index of last IP&lt;br /&gt;
		imin = imin,      -- index of IP with minimum common&lt;br /&gt;
		size = vmin,      -- number of common bits in prefix (the minimum)&lt;br /&gt;
		prefix = ip_prefix(iplist[imin], vmin),  -- IP table of the base IP&lt;br /&gt;
		done = done,      -- true if know that this range cannot be improved&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function split_range(iplist, range, depth)&lt;br /&gt;
	-- Return a table of two or more ranges that more precisely target&lt;br /&gt;
	-- the IPs in range, or return nothing if unable to improve range.&lt;br /&gt;
	depth = depth and depth + 1 or 0&lt;br /&gt;
	if depth &amp;lt;= 20 and  -- 20 examines 1M contiguous addresses down to individual IPs&lt;br /&gt;
			not range.done and&lt;br /&gt;
			range.size &amp;lt; iplist.allocation and&lt;br /&gt;
			range.ifirst &amp;lt; range.ilast then&lt;br /&gt;
		local imin = range.imin&lt;br /&gt;
		assert(imin and range.ifirst &amp;lt;= imin and imin &amp;lt; range.ilast)&lt;br /&gt;
		local r1 = make_range(iplist, range.ifirst, range.imin)&lt;br /&gt;
		local r2 = make_range(iplist, range.imin + 1, range.ilast)&lt;br /&gt;
		local pointless = range.size + 1&lt;br /&gt;
		if r1.size &amp;gt; pointless or r2.size &amp;gt; pointless then&lt;br /&gt;
			return { r1, r2 }&lt;br /&gt;
		end&lt;br /&gt;
		local result = Collection.new()&lt;br /&gt;
		local function store_split(range)&lt;br /&gt;
			local split = split_range(iplist, range, depth)&lt;br /&gt;
			if split then&lt;br /&gt;
				for _, r in ipairs(split) do&lt;br /&gt;
					result:add(r)&lt;br /&gt;
				end&lt;br /&gt;
				return true&lt;br /&gt;
			else&lt;br /&gt;
				result:add(range)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		local improved1 = store_split(r1)&lt;br /&gt;
		local improved2 = store_split(r2)&lt;br /&gt;
		if improved1 or improved2 then&lt;br /&gt;
			return result&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	range.done = true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function better_summary(iplist, summary)&lt;br /&gt;
	-- Return a better summary that more precisely targets the specified IPs,&lt;br /&gt;
	-- or return nil if unable to improve the summary.&lt;br /&gt;
	local better = Collection.new()&lt;br /&gt;
	local improved&lt;br /&gt;
	for _, range in ipairs(summary) do&lt;br /&gt;
		local split = split_range(iplist, range)&lt;br /&gt;
		if split then&lt;br /&gt;
			improved = true&lt;br /&gt;
			for _, r in ipairs(split) do&lt;br /&gt;
				better:add(r)&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			better:add(range)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return improved and better&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_summaries(iplist)&lt;br /&gt;
	-- Return a collection where each item is a summary.&lt;br /&gt;
	-- A summary is a table of one or more ranges.&lt;br /&gt;
	-- A summary covers all the given IPs and probably more.&lt;br /&gt;
	-- A range is a table representing a CIDR block such as 1.2.248.0/21.&lt;br /&gt;
	-- The first summary found is a single range; each subsequent summary&lt;br /&gt;
	-- (if any) uses more ranges to better target the given IPs.&lt;br /&gt;
	-- The result omits any summary with a range size that is too small (too many IPs).&lt;br /&gt;
	local function good_size(summary)&lt;br /&gt;
		for _, range in ipairs(summary) do&lt;br /&gt;
			if range.size &amp;lt; iplist.minsize then&lt;br /&gt;
				return false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	local summaries = Collection.new()&lt;br /&gt;
	if iplist.n &amp;gt; 0 then&lt;br /&gt;
		for i = 1, iplist.n - 1 do&lt;br /&gt;
			-- Set length of prefixes common between each pair of IPs.&lt;br /&gt;
			iplist[i].common = common_prefix_length(iplist[i], iplist[i+1])&lt;br /&gt;
		end&lt;br /&gt;
		local summary = { make_range(iplist, 1, iplist.n) }&lt;br /&gt;
		while summary and summaries.n &amp;lt; iplist.maxresults do&lt;br /&gt;
			if good_size(summary) then&lt;br /&gt;
				summaries:add(summary)&lt;br /&gt;
			end&lt;br /&gt;
			summary = better_summary(iplist, summary)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return summaries&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function extract_ipv4(result, omitted, line)&lt;br /&gt;
	-- Extract any IPv4 addresses from given line or throw error.&lt;br /&gt;
	-- Accept CIDR /n to specify a range (only accept 16 to 32).&lt;br /&gt;
	-- Addresses must be delimited with whitespace to reduce false positives.&lt;br /&gt;
	local function store(hit)&lt;br /&gt;
		local n = 32&lt;br /&gt;
		local lhs, rhs = hit:match(&amp;#039;^(.-)/(%d+)$&amp;#039;)&lt;br /&gt;
		if lhs then&lt;br /&gt;
			hit = lhs&lt;br /&gt;
			n = tonumber(rhs)&lt;br /&gt;
			if not (n and 16 &amp;lt;= n and n &amp;lt;= 32) then&lt;br /&gt;
				error(&amp;#039;CIDR /n only accepts n = 16 to 32, invalid: &amp;#039; .. lhs .. &amp;#039;/&amp;#039; .. rhs, 0)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		local ip = ipv4_address(hit)&lt;br /&gt;
		if ip then&lt;br /&gt;
			if n == 32 then&lt;br /&gt;
				result:add(ip)&lt;br /&gt;
			else&lt;br /&gt;
				if ip ~= ip_prefix(ip, n) then&lt;br /&gt;
					error(&amp;#039;Invalid base address (host bits should be zero): &amp;#039; .. hit, 0)&lt;br /&gt;
				end&lt;br /&gt;
				for _ = 1, 2^(32 - n) do&lt;br /&gt;
					result:add(ip)&lt;br /&gt;
					ip = ip_incremented(ip)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			omitted:add(hit)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	line = line:gsub(&amp;#039;:&amp;#039;, &amp;#039; &amp;#039;)  -- so wikitext indents or other colons don&amp;#039;t obscure an IVp4 address&lt;br /&gt;
	for hit in line:gmatch(&amp;#039;%S+&amp;#039;) do&lt;br /&gt;
		if hit:match(&amp;#039;^%d+%.%d+[%.%d/]+$&amp;#039;) then&lt;br /&gt;
			local _, n = hit:gsub(&amp;#039;%.&amp;#039;, &amp;#039;.&amp;#039;)&lt;br /&gt;
			if n &amp;gt;= 3 then&lt;br /&gt;
				store(hit)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function extract_ipv6(result, omitted, line)&lt;br /&gt;
	-- Extract any IPv6 addresses from given line or throw error.&lt;br /&gt;
	-- Addresses must be delimited with whitespace to reduce false positives.&lt;br /&gt;
	-- Want to accept all valid IPv6 despite the fact that contributors will&lt;br /&gt;
	-- not have an address starting with &amp;#039;:&amp;#039;.&lt;br /&gt;
	-- Also want to be able to parse arbitrary wikitext which might use colons&lt;br /&gt;
	-- for indenting. To achieve that, if an address at the start of a line&lt;br /&gt;
	-- is valid, use it; otherwise strip any leading colons and try again.&lt;br /&gt;
	for pos, hit in line:gmatch(&amp;#039;()(%S+)&amp;#039;) do&lt;br /&gt;
		local ipstr, length = hit:match(&amp;#039;^([:%x]+)(/?%d*)$&amp;#039;)&lt;br /&gt;
		if ipstr then&lt;br /&gt;
			local _, n = ipstr:gsub(&amp;#039;:&amp;#039;, &amp;#039;:&amp;#039;)&lt;br /&gt;
			if n &amp;gt;= 2 then&lt;br /&gt;
				local ip = ipv6_address(ipstr)&lt;br /&gt;
				if not ip and pos == 1 then&lt;br /&gt;
					ipstr, n = ipstr:gsub(&amp;#039;^:+&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
					if n &amp;gt; 0 then&lt;br /&gt;
						ip = ipv6_address(ipstr)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
				if ip then&lt;br /&gt;
					if length and #length &amp;gt; 0 then&lt;br /&gt;
						error(&amp;#039;CIDR /n not accepted for IPv6: &amp;#039; .. hit, 0)&lt;br /&gt;
					end&lt;br /&gt;
					result:add(ip)&lt;br /&gt;
				else&lt;br /&gt;
					omitted:add(hit)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function contribs(address, strings, ipbase, size)&lt;br /&gt;
	-- Return a URL or wikilink to list the contributions for an IP or IP range,&lt;br /&gt;
	-- or return an empty string if cannot do anything useful.&lt;br /&gt;
	-- The given address is a string of either a single IP or a CIDR range.&lt;br /&gt;
	-- If using old system:&lt;br /&gt;
	--   For IPv6 CIDR, return a Special:Contributions link using an asterisk&lt;br /&gt;
	--   wildcard which should work if the user has enabled the gadget&lt;br /&gt;
	--   &amp;quot;Allow /16, /24 and /27 – /32 CIDR ranges on Special:Contributions&amp;quot;.&lt;br /&gt;
	local encoded, count = address:gsub(&amp;#039;/&amp;#039;, &amp;#039;%%2F&amp;#039;)&lt;br /&gt;
	if strings.want_old and count &amp;gt; 0 then&lt;br /&gt;
		make_note(strings, &amp;#039;range&amp;#039;)&lt;br /&gt;
		if address:find(&amp;#039;:&amp;#039;, 1, true) then&lt;br /&gt;
			if ipbase and size then&lt;br /&gt;
				local digits = math.floor(size / 4)&lt;br /&gt;
				if digits &amp;lt; 3 then&lt;br /&gt;
					digits = 3&lt;br /&gt;
				end&lt;br /&gt;
				local wildcard = digits % 4 == 0 and &amp;#039;:*&amp;#039; or &amp;#039;*&amp;#039;&lt;br /&gt;
				local parts = {}&lt;br /&gt;
				for i = 1, 8 do&lt;br /&gt;
					local hex = string.format(&amp;#039;%X&amp;#039;, ipbase[i])  -- must be uppercase&lt;br /&gt;
					if digits &amp;gt;= 4 then&lt;br /&gt;
						parts[i] = hex&lt;br /&gt;
						digits = digits - 4&lt;br /&gt;
						if digits &amp;lt;= 0 then&lt;br /&gt;
							break&lt;br /&gt;
						end&lt;br /&gt;
					else&lt;br /&gt;
						local nz  -- number of leading zeros in this group of four digits&lt;br /&gt;
						if hex == &amp;#039;0&amp;#039; then&lt;br /&gt;
							nz = 4&lt;br /&gt;
						else&lt;br /&gt;
							nz = 4 - #hex&lt;br /&gt;
						end&lt;br /&gt;
						if digits &amp;lt;= nz then&lt;br /&gt;
							-- Cannot properly handle this case; have to omit group&lt;br /&gt;
							-- because &amp;quot;0&amp;quot; never occurs as the first digit.&lt;br /&gt;
							wildcard = &amp;#039;:*&amp;#039;&lt;br /&gt;
						else&lt;br /&gt;
							hex = string.rep(&amp;#039;0&amp;#039;, nz) .. hex  -- four digits&lt;br /&gt;
							parts[i] = hex:sub(1, digits)&lt;br /&gt;
						end&lt;br /&gt;
						break&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
				address = table.concat(parts, &amp;#039;:&amp;#039;) .. wildcard&lt;br /&gt;
				local url = &amp;#039;[https://en.wikipedia.org/wiki/Special:Contributions/%s?ucstart=%s contribs]&amp;#039;&lt;br /&gt;
				-- %s = IPv6 prefix address in uppercase with &amp;#039;*&amp;#039; wildcard at end&lt;br /&gt;
				-- %s = Start date formatted &amp;#039;yyyymmdd000000&amp;#039;&lt;br /&gt;
				return string.format(url, address, start_date(&amp;#039;ymdHMS&amp;#039;, strings.months)) .. make_note(strings, &amp;#039;gadget&amp;#039;)&lt;br /&gt;
			end&lt;br /&gt;
			return &amp;#039;&amp;#039;  -- no contributions link available&lt;br /&gt;
		end&lt;br /&gt;
		local url = &amp;#039;[https://tools.wmflabs.org/xtools/rangecontribs/?project=en.wikipedia.org&amp;amp;namespace=all&amp;amp;limit=50&amp;amp;text=%s&amp;amp;begin=%s contribs]&amp;#039;&lt;br /&gt;
		-- %s = IPv4 CIDR range with &amp;#039;/&amp;#039; changed to &amp;#039;%2F&amp;#039;&lt;br /&gt;
		-- %s = Start date formatted &amp;#039;yyyy-mm-dd&amp;#039;&lt;br /&gt;
		return string.format(url, encoded, start_date(&amp;#039;y-m-d&amp;#039;, strings.months))&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;[[Special:Contributions/&amp;#039; .. address .. &amp;#039;|contribs]]&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Strings for results using plain text.&lt;br /&gt;
-- The pre tags used are html which do not provide &amp;quot;nowiki&amp;quot;,&lt;br /&gt;
-- but that is not required by the text used.&lt;br /&gt;
local plaintext = {&lt;br /&gt;
&lt;br /&gt;
header = [=[&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Total       Affected     Given        Range]=],&lt;br /&gt;
&lt;br /&gt;
footer = &amp;#039;&amp;lt;/pre&amp;gt;&amp;#039;,&lt;br /&gt;
&lt;br /&gt;
sumfirst = [=[&lt;br /&gt;
----------------------------------------------------------&lt;br /&gt;
%s%-12s %-12s %-11d %s%s]=],&lt;br /&gt;
-- %s = empty string (dummy for compatibility)&lt;br /&gt;
-- %s = total affected&lt;br /&gt;
-- %s = affected&lt;br /&gt;
-- %d = given (number of addresses given in input covered by this range)&lt;br /&gt;
-- %s = IP address range&lt;br /&gt;
-- %s = empty string&lt;br /&gt;
&lt;br /&gt;
sumnext = [=[&lt;br /&gt;
             %-12s %-11d %s%s]=],&lt;br /&gt;
-- %s = affected&lt;br /&gt;
-- %d = given&lt;br /&gt;
-- %s = IP address range&lt;br /&gt;
-- %s = empty string&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Strings for results using a table in wikitext.&lt;br /&gt;
local wikitable = {&lt;br /&gt;
&lt;br /&gt;
header = [=[&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Total&amp;lt;br /&amp;gt;affected !! Affected&amp;lt;br /&amp;gt;addresses !! Given&amp;lt;br /&amp;gt;addresses !! Range !! Contribs]=],&lt;br /&gt;
&lt;br /&gt;
footer = &amp;#039;|}&amp;#039;,&lt;br /&gt;
&lt;br /&gt;
sumfirst = [=[&lt;br /&gt;
|- style=&amp;quot;background: darkgray; height: 6px;&amp;quot;&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot; |&lt;br /&gt;
|- style=&amp;quot;vertical-align: top;&amp;quot;&lt;br /&gt;
|rowspan=&amp;quot;%s&amp;quot; |%s ||%s ||%d ||%s ||%s]=],&lt;br /&gt;
-- %s = string of number of ranges in summary (number of rows)&lt;br /&gt;
-- %s = total affected&lt;br /&gt;
-- %s = affected&lt;br /&gt;
-- %d = given&lt;br /&gt;
-- %s = IP address range&lt;br /&gt;
-- %s = contributions link&lt;br /&gt;
&lt;br /&gt;
sumnext = [=[&lt;br /&gt;
|-&lt;br /&gt;
|%s ||%d ||%s ||%s]=],&lt;br /&gt;
-- %s = affected&lt;br /&gt;
-- %d = given&lt;br /&gt;
-- %s = IP address range&lt;br /&gt;
-- %s = contributions link&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local function show_summary(lines, strings, iplist, summary)&lt;br /&gt;
	-- Show the summary by adding table wikitext or plain text to lines.&lt;br /&gt;
	local want_plain = iplist.want_plain&lt;br /&gt;
	local total = 0&lt;br /&gt;
	for _, range in ipairs(summary) do&lt;br /&gt;
		-- A number is a double which easily handles 2^128 = 3.4e38.&lt;br /&gt;
		total = total + 2^(iplist.ipsize - range.size)&lt;br /&gt;
	end&lt;br /&gt;
	for i, range in ipairs(summary) do&lt;br /&gt;
		local prefix = ip_string(range.prefix)&lt;br /&gt;
		local size = range.size&lt;br /&gt;
		local affected = describe_size(iplist.ipsize, size)&lt;br /&gt;
		local given = range.ilast - range.ifirst + 1&lt;br /&gt;
		local address&lt;br /&gt;
		local link = &amp;#039;&amp;#039;&lt;br /&gt;
		if size == iplist.ipsize then&lt;br /&gt;
			address = prefix&lt;br /&gt;
			if not want_plain then&lt;br /&gt;
				link = contribs(address, strings)&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			address = prefix .. &amp;#039;/&amp;#039; .. size&lt;br /&gt;
			if not want_plain then&lt;br /&gt;
				link = contribs(address, strings, range.prefix, size)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		local s&lt;br /&gt;
		if i == 1 then&lt;br /&gt;
			s = string.format(strings.sumfirst,&lt;br /&gt;
					want_plain and &amp;#039;&amp;#039; or tostring(#summary),&lt;br /&gt;
					describe_total(total),&lt;br /&gt;
					affected, given, address, link)&lt;br /&gt;
		else&lt;br /&gt;
			s = string.format(strings.sumnext,&lt;br /&gt;
					affected, given, address, link)&lt;br /&gt;
		end&lt;br /&gt;
		-- Pre tags returned by a module are html tags, not like wikitext &amp;lt;pre&amp;gt;...&amp;lt;/pre&amp;gt;.&lt;br /&gt;
		lines:add(want_plain and mw.text.nowiki(s) or s)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function process_ips(lines, iplist, omitted)&lt;br /&gt;
	-- Process a list of IP addresses, adding text of results to lines.&lt;br /&gt;
	-- The list should contain either all IPv4 addresses, or all IPv6 (not a mixture).&lt;br /&gt;
	local seq1, seq2, seqmany&lt;br /&gt;
	local function show_sequence()&lt;br /&gt;
		if seq1 and seq2 then&lt;br /&gt;
			local text = ip_string(seq1)&lt;br /&gt;
			if seqmany then&lt;br /&gt;
				seqmany = false&lt;br /&gt;
				text = text .. &amp;#039; – &amp;#039; .. ip_string(seq2)&lt;br /&gt;
			end&lt;br /&gt;
			seq1 = nil&lt;br /&gt;
			seq2 = nil&lt;br /&gt;
			local markup = text:sub(1, 1) == &amp;#039;:&amp;#039; and &amp;#039;:&amp;lt;nowiki/&amp;gt;&amp;#039; or &amp;#039;:&amp;#039;&lt;br /&gt;
			lines:add(markup .. text)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local function show_ip(ip)&lt;br /&gt;
		-- Show IP or record it to be included in a &amp;quot;from to&amp;quot; sequence of IPs.&lt;br /&gt;
		if is_next_ip(seq2, ip) then&lt;br /&gt;
			seq2 = ip&lt;br /&gt;
			seqmany = true&lt;br /&gt;
		else&lt;br /&gt;
			show_sequence()&lt;br /&gt;
			seq1 = ip&lt;br /&gt;
			seq2 = ip&lt;br /&gt;
			seqmany = false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if iplist.n &amp;lt; 1 then&lt;br /&gt;
		return&lt;br /&gt;
	end&lt;br /&gt;
	if lines.n &amp;gt; 0 then&lt;br /&gt;
		lines:add(&amp;#039;&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	if omitted.n &amp;gt; 0 then&lt;br /&gt;
		lines:add(&amp;#039;Warning, omitted as invalid: &amp;#039; .. omitted:join(&amp;#039; &amp;#039;))&lt;br /&gt;
		lines:add(&amp;#039;&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	local heading_line&lt;br /&gt;
	if not iplist.nolist then&lt;br /&gt;
		lines:add(&amp;#039;&amp;#039;)  -- this blank line is replaced with a heading&lt;br /&gt;
		heading_line = lines.n&lt;br /&gt;
	end&lt;br /&gt;
	local duplicates = Collection.new()&lt;br /&gt;
	local previous&lt;br /&gt;
	iplist:sort()&lt;br /&gt;
	-- Check for duplicates which can interfere with method to get ranges.&lt;br /&gt;
	for i, ip in ipairs(iplist) do&lt;br /&gt;
		if previous == ip then&lt;br /&gt;
			duplicates:add(i)  -- index to omit duplicate later&lt;br /&gt;
		elseif not iplist.nolist then&lt;br /&gt;
			show_ip(ip)&lt;br /&gt;
		end&lt;br /&gt;
		previous = ip&lt;br /&gt;
	end&lt;br /&gt;
	show_sequence()&lt;br /&gt;
	local duplicate_text = &amp;#039;&amp;#039;&lt;br /&gt;
	if duplicates.n &amp;gt; 0 then&lt;br /&gt;
		duplicate_text = &amp;#039; (after omitting some duplicates)&amp;#039;&lt;br /&gt;
		for i = duplicates.n, 1, -1 do&lt;br /&gt;
			iplist:remove(duplicates[i])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local heading_text = string.format(&amp;#039;Sorted %d %s address%s&amp;#039;,&lt;br /&gt;
		iplist.n,&lt;br /&gt;
		iplist.ipname,&lt;br /&gt;
		iplist.n == 1 and &amp;#039;&amp;#039; or &amp;#039;es&amp;#039;&lt;br /&gt;
		)&lt;br /&gt;
	if heading_line then&lt;br /&gt;
		lines[heading_line] = heading_text .. duplicate_text .. &amp;#039;:&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local strings = iplist.want_plain and plaintext or wikitable&lt;br /&gt;
	strings.notes = nil  -- needed when module is kept loaded for multiple tests&lt;br /&gt;
	strings.want_old = iplist.want_old&lt;br /&gt;
	strings.nonote = iplist.nonote&lt;br /&gt;
	strings.months = iplist.months&lt;br /&gt;
	lines:add(strings.header)&lt;br /&gt;
	local upto = lines.n&lt;br /&gt;
	for _, summary in ipairs(make_summaries(iplist)) do&lt;br /&gt;
		show_summary(lines, strings, iplist, summary)&lt;br /&gt;
	end&lt;br /&gt;
	lines:add(strings.footer)&lt;br /&gt;
	if upto + 1 == lines.n then&lt;br /&gt;
		-- Show message in the very unlikely event that no results are found.&lt;br /&gt;
		lines:add(&amp;#039;----&amp;#039;)&lt;br /&gt;
		lines:add(&amp;#039;No suitable ranges found; use &amp;lt;code&amp;gt;|results=all&amp;lt;/code&amp;gt; to see all ranges.&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	if strings.notes then&lt;br /&gt;
		lines:add(&amp;#039;&amp;#039;)&lt;br /&gt;
		lines:add(&amp;quot;&amp;#039;&amp;#039;&amp;#039;Notes&amp;#039;&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
		for _, key in ipairs({&amp;#039;range&amp;#039;, &amp;#039;gadget&amp;#039;}) do&lt;br /&gt;
			if strings.notes[key] then&lt;br /&gt;
				lines:add(strings.notes[key])&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_options(args)&lt;br /&gt;
	-- Return table of options from validated args or throw error.&lt;br /&gt;
	local options = {}&lt;br /&gt;
	if not empty(args.comment) then&lt;br /&gt;
		options.comment = args.comment&lt;br /&gt;
	end&lt;br /&gt;
	-- Parameter &amp;#039;months&amp;#039; is only used if &amp;#039;old&amp;#039; is also used.&lt;br /&gt;
	local months = math.floor(tonumber(args.months) or tonumber(args.month) or 1)&lt;br /&gt;
	if months &amp;lt; 1 then&lt;br /&gt;
		months = 1&lt;br /&gt;
	elseif months &amp;gt; 12 then&lt;br /&gt;
		months = 12&lt;br /&gt;
	end&lt;br /&gt;
	options.months = months  -- silently ignore invalid input&lt;br /&gt;
	local allocation&lt;br /&gt;
	if not empty(args.allocation) then&lt;br /&gt;
		allocation = tonumber(args.allocation)&lt;br /&gt;
		if not (allocation and 48 &amp;lt;= allocation and allocation &amp;lt;= 128) then&lt;br /&gt;
			error(&amp;#039;Invalid allocation &amp;quot;&amp;#039; .. args.allocation .. &amp;#039;&amp;quot; (should be 48 to 128; default is 64)&amp;#039;, 0)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local maxresults&lt;br /&gt;
	if not empty(args.results) then&lt;br /&gt;
		if args.results == &amp;#039;all&amp;#039; then&lt;br /&gt;
			options.all = true&lt;br /&gt;
			allocation = allocation or 128&lt;br /&gt;
			maxresults = 1000&lt;br /&gt;
		else&lt;br /&gt;
			maxresults = tonumber(args.results)&lt;br /&gt;
			if not (maxresults and 1 &amp;lt;= maxresults and maxresults &amp;lt;= 100) then&lt;br /&gt;
				error(&amp;#039;Invalid results &amp;quot;&amp;#039; .. args.results .. &amp;#039;&amp;quot; (should be 1 to 100)&amp;#039;, 0)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	options.allocation = allocation or 64&lt;br /&gt;
	options.maxresults = maxresults or 10&lt;br /&gt;
	local keywords = {&lt;br /&gt;
		-- Table of k, v strings.&lt;br /&gt;
		-- If an argument matches k, an option named v is set to true.&lt;br /&gt;
		ok = &amp;#039;noannounce&amp;#039;,&lt;br /&gt;
		old = &amp;#039;want_old&amp;#039;,&lt;br /&gt;
		nolist = &amp;#039;nolist&amp;#039;,&lt;br /&gt;
		nonote = &amp;#039;nonote&amp;#039;,&lt;br /&gt;
		text = &amp;#039;text&amp;#039;,&lt;br /&gt;
	}&lt;br /&gt;
	local want_old&lt;br /&gt;
	for i, arg in ipairs(args) do&lt;br /&gt;
		local flag = keywords[arg:match(&amp;#039;^%s*(%w+)%s*$&amp;#039;)]&lt;br /&gt;
		if flag then&lt;br /&gt;
			options[i] = &amp;#039;skip&amp;#039;&lt;br /&gt;
			options[flag] = true&lt;br /&gt;
			if flag == &amp;#039;want_old&amp;#039; then&lt;br /&gt;
				want_old = true&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if not want_old then&lt;br /&gt;
		options.nonote = true&lt;br /&gt;
	end&lt;br /&gt;
	return options&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function _IPblock(args)&lt;br /&gt;
	-- Process given args; can be called from another module.&lt;br /&gt;
	-- Throw an error if need to report a problem.&lt;br /&gt;
	local options = make_options(args)&lt;br /&gt;
	local v4list, v4omitted = Collection.new(), Collection.new()&lt;br /&gt;
	local v6list, v6omitted = Collection.new(), Collection.new()&lt;br /&gt;
	v4list.ipsize = 32&lt;br /&gt;
	v4list.ipname = &amp;#039;IPv4&amp;#039;&lt;br /&gt;
	v6list.ipsize = 128&lt;br /&gt;
	v6list.ipname = &amp;#039;IPv6&amp;#039;&lt;br /&gt;
	v4list.allocation = 32&lt;br /&gt;
	v6list.allocation = options.allocation&lt;br /&gt;
	if options.all then&lt;br /&gt;
		v4list.minsize = 0&lt;br /&gt;
		v6list.minsize = 0&lt;br /&gt;
	else&lt;br /&gt;
		v4list.minsize = 16  -- cannot block more IPs than /16 for IPv4&lt;br /&gt;
		v6list.minsize = 19  -- or /19 for IPv6 ($wgBlockCIDRLimit)&lt;br /&gt;
	end&lt;br /&gt;
	for _, k in ipairs({&amp;#039;maxresults&amp;#039;, &amp;#039;months&amp;#039;, &amp;#039;want_old&amp;#039;, &amp;#039;nolist&amp;#039;, &amp;#039;nonote&amp;#039;}) do&lt;br /&gt;
		v4list[k] = options[k]&lt;br /&gt;
		v6list[k] = options[k]&lt;br /&gt;
	end&lt;br /&gt;
	if options.text then&lt;br /&gt;
		v4list.want_plain = true&lt;br /&gt;
		v6list.want_plain = true&lt;br /&gt;
	end&lt;br /&gt;
	for i, arg in ipairs(args) do&lt;br /&gt;
		if options[i] ~= &amp;#039;skip&amp;#039; then&lt;br /&gt;
			for line in string.gmatch(arg .. &amp;#039;\n&amp;#039;, &amp;#039;[\t ]*(.-)[\t\r ]*\n&amp;#039;) do&lt;br /&gt;
				-- Skip line if is empty or a comment.&lt;br /&gt;
				if line ~= &amp;#039;&amp;#039; then&lt;br /&gt;
					local comment = options.comment&lt;br /&gt;
					if not (comment and line:sub(1, #comment) == comment) then&lt;br /&gt;
						line = line&lt;br /&gt;
							:gsub(&amp;#039;[Ss]pecial:[Cc]ontrib%w*/&amp;#039;, &amp;#039; &amp;#039;)  -- so input &amp;quot;Special:Contributions/1.2.3.4&amp;quot; works&lt;br /&gt;
							:gsub(&amp;#039;[Tt]alk:&amp;#039;, &amp;#039; &amp;#039;)&lt;br /&gt;
							:gsub(&amp;#039;[Uu]ser:&amp;#039;, &amp;#039; &amp;#039;)&lt;br /&gt;
							:gsub(&amp;#039;[!&amp;quot;#&amp;amp;\&amp;#039;()+,%-;&amp;lt;=&amp;gt;?[%]_{|}]&amp;#039;, &amp;#039; &amp;#039;)  -- replace accepted delimiters with a space&lt;br /&gt;
							:gsub(&amp;#039;\226\128\142&amp;#039;, &amp;#039; &amp;#039;)  -- replace LTR marks (U+200E)&lt;br /&gt;
						extract_ipv4(v4list, v4omitted, line)&lt;br /&gt;
						extract_ipv6(v6list, v6omitted, line)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if v4list.n &amp;lt; 1 and v6list.n &amp;lt; 1 then&lt;br /&gt;
		error(&amp;#039;No valid IPv4 or IPv6 address in arguments&amp;#039;, 0)&lt;br /&gt;
	end&lt;br /&gt;
	local lines = Collection.new()&lt;br /&gt;
	if not options.noannounce then&lt;br /&gt;
		-- 1: Commented out April 2016 as expired.&lt;br /&gt;
		-- 1: lines:add(&amp;quot;&amp;#039;&amp;#039;&amp;#039;Please see [[Template talk:Blockcalc#Version February 2016|this announcement]].&amp;#039;&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
		-- 2: Commented out December 2017 as expired.&lt;br /&gt;
		-- 2: lines:add(&amp;quot;&amp;#039;&amp;#039;&amp;#039;By default, links now use [[Special:Contributions]] per [[Template talk:IP range calculator#Version November 2017|this announcement]].&amp;#039;&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	process_ips(lines, v4list, v4omitted)&lt;br /&gt;
	process_ips(lines, v6list, v6omitted)&lt;br /&gt;
	return lines:join(&amp;#039;\n&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function IPblock(frame)&lt;br /&gt;
	-- Return wikitext to display the smallest IPv4 or IPv6 CIDR range that&lt;br /&gt;
	-- covers each address given in the arguments, or return error text.&lt;br /&gt;
	-- Input can have any mixture of IPs; IPv4 and IPv6 are processed separately.&lt;br /&gt;
	local ok, msg = pcall(_IPblock, frame:getParent().args)&lt;br /&gt;
	if ok then&lt;br /&gt;
		return msg&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;&amp;lt;strong class=&amp;quot;error&amp;quot;&amp;gt;Error: &amp;#039; .. msg .. &amp;#039;&amp;lt;/strong&amp;gt;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function sha1(frame)&lt;br /&gt;
	-- Return SHA-1 hash of first parameter.&lt;br /&gt;
	-- This is for use at [[User:Johnuniq/Security]] to generate hash of a password.&lt;br /&gt;
	local text = (frame.args[1] or &amp;#039;&amp;#039;):match(&amp;quot;^%s*(.-)%s*$&amp;quot;)&lt;br /&gt;
	if text ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		return &amp;#039;SHA-1 hash after removing any leading or trailing whitespace is &amp;lt;code&amp;gt;&amp;#039; .. mw.hash.hashValue(&amp;#039;sha1&amp;#039;, text) .. &amp;#039;&amp;lt;/code&amp;gt;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;#039;Usage: &amp;lt;code&amp;gt;&amp;amp;#123;{#invoke:IPblock|sha1|text}&amp;amp;#125;&amp;lt;/code&amp;gt; to display the SHA-1 hash of &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt;.&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
	IPblock = IPblock,&lt;br /&gt;
	_IPblock = _IPblock,&lt;br /&gt;
	sha1 = sha1,&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Wikipedia&gt;Johnuniq</name></author>
	</entry>
</feed>