<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://stockhub.co/index.php?action=history&amp;feed=atom&amp;title=Module%3ASensitive_IP_addresses%2FAPI</id>
	<title>Module:Sensitive IP addresses/API - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://stockhub.co/index.php?action=history&amp;feed=atom&amp;title=Module%3ASensitive_IP_addresses%2FAPI"/>
	<link rel="alternate" type="text/html" href="https://stockhub.co/index.php?title=Module:Sensitive_IP_addresses/API&amp;action=history"/>
	<updated>2026-04-21T12:55:36Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://stockhub.co/index.php?title=Module:Sensitive_IP_addresses/API&amp;diff=146708&amp;oldid=prev</id>
		<title>imported&gt;Mr. Stradivarius: Protected &quot;Module:Sensitive IP addresses/API&quot;: High-risk Lua module: used in the MediaWiki interface, e.g. MediaWiki:Blockiptext via Template:Sensitive IP addresses ([Edit=Require administrator access] (indefinite...</title>
		<link rel="alternate" type="text/html" href="https://stockhub.co/index.php?title=Module:Sensitive_IP_addresses/API&amp;diff=146708&amp;oldid=prev"/>
		<updated>2016-11-23T09:27:57Z</updated>

		<summary type="html">&lt;p&gt;Protected &amp;quot;&lt;a href=&quot;/research/Module:Sensitive_IP_addresses/API&quot; title=&quot;Module:Sensitive IP addresses/API&quot;&gt;Module:Sensitive IP addresses/API&lt;/a&gt;&amp;quot;: &lt;a href=&quot;/index.php?title=WP:High-risk_templates&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;WP:High-risk templates (page does not exist)&quot;&gt;High-risk Lua module&lt;/a&gt;: used in the MediaWiki interface, e.g. &lt;a href=&quot;/research/MediaWiki:Blockiptext&quot; title=&quot;MediaWiki:Blockiptext&quot;&gt;MediaWiki:Blockiptext&lt;/a&gt; via &lt;a href=&quot;/index.php?title=Template:Sensitive_IP_addresses&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;Template:Sensitive IP addresses (page does not exist)&quot;&gt;Template:Sensitive IP addresses&lt;/a&gt; ([Edit=Require administrator access] (indefinite...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- This module provides functions for handling sensitive IP addresses.&lt;br /&gt;
&lt;br /&gt;
-- Load modules&lt;br /&gt;
local mIP = require(&amp;#039;Module:IP&amp;#039;)&lt;br /&gt;
local IPAddress = mIP.IPAddress&lt;br /&gt;
local Subnet = mIP.Subnet&lt;br /&gt;
local IPv4Collection = mIP.IPv4Collection&lt;br /&gt;
local IPv6Collection = mIP.IPv6Collection&lt;br /&gt;
&lt;br /&gt;
-- Lazily load the jf-JSON module&lt;br /&gt;
local JSON&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local function deepCopy(val)&lt;br /&gt;
	-- Make a deep copy of a value, but don&amp;#039;t worry about self-references or&lt;br /&gt;
	-- metatables as mw.clone does. If a table in val has a self-reference,&lt;br /&gt;
	-- you will get an infinite loop, so don&amp;#039;t do that.&lt;br /&gt;
	if type(val) == &amp;#039;table&amp;#039; then&lt;br /&gt;
		local ret = {}&lt;br /&gt;
		for k, v in pairs(val) do&lt;br /&gt;
			ret[k] = deepCopy(v)&lt;br /&gt;
		end&lt;br /&gt;
		return ret&lt;br /&gt;
	else&lt;br /&gt;
		return val&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function deepCopyInto(source, dest)&lt;br /&gt;
	-- Do a deep copy of a source table into a destination table, ignoring&lt;br /&gt;
	-- self-references and metatables. If a table in source has a self-reference&lt;br /&gt;
	-- you will get an infinite loop.&lt;br /&gt;
	for k, v in pairs(source) do&lt;br /&gt;
		if type(v) == &amp;#039;table&amp;#039; then&lt;br /&gt;
			dest[k] = {}&lt;br /&gt;
			deepCopyInto(v, dest[k])&lt;br /&gt;
		else&lt;br /&gt;
			dest[k] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function removeDuplicates(t)&lt;br /&gt;
	-- Return a copy of an array with duplicate values removed.&lt;br /&gt;
	local keys, ret = {}, {}&lt;br /&gt;
	for i, v in ipairs(t) do&lt;br /&gt;
		if not keys[v] then&lt;br /&gt;
			table.insert(ret, v)&lt;br /&gt;
			keys[v] = true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- SensitiveEntity class&lt;br /&gt;
-- A country or organization for which blocks must be handled with care.&lt;br /&gt;
-- Media organizations may inspect block messages for IP addresses and ranges&lt;br /&gt;
-- belonging to these entities and those messages may end up in the press.&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local SensitiveEntity = {}&lt;br /&gt;
SensitiveEntity.__index = SensitiveEntity&lt;br /&gt;
&lt;br /&gt;
SensitiveEntity.reasons = {&lt;br /&gt;
	-- The reasons that an entity may be sensitive. Used to verify data in&lt;br /&gt;
	-- Module:Sensitive IP addresses/list.&lt;br /&gt;
	political = true,&lt;br /&gt;
	technical = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
do&lt;br /&gt;
	-- Private methods&lt;br /&gt;
	local function addRanges(self, key, collectionConstructor, ranges)&lt;br /&gt;
		if ranges and ranges[1] then&lt;br /&gt;
			self[key] = collectionConstructor()&lt;br /&gt;
			for i, range in ipairs(ranges) do&lt;br /&gt;
				self[key]:addSubnet(Subnet.new(range))&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Constructor&lt;br /&gt;
	function SensitiveEntity.new(data)&lt;br /&gt;
		local self = setmetatable({}, SensitiveEntity)&lt;br /&gt;
&lt;br /&gt;
		-- Set data&lt;br /&gt;
		self.data = data&lt;br /&gt;
		addRanges(self, &amp;#039;v4Collection&amp;#039;, IPv4Collection.new, data.ipv4Ranges)&lt;br /&gt;
		addRanges(self, &amp;#039;v6Collection&amp;#039;, IPv6Collection.new, data.ipv6Ranges)&lt;br /&gt;
&lt;br /&gt;
		return self&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function SensitiveEntity:matchesIPOrRange(str)&lt;br /&gt;
	-- Returns true, matchObj, queryObj if there is a match for the IP address&lt;br /&gt;
	-- string or CIDR range str in the sensitive entity. Returns false&lt;br /&gt;
	-- otherwise. matchObj is the Subnet object that was matched, and queryObj&lt;br /&gt;
	-- is the IPAddress or Subnet object corresponding to the input string.&lt;br /&gt;
&lt;br /&gt;
	-- Get the IPAddress or Subnet object for str&lt;br /&gt;
	local isIP, isSubnet, obj&lt;br /&gt;
	isIP, obj = pcall(IPAddress.new, str)&lt;br /&gt;
	if isIP and not obj then&lt;br /&gt;
		isIP = false&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if not isIP then&lt;br /&gt;
		isSubnet, obj = pcall(Subnet.new, str)&lt;br /&gt;
		if not isSubnet or not obj then&lt;br /&gt;
			error(string.format(&lt;br /&gt;
				&amp;quot;&amp;#039;%s&amp;#039; is not a valid IP address or CIDR string&amp;quot;,&lt;br /&gt;
				str&lt;br /&gt;
			), 2)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Try matching the object to the appropriate collection&lt;br /&gt;
	local function isInCollection(collection, obj, isIP)&lt;br /&gt;
		if isIP then&lt;br /&gt;
			if collection then&lt;br /&gt;
				local isMatch, matchObj = collection:containsIP(obj)&lt;br /&gt;
				return isMatch, matchObj, obj&lt;br /&gt;
			else&lt;br /&gt;
				return false&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			if collection then&lt;br /&gt;
				local isMatch, matchObj = collection:overlapsSubnet(obj)&lt;br /&gt;
				return isMatch, matchObj, obj&lt;br /&gt;
			else&lt;br /&gt;
				return false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if obj:isIPv4() then&lt;br /&gt;
		return isInCollection(self.v4Collection, obj, isIP)&lt;br /&gt;
	else&lt;br /&gt;
		return isInCollection(self.v6Collection, obj, isIP)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Sensitive IP API&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- This API is used by external tools and gadgets, so it should be kept&lt;br /&gt;
-- backwards-compatible. Clients query the API with a query table, and the&lt;br /&gt;
-- API returns a response table. The response table is available as a Lua table&lt;br /&gt;
-- for other Lua modules, and as JSON for external clients.&lt;br /&gt;
&lt;br /&gt;
-- Example query tables:&lt;br /&gt;
--&lt;br /&gt;
-- Query IP addresses and ranges:&lt;br /&gt;
-- {&lt;br /&gt;
-- 	test = {&amp;#039;1.2.3.4&amp;#039;, &amp;#039;4.5.6.0/24&amp;#039;, &amp;#039;2001:db8::ff00:12:3456&amp;#039;, &amp;#039;2001:db8::ff00:12:0/112&amp;#039;},&lt;br /&gt;
-- }&lt;br /&gt;
--&lt;br /&gt;
-- Query specific entities:&lt;br /&gt;
-- {&lt;br /&gt;
-- 	entities = {&amp;#039;ussenate&amp;#039;, &amp;#039;ushr&amp;#039;}&lt;br /&gt;
-- }&lt;br /&gt;
--&lt;br /&gt;
-- Query all entities:&lt;br /&gt;
-- {&lt;br /&gt;
-- 	entities = {&amp;#039;all&amp;#039;}&lt;br /&gt;
-- }&lt;br /&gt;
--&lt;br /&gt;
-- Query all entities and format the result as a JSON string:&lt;br /&gt;
-- {&lt;br /&gt;
-- 	entities = {&amp;#039;all&amp;#039;},&lt;br /&gt;
--  format = &amp;#039;json&amp;#039;&lt;br /&gt;
-- }&lt;br /&gt;
--&lt;br /&gt;
-- Combined query:&lt;br /&gt;
-- {&lt;br /&gt;
-- 	test = {&amp;#039;1.2.3.4&amp;#039;, &amp;#039;4.5.6.0/24&amp;#039;, &amp;#039;2001:db8::ff00:12:3456&amp;#039;, &amp;#039;2001:db8::ff00:12:0/112&amp;#039;},&lt;br /&gt;
-- 	entities = {&amp;#039;ussenate&amp;#039;, &amp;#039;ushr&amp;#039;}&lt;br /&gt;
-- }&lt;br /&gt;
&lt;br /&gt;
-- Example response:&lt;br /&gt;
--&lt;br /&gt;
-- {&lt;br /&gt;
--     sensitiveips = {&lt;br /&gt;
--         matches = {&lt;br /&gt;
--             {&lt;br /&gt;
--                 ip = &amp;#039;1.2.3.4&amp;#039;,&lt;br /&gt;
--                 type = &amp;#039;ip&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;ip-version&amp;#039;] = &amp;#039;IPv4&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;matches-range&amp;#039;] = &amp;#039;1.2.3.0/24&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;entity-id&amp;#039;] = &amp;#039;entityid&amp;#039;&lt;br /&gt;
--             },&lt;br /&gt;
--             {&lt;br /&gt;
--                 range = &amp;#039;4.5.6.0/24&amp;#039;,&lt;br /&gt;
--                 type = &amp;#039;range&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;ip-version&amp;#039;] = &amp;#039;IPv4&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;matches-range&amp;#039;] = &amp;#039;4.5.0.0/16&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;entity-id&amp;#039;] = &amp;#039;entityid&amp;#039;&lt;br /&gt;
--             }&lt;br /&gt;
--         },&lt;br /&gt;
--         [&amp;#039;matched-ranges&amp;#039;] = {&lt;br /&gt;
--             [&amp;#039;1.2.3.0/24&amp;#039;] = {&lt;br /&gt;
--                 range = &amp;#039;1.2.3.0/24&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;ip-version&amp;#039;] = &amp;#039;IPv4&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;entity-id&amp;#039;] = &amp;#039;entityid&amp;#039;&lt;br /&gt;
--             },&lt;br /&gt;
--             [&amp;#039;4.5.0.0/16&amp;#039;] = {&lt;br /&gt;
--                 range = &amp;#039;4.5.0.0/16&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;ip-version&amp;#039;] = &amp;#039;IPv4&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;entity-id&amp;#039;] = &amp;#039;entityid&amp;#039;&lt;br /&gt;
--             }&lt;br /&gt;
--         },&lt;br /&gt;
--         entities = {&lt;br /&gt;
--             [&amp;#039;entityid&amp;#039;] = {&lt;br /&gt;
--                 id = &amp;#039;entityid&amp;#039;,&lt;br /&gt;
--                 name = &amp;#039;The entity name&amp;#039;,&lt;br /&gt;
--                 description = &amp;#039;A description of the entity&amp;#039;,&lt;br /&gt;
--                 [&amp;#039;ipv4-ranges&amp;#039;] = {&lt;br /&gt;
--                     &amp;#039;1.2.3.0/24&amp;#039;,&lt;br /&gt;
--                     &amp;#039;4.5.0.0/16&amp;#039;&lt;br /&gt;
--                     &amp;#039;6.7.0.0/16&amp;#039;&lt;br /&gt;
--                 },&lt;br /&gt;
--                 [&amp;#039;ipv6-ranges&amp;#039;] = {&lt;br /&gt;
--                     &amp;#039;2001:db8::ff00:12:0/112&amp;#039;&lt;br /&gt;
--                 },&lt;br /&gt;
--                 notes = &amp;#039;Notes about the entity or its ranges&amp;#039;&lt;br /&gt;
--             }&lt;br /&gt;
--         }&lt;br /&gt;
--         [&amp;#039;entity-ids&amp;#039;] = {&lt;br /&gt;
--             &amp;#039;entityid&amp;#039;&lt;br /&gt;
--         }&lt;br /&gt;
--     }&lt;br /&gt;
-- }&lt;br /&gt;
--&lt;br /&gt;
-- Response with errors:&lt;br /&gt;
--&lt;br /&gt;
-- {&lt;br /&gt;
--     error = {&lt;br /&gt;
--         code = &amp;#039;example-error&amp;#039;,&lt;br /&gt;
--         info = &amp;#039;There was an error&amp;#039;,&lt;br /&gt;
--         [&amp;#039;*&amp;#039;] = &amp;#039;See https://en.wikipedia.org/wiki/Module:Sensitive_IP_addresses for API usage&amp;#039;&lt;br /&gt;
--     }&lt;br /&gt;
-- }&lt;br /&gt;
&lt;br /&gt;
local function query(options)&lt;br /&gt;
	-- Make entity objects&lt;br /&gt;
	local entities, entityIndexes = {}, {}&lt;br /&gt;
	local data = mw.loadData(&amp;#039;Module:Sensitive IP addresses/list&amp;#039;)&lt;br /&gt;
	for i, entityData in ipairs(data) do&lt;br /&gt;
		entities[entityData.id] = SensitiveEntity.new(entityData)&lt;br /&gt;
		entityIndexes[entityData.id] = i -- Keep track of the original order&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local function makeError(code, info, format)&lt;br /&gt;
		local ret = {[&amp;#039;error&amp;#039;] = {&lt;br /&gt;
			code = code,&lt;br /&gt;
			info = info,&lt;br /&gt;
			[&amp;#039;*&amp;#039;] = &amp;#039;See https://en.wikipedia.org/wiki/Module:Sensitive_IP_addresses/API for API usage&amp;#039;,&lt;br /&gt;
		}}&lt;br /&gt;
		if format == &amp;#039;json&amp;#039; then&lt;br /&gt;
			return mw.text.jsonEncode(ret)&lt;br /&gt;
		else&lt;br /&gt;
			return ret&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Construct result&lt;br /&gt;
	local result = {&lt;br /&gt;
		matches = {},&lt;br /&gt;
		[&amp;#039;matched-ranges&amp;#039;] = {},&lt;br /&gt;
		entities = {},&lt;br /&gt;
		[&amp;#039;entity-ids&amp;#039;] = {}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if type(options) ~= &amp;#039;table&amp;#039; then&lt;br /&gt;
		return makeError(&lt;br /&gt;
			&amp;#039;sipa-options-type-error&amp;#039;,&lt;br /&gt;
			string.format(&lt;br /&gt;
				&amp;quot;type error in argument #1 of &amp;#039;query&amp;#039; (expected table, received %s)&amp;quot;,&lt;br /&gt;
				type(options)&lt;br /&gt;
			)&lt;br /&gt;
		)&lt;br /&gt;
	elseif not options.test and not options.entities then&lt;br /&gt;
		return makeError(&lt;br /&gt;
			&amp;#039;sipa-blank-options&amp;#039;,&lt;br /&gt;
			&amp;quot;the options table didn&amp;#039;t contain a &amp;#039;test&amp;#039; or an &amp;#039;entities&amp;#039; key&amp;quot;,&lt;br /&gt;
			options.format&lt;br /&gt;
		)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if options.test then&lt;br /&gt;
		if type(options.test) ~= &amp;#039;table&amp;#039; then&lt;br /&gt;
			return makeError(&lt;br /&gt;
				&amp;#039;sipa-test-type-error&amp;#039;,&lt;br /&gt;
				string.format(&lt;br /&gt;
					&amp;quot;&amp;#039;test&amp;#039; options key was type %s (expected table)&amp;quot;,&lt;br /&gt;
					type(options.test)&lt;br /&gt;
				),&lt;br /&gt;
				options.format&lt;br /&gt;
			)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		for i, testString in ipairs(options.test) do&lt;br /&gt;
			if type(testString) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
				return makeError(&lt;br /&gt;
					&amp;#039;sipa-test-string-type-error&amp;#039;,&lt;br /&gt;
					string.format(&lt;br /&gt;
						&amp;quot;type error in item #%d in the &amp;#039;test&amp;#039; array (expected string, received %s)&amp;quot;,&lt;br /&gt;
						i,&lt;br /&gt;
						type(testString)&lt;br /&gt;
					),&lt;br /&gt;
					options.format&lt;br /&gt;
				)&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
			for k, entity in pairs(entities) do&lt;br /&gt;
				-- Try to match the range with the current sensitive entity.&lt;br /&gt;
				local success, isMatch, matchObj, queryObj = pcall(&lt;br /&gt;
					entity.matchesIPOrRange,&lt;br /&gt;
					entity,&lt;br /&gt;
					testString&lt;br /&gt;
				)&lt;br /&gt;
				if not success then&lt;br /&gt;
					-- The string was invalid.&lt;br /&gt;
					return makeError(&lt;br /&gt;
						&amp;#039;sipa-invalid-test-string&amp;#039;,&lt;br /&gt;
						string.format(&lt;br /&gt;
							&amp;quot;test string #%d &amp;#039;%s&amp;#039; was not a valid IP address or CIDR string&amp;quot;,&lt;br /&gt;
							i,&lt;br /&gt;
							testString&lt;br /&gt;
						),&lt;br /&gt;
						options.format&lt;br /&gt;
					)&lt;br /&gt;
				end&lt;br /&gt;
				if isMatch then&lt;br /&gt;
					-- The string was a sensitive IP address or subnet.&lt;br /&gt;
&lt;br /&gt;
					-- Add match data&lt;br /&gt;
					local match = {}&lt;br /&gt;
					-- Quick and dirty hack to find if queryObj is an IPAddress object.&lt;br /&gt;
					local isIP = queryObj.getNextIP ~= nil and queryObj.isInSubnet ~= nil&lt;br /&gt;
					if isIP then&lt;br /&gt;
						match.type = &amp;#039;ip&amp;#039;&lt;br /&gt;
						match.ip = tostring(queryObj)&lt;br /&gt;
					else&lt;br /&gt;
						match.type = &amp;#039;range&amp;#039;&lt;br /&gt;
						match.range = tostring(queryObj)&lt;br /&gt;
					end&lt;br /&gt;
					match[&amp;#039;ip-version&amp;#039;] = queryObj:getVersion()&lt;br /&gt;
					match[&amp;#039;matches-range&amp;#039;] = matchObj:getCIDR()&lt;br /&gt;
					match[&amp;#039;entity-id&amp;#039;] = entity.data.id&lt;br /&gt;
					table.insert(result.matches, match)&lt;br /&gt;
&lt;br /&gt;
					-- Add the matched range data.&lt;br /&gt;
					result[&amp;#039;matched-ranges&amp;#039;][match[&amp;#039;matches-range&amp;#039;]] = {&lt;br /&gt;
						range = match[&amp;#039;matches-range&amp;#039;],&lt;br /&gt;
						[&amp;#039;ip-version&amp;#039;] = match[&amp;#039;ip-version&amp;#039;],&lt;br /&gt;
						[&amp;#039;entity-id&amp;#039;] = match[&amp;#039;entity-id&amp;#039;],&lt;br /&gt;
					}&lt;br /&gt;
&lt;br /&gt;
					-- Add the entity data for the entity we matched.&lt;br /&gt;
					result.entities[match[&amp;#039;entity-id&amp;#039;]] = deepCopy(&lt;br /&gt;
						entities[match[&amp;#039;entity-id&amp;#039;]].data&lt;br /&gt;
					)&lt;br /&gt;
&lt;br /&gt;
					-- Add the entity ID for the entity we matched.&lt;br /&gt;
					table.insert(result[&amp;#039;entity-ids&amp;#039;], match[&amp;#039;entity-id&amp;#039;])&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Add entity data requested explicitly.&lt;br /&gt;
	if options.entities then&lt;br /&gt;
		if type(options.entities) ~= &amp;#039;table&amp;#039; then&lt;br /&gt;
			return makeError(&lt;br /&gt;
				&amp;#039;sipa-entities-type-error&amp;#039;,&lt;br /&gt;
				string.format(&lt;br /&gt;
					&amp;quot;&amp;#039;entities&amp;#039; options key was type %s (expected table)&amp;quot;,&lt;br /&gt;
					type(options.test)&lt;br /&gt;
				),&lt;br /&gt;
				options.format&lt;br /&gt;
			)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		-- Check the type of all the entity strings, and check if &amp;#039;all&amp;#039; has&lt;br /&gt;
		-- been specified.&lt;br /&gt;
		local isAll = false&lt;br /&gt;
		for i, entityString in ipairs(options.entities) do&lt;br /&gt;
			if type(entityString) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
				return makeError(&lt;br /&gt;
					&amp;#039;sipa-entity-string-type-error&amp;#039;,&lt;br /&gt;
					string.format(&lt;br /&gt;
						&amp;quot;type error in item #%d in the &amp;#039;entities&amp;#039; array (expected string, received %s)&amp;quot;,&lt;br /&gt;
						i,&lt;br /&gt;
						type(entityString)&lt;br /&gt;
					),&lt;br /&gt;
					options.format&lt;br /&gt;
				)&lt;br /&gt;
			end&lt;br /&gt;
			if entityString == &amp;#039;all&amp;#039; then&lt;br /&gt;
				isAll = true&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		if isAll then&lt;br /&gt;
			-- Add all the entity data.&lt;br /&gt;
			-- As the final result will contain all the entity data, we can&lt;br /&gt;
			-- just create the entities and entity-ids subtables from scratch&lt;br /&gt;
			-- without worrying about what any existing values might be.&lt;br /&gt;
			result.entities = {}&lt;br /&gt;
			result[&amp;#039;entity-ids&amp;#039;] = {}&lt;br /&gt;
			for i, entityData in ipairs(data) do&lt;br /&gt;
				result.entities[entityData.id] = deepCopy(entityData)&lt;br /&gt;
				result[&amp;#039;entity-ids&amp;#039;][i] = entityData.id&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			-- Add data for the entities specified.&lt;br /&gt;
			-- Insert the entity and entity-id subtables if they aren&amp;#039;t already&lt;br /&gt;
			-- present.&lt;br /&gt;
			for i, entityString in ipairs(options.entities) do&lt;br /&gt;
				if entities[entityString] then&lt;br /&gt;
					result.entities[entityString] = deepCopy(&lt;br /&gt;
						entities[entityString].data&lt;br /&gt;
					)&lt;br /&gt;
					table.insert(result[&amp;#039;entity-ids&amp;#039;], entityString)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			result[&amp;#039;entity-ids&amp;#039;] = removeDuplicates(result[&amp;#039;entity-ids&amp;#039;])&lt;br /&gt;
			table.sort(result[&amp;#039;entity-ids&amp;#039;], function(s1, s2)&lt;br /&gt;
				return entityIndexes[s1] &amp;lt; entityIndexes[s2]&lt;br /&gt;
			end)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Add any missing reason fields from entities.&lt;br /&gt;
	for id, entityData in pairs(result.entities) do&lt;br /&gt;
		entityData.reason = entityData.reason or &amp;#039;political&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Wrap the result in an outer layer like the MediaWiki Action API does.&lt;br /&gt;
	result = {sensitiveips = result}&lt;br /&gt;
&lt;br /&gt;
	if options.format == &amp;#039;json&amp;#039; then&lt;br /&gt;
		-- Load jf-JSON&lt;br /&gt;
		JSON = JSON or require(&amp;#039;Module:jf-JSON&amp;#039;)&lt;br /&gt;
		JSON.strictTypes = true -- Necessary for correct blank-object encoding&lt;br /&gt;
		-- Decode a skeleton result JSON string. This ensures that blank objects&lt;br /&gt;
		-- are re-encoded as blank objects and not as blank arrays.&lt;br /&gt;
		local jsonResult = JSON:decode([[{&amp;quot;sensitiveips&amp;quot;: {&lt;br /&gt;
			&amp;quot;matches&amp;quot;: [],&lt;br /&gt;
			&amp;quot;matched-ranges&amp;quot;: {},&lt;br /&gt;
			&amp;quot;entities&amp;quot;: {},&lt;br /&gt;
			&amp;quot;entity-ids&amp;quot;: []&lt;br /&gt;
		}}]])&lt;br /&gt;
		for i, key in ipairs{&amp;#039;matches&amp;#039;, &amp;#039;matched-ranges&amp;#039;, &amp;#039;entities&amp;#039;, &amp;#039;entity-ids&amp;#039;} do&lt;br /&gt;
			deepCopyInto(result.sensitiveips[key], jsonResult.sensitiveips[key])&lt;br /&gt;
		end&lt;br /&gt;
		return JSON:encode(jsonResult)&lt;br /&gt;
	elseif options.format == nil or options.format == &amp;#039;lua&amp;#039; then&lt;br /&gt;
		return result&lt;br /&gt;
	elseif type(options.format) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
		return makeError(&lt;br /&gt;
			&amp;#039;sipa-format-type-error&amp;#039;,&lt;br /&gt;
			string.format(&lt;br /&gt;
				&amp;quot;&amp;#039;format&amp;#039; options key was type %s (expected string or nil)&amp;quot;,&lt;br /&gt;
				type(options.format)&lt;br /&gt;
			)&lt;br /&gt;
		)&lt;br /&gt;
	else&lt;br /&gt;
		return makeError(&lt;br /&gt;
			&amp;#039;sipa-invalid-format&amp;#039;,&lt;br /&gt;
			string.format(&lt;br /&gt;
				&amp;quot;invalid format &amp;#039;%s&amp;#039; (expected &amp;#039;json&amp;#039; or &amp;#039;lua&amp;#039;)&amp;quot;,&lt;br /&gt;
				type(options.format)&lt;br /&gt;
			)&lt;br /&gt;
		)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Exports&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
function p._isValidSensitivityReason(s)&lt;br /&gt;
	-- Return true if s is a valid sensitivity reason; otherwise return false.&lt;br /&gt;
	return s ~= nil and SensitiveEntity.reasons[s] ~= nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p._getSensitivityReasons(separator, conjunction)&lt;br /&gt;
	-- Return an string of valid sensitivity reasons, ordered alphabetically.&lt;br /&gt;
	-- The reasons are separated by an optional separator; if conjunction is&lt;br /&gt;
	-- specified it is used instead of the last separator, as in&lt;br /&gt;
	-- mw.text.listToText.&lt;br /&gt;
&lt;br /&gt;
	-- Get an array of valid sensitivity reasons.&lt;br /&gt;
	local reasons = {}&lt;br /&gt;
	for reason in pairs(SensitiveEntity.reasons) do&lt;br /&gt;
		reasons[#reasons + 1] = reason&lt;br /&gt;
	end&lt;br /&gt;
	table.sort(reasons)&lt;br /&gt;
&lt;br /&gt;
	-- Convert arguments if we are being called from wikitext.&lt;br /&gt;
	if type(separator) == &amp;#039;table&amp;#039; and type(separator.getParent) == &amp;#039;function&amp;#039; then&lt;br /&gt;
		-- separator is a frame object&lt;br /&gt;
		local frame = separator&lt;br /&gt;
		separator = frame.args[1]&lt;br /&gt;
		conjunction = frame.args[2]&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Return a formatted string&lt;br /&gt;
	return mw.text.listToText(reasons, separator, conjunction)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Export the API query function&lt;br /&gt;
p.query = query&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>imported&gt;Mr. Stradivarius</name></author>
	</entry>
</feed>