Module:IPblock/sandbox
Appearance
From Wikipedia, the free encyclopedia
You might want to create a documentation page for this Scribunto module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Add categories to the /doc subpage. Subpages of this module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Add categories to the /doc subpage. Subpages of this module.
-- Calculate the minimum-sized blocks of IP addresses that cover each -- IPv4 or IPv6 address entered in the arguments. localbit32=require('bit32') localCollection-- a table to hold items Collection={ add=function(self,item) ifitem~=nilthen self.n=self.n+1 self[self.n]=item end end, join=function(self,sep) returntable.concat(self,sep) end, remove=function(self,pos) ifself.n>0and(pos==nilor(0<posandpos<=self.n))then self.n=self.n-1 returntable.remove(self,pos) end end, sort=function(self,comp) table.sort(self,comp) end, new=function() returnsetmetatable({n=0},Collection) end } Collection.__index=Collection localfunctionempty(text) -- Return true if text is nil or empty (assuming a string). returntext==nilortext=='' end localtimestamps={}-- cache localfunctionstart_date(code,months) -- Return a timestamp string for a URL to list user contributions -- on and after the returned date. -- The code specifies the wanted format. -- For this module, only recent contributions are wanted, so the -- timestamp is today's date less the given number of months (1 to 12). localkey=code..months ifnottimestamps[key]then localdate=os.date('!*t')-- today's UTC date localy,m,d=date.year,date.month,date.day-- full year, month (1-12), day (1-31) m=m-months ifm<=0then m=m+12 y=y-1 end locallimit=m==2and28or30 ifd>limitthen d=limit-- good enough to ensure date is valid end timestamps['y-m-d'..months]=string.format('%d-%02d-%02d',y,m,d) timestamps['ymdHMS'..months]=string.format('%d%02d%02d000000',y,m,d) end returntimestamps[key]or'' end localnote_text={ range='*Links for ranges show the contributions in the previous %s.', gadget=[=[ *<span id="need-gadget"></span>Contributions links for IPv6 ranges need the "<span style="color:green;">Allow /16, /24 and /27 – /32 CIDR ranges on Special:Contributions forms</span>" gadget enabled in [[Special:Preferences#mw-prefsection-gadgets|Special:Preferences]], and scripting enabled in the browser.]=], } localfunctionmake_note(strings,key) -- Record the fact that a particular note is needed, and return -- wikitext for a link to the note or '' if no link needed. ifnotstrings.nonotethen strings.notes=strings.notesor{} ifnotstrings.notes[key]then ifkey=='gadget'then strings.notes[key]=note_text[key] elseifkey=='range'then localwhen='month' ifstrings.months>1then when=strings.months..' months' end strings.notes[key]=string.format(note_text.range,when) else error('make_note: unexpected key') end end ifkey=='gadget'then return' [[#need-gadget|<sup>[note]</sup>]]' end end return'' end localfunctiondescribe_total(total,isalloc) -- Return text describing given number of addresses or /64 allocations. iftotal<=9999then -- Can have fractions if total is the number of /64 allocations. iftotal<9then return(string.format('%.1f',total):gsub('%.0$','')) end returnstring.format('%.0f',total) end ifnotisallocthen localalloc=2^64 iftotal>=allocthen returndescribe_total(total/alloc,true)..' /64' end end total=total/1024 localsuffix='K' iftotal>=1024then total=total/1024 suffix='M' iftotal>=1024then total=total/1024 suffix='G' iftotal>64then return'>64G' end end end returnstring.format('%.0f',total)..suffix end localfunctiondescribe_size(ipsize,size) -- Return text describing how many IPs are in a range with size = prefix length. localfunctionnumtext(n) ifn<=16then returntostring(2^n) end ifn<=19then returntostring(2^(n-10))..'K' end ifn<=29then returntostring(2^(n-20))..'M' end ifn<=36then returntostring(2^(n-30))..'G' end return'>64G' end localhost=ipsize-size ifhost<=32then -- IPv4 or IPv6. returnnumtext(host) end -- Must be IPv6. ifhost<=64then locals=({ [64]='1',[63]='50%',[62]='25%',[61]='12%', [60]='6%',[59]='3%',[58]='2%' })[host]or'<1%' returns..' /64' end -- IPv6 with size < 64. returnnumtext(host-64)..' /64' end localfunctionipv6_string(ip) -- Return a string equivalent to the given IPv6 address. localz1,z2-- indices of run of zeros to be displayed as "::" localzstart,zcount fori=1,9do -- Find left-most occurrence of longest run of two or more zeros. ifi<9andip[i]==0then ifzstartthen zcount=zcount+1 else zstart=i zcount=1 end else ifzcountandzcount>1then ifnotz1orzcount>z2-z1+1then z1=zstart z2=zstart+zcount-1 end end zstart=nil zcount=nil end end localparts=Collection.new() fori=1,8do ifz1andz1<=iandi<=z2then ifi==z1then ifz1==1orz2==8then ifz1==1andz2==8then return'::' end parts:add(':') else parts:add('') end end else parts:add(string.format('%x',ip[i])) end end returnparts:join(':') end localfunctionip_string(ip) -- Return a string equivalent to given IP address (IPv4 or IPv6). ifip.n==2then -- IPv4. localparts={} fori=1,2do localw=ip[i] localq=i==1and1or3 parts[q]=math.floor(w/256) parts[q+1]=w%256 end returntable.concat(parts,'.') end returnipv6_string(ip) end -- Metatable for some operations on IP addresses. localipmt={ __eq=function(lhs,rhs) -- Return true if values in numbered tables match. iflhs.n==rhs.nthen fori=1,lhs.ndo iflhs[i]~=rhs[i]then returnfalse end end returntrue end returnfalse end, __lt=function(lhs,rhs) -- Return true if lhs < rhs; for sort. iflhs.n==rhs.nthen fori=1,lhs.ndo iflhs[i]~=rhs[i]then returnlhs[i]<rhs[i] end end returnfalse end returnlhs.n<rhs.n-- sort IPv4 before IPv6, although not needed end, } localfunctionipv4_address(ip_str) -- Return a collection of two 16-bit words (numbers) equivalent -- to the IPv4 address given as a quad-dotted string, or -- return nil if invalid. -- This representation is for compatibility with IPv6 addresses. localparts=Collection.new() locals=ip_str:match('^%s*(.-)%s*$')..'.' foritemins:gmatch('(.-)%.')do parts:add(item) end ifparts.n==4then fori,sinipairs(parts)do ifs:match('^%d+$')then localnum=tonumber(s) if0<=numandnum<=255then ifnum>0ands:match('^0')then -- A redundant leading zero is an error because it is for an IP in octal. returnnil end parts[i]=num else returnnil end else returnnil end end localresult=Collection.new() fori=1,3,2do result:add(parts[i]*256+parts[i+1]) end returnsetmetatable(result,ipmt) end returnnil end localfunctionipv6_address(ip_str) -- Return a collection of eight 16-bit words (numbers) equivalent -- to the IPv6 address given as a colon-delimited string, or -- return nil if invalid. local_,n=ip_str:gsub(':',':') ifn<7then ip_str,n=ip_str:gsub('::',string.rep(':',9-n)) end localparts=Collection.new() foritemin(ip_str..':'):gmatch('(.-):')do parts:add(item) end ifparts.n==8then fori,sinipairs(parts)do ifs==''then parts[i]=0 else localnum=tonumber('0x'..s) ifnumand0<=numandnum<=65535then parts[i]=num else returnnil end end end returnsetmetatable(parts,ipmt) end returnnil end localfunctioncommon_length(num1,num2,nr_bits) -- Return number of prefix bits that two integers have in common. -- Number of bits in each number is nr_bits = 16, 8, 4, 2 or 1. ifnr_bits<=1then returnnum1==num2and1or0 end localhalf=nr_bits/2 localsplitter=2^half localupper1,lower1=math.modf(num1/splitter) localupper2,lower2=math.modf(num2/splitter) ifupper1==upper2then lower1=math.floor(lower1*splitter+0.5) lower2=math.floor(lower2*splitter+0.5) returnhalf+common_length(lower1,lower2,half) end returncommon_length(upper1,upper2,half) end localfunctioncommon_prefix_length(ip1,ip2) -- Return number of prefix bits that two IPs have in common. -- Caller ensures that both IPs are IPv4 or both are IPv6. localsize=0 fori=1,ip1.ndo localw1,w2=ip1[i],ip2[i] ifw1==w2then size=size+16 else returnsize+common_length(w1,w2,16) end end returnsize end localfunctionip_prefix(ip,length) -- Return a copy of ip masked to contain only the prefix of given length. localresult={n=ip.n} fori=1,ip.ndo iflength>0then iflength>=16then result[i]=ip[i] length=length-16 else result[i]=bit32.band(ip[i],bit32.arshift(0xffff8000,length-1)) length=0 end else result[i]=0 end end returnsetmetatable(result,ipmt) end localfunctionip_incremented(ip) -- Return a new IP equal to ip + 1. -- Will wraparound (255.255.255.255 + 1 = 0.0.0.0)! localresult={n=ip.n} localcarry=1 fori=ip.n,1,-1do localsum=ip[i]+carry ifsum>=0x10000then carry=1 sum=sum-0x10000 else carry=0 end result[i]=sum end returnsetmetatable(result,ipmt) end localfunctionis_next_ip(ip1,ip2) -- Return true if ip2 is the next IP after ip1 (ip2 == ip1 + 1). -- IPs are sorted and unique so ip1 < ip2 and can ignore wrapping to zero. -- This is lower overhead than making a new incremented IP then comparing. ifip1andip2then localcarry=1 fori=ip1.n,1,-1do localsum=ip1[i]+carry ifsum>=0x10000then carry=1 sum=sum-0x10000 else carry=0 end ifsum~=ip2[i]then returnfalse end end returntrue end end -- Each IP in a range except for the last IP has a 'common' field which is -- a number specifying how many bits are common between the prefixes of this -- IP and the next IP (0 if this IP starts with 0 and the next starts with 1). -- Each non-empty range has exactly one "minimum common", that is, its value -- of common is smaller than all others. That there is only one minimum common -- follows from the fact that the IPs are unique and sorted. localfunctionmake_range(iplist,ifirst,ilast) -- Return a table for the range of IPs from iplist[ifirst] to iplist[ilast] inclusive. localimin,vmin,done ififirst<ilastthen fori=ifirst,ilast-1do -- Find the (unique) minimum of common lengths. localcommon=iplist[i].common ifvminthen ifvmin>commonthen vmin=common imin=i end else vmin=common imin=i end end else vmin=iplist.ipsize imin=ifirst done=true end ifvmin>iplist.allocationthen -- For IPv6, the default allocation is /64 and there is no point having -- more precise ranges as they add unnecessary complexity. -- However, using results=all sets allocation = 128 so vmin is not changed. vmin=iplist.allocation end return{ ifirst=ifirst,-- index of first IP ilast=ilast,-- index of last IP imin=imin,-- index of IP with minimum common size=vmin,-- number of common bits in prefix (the minimum) prefix=ip_prefix(iplist[imin],vmin),-- IP table of the base IP done=done,-- true if know that this range cannot be improved } end localfunctionsplit_range(iplist,range,depth) -- Return a table of two or more ranges that more precisely target -- the IPs in range, or return nothing if unable to improve range. depth=depthanddepth+1or0 ifdepth<=20and-- 20 examines 1M contiguous addresses down to individual IPs notrange.doneand range.size<iplist.allocationand range.ifirst<range.ilastthen localimin=range.imin assert(iminandrange.ifirst<=iminandimin<range.ilast) localr1=make_range(iplist,range.ifirst,range.imin) localr2=make_range(iplist,range.imin+1,range.ilast) localpointless=range.size+1 ifr1.size>pointlessorr2.size>pointlessthen return{r1,r2} end localresult=Collection.new() localfunctionstore_split(range) localsplit=split_range(iplist,range,depth) ifsplitthen for_,rinipairs(split)do result:add(r) end returntrue else result:add(range) end end localimproved1=store_split(r1) localimproved2=store_split(r2) ifimproved1orimproved2then returnresult end end range.done=true end localfunctionbetter_summary(iplist,summary) -- Return a better summary that more precisely targets the specified IPs, -- or return nil if unable to improve the summary. localbetter=Collection.new() localimproved for_,rangeinipairs(summary)do localsplit=split_range(iplist,range) ifsplitthen improved=true for_,rinipairs(split)do better:add(r) end else better:add(range) end end returnimprovedandbetter end localfunctionmake_summaries(iplist) -- Return a collection where each item is a summary. -- A summary is a table of one or more ranges. -- A summary covers all the given IPs and probably more. -- A range is a table representing a CIDR block such as 1.2.248.0/21. -- The first summary found is a single range; each subsequent summary -- (if any) uses more ranges to better target the given IPs. -- The result omits any summary with a range size that is too small (too many IPs). localfunctiongood_size(summary) for_,rangeinipairs(summary)do ifrange.size<iplist.minsizethen returnfalse end end returntrue end localsummaries=Collection.new() ifiplist.n>0then fori=1,iplist.n-1do -- Set length of prefixes common between each pair of IPs. iplist[i].common=common_prefix_length(iplist[i],iplist[i+1]) end localsummary={make_range(iplist,1,iplist.n)} whilesummaryandsummaries.n<iplist.maxresultsdo ifgood_size(summary)then summaries:add(summary) end summary=better_summary(iplist,summary) end end returnsummaries end localfunctionextract_ipv4(result,omitted,line) -- Extract any IPv4 addresses from given line or throw error. -- Accept CIDR /n to specify a range (only accept 16 to 32). -- Addresses must be delimited with whitespace to reduce false positives. localfunctionstore(hit) localn=32 locallhs,rhs=hit:match('^(.-)/(%d+)$') iflhsthen hit=lhs n=tonumber(rhs) ifnot(nand16<=nandn<=32)then error('CIDR /n only accepts n = 16 to 32, invalid: '..lhs..'/'..rhs,0) end end localip=ipv4_address(hit) ifipthen ifn==32then result:add(ip) else ifip~=ip_prefix(ip,n)then error('Invalid base address (host bits should be zero): '..hit,0) end for_=1,2^(32-n)do result:add(ip) ip=ip_incremented(ip) end end else omitted:add(hit) end end line=line:gsub(':',' ')-- so wikitext indents or other colons don't obscure an IVp4 address forhitinline:gmatch('%S+')do ifhit:match('^%d+%.%d+[%.%d/]+$')then local_,n=hit:gsub('%.','.') ifn>=3then store(hit) end end end end localfunctionextract_ipv6(result,omitted,line) -- Extract any IPv6 addresses from given line or throw error. -- Addresses must be delimited with whitespace to reduce false positives. -- Want to accept all valid IPv6 despite the fact that contributors will -- not have an address starting with ':'. -- Also want to be able to parse arbitrary wikitext which might use colons -- for indenting. To achieve that, if an address at the start of a line -- is valid, use it; otherwise strip any leading colons and try again. forpos,hitinline:gmatch('()(%S+)')do localipstr,length=hit:match('^([:%x]+)(/?%d*)$') ifipstrthen local_,n=ipstr:gsub(':',':') ifn>=2then localip=ipv6_address(ipstr) ifnotipandpos==1then ipstr,n=ipstr:gsub('^:+','') ifn>0then ip=ipv6_address(ipstr) end end ifipthen iflengthand#length>0then error('CIDR /n not accepted for IPv6: '..hit,0) end result:add(ip) else omitted:add(hit) end end end end end localfunctioncontribs(address,strings,ipbase,size) -- Return a URL or wikilink to list the contributions for an IP or IP range, -- or return an empty string if cannot do anything useful. -- The given address is a string of either a single IP or a CIDR range. -- If using old system: -- For IPv6 CIDR, return a Special:Contributions link using an asterisk -- wildcard which should work if the user has enabled the gadget -- "Allow /16, /24 and /27 – /32 CIDR ranges on Special:Contributions". localencoded,count=address:gsub('/','%%2F') ifstrings.want_oldandcount>0then make_note(strings,'range') ifaddress:find(':',1,true)then ifipbaseandsizethen localdigits=math.floor(size/4) ifdigits<3then digits=3 end localwildcard=digits%4==0and':*'or'*' localparts={} fori=1,8do localhex=string.format('%X',ipbase[i])-- must be uppercase ifdigits>=4then parts[i]=hex digits=digits-4 ifdigits<=0then break end else localnz-- number of leading zeros in this group of four digits ifhex=='0'then nz=4 else nz=4-#hex end ifdigits<=nzthen -- Cannot properly handle this case; have to omit group -- because "0" never occurs as the first digit. wildcard=':*' else hex=string.rep('0',nz)..hex-- four digits parts[i]=hex:sub(1,digits) end break end end address=table.concat(parts,':')..wildcard localurl='[https://en.wikipedia.org/wiki/Special:Contributions/%s?ucstart=%s c]' -- %s = IPv6 prefix address in uppercase with '*' wildcard at end -- %s = Start date formatted 'yyyymmdd000000' returnstring.format(url,address,start_date('ymdHMS',strings.months))..make_note(strings,'gadget') end return''-- no contributions link available end localurl='[https://tools.wmflabs.org/xtools/rangecontribs/?project=en.wikipedia.org&namespace=all&limit=50&text=%s&begin=%s c]' -- %s = IPv4 CIDR range with '/' changed to '%2F' -- %s = Start date formatted 'yyyy-mm-dd' returnstring.format(url,encoded,start_date('y-m-d',strings.months)) end return'[[Special:Contributions/'..address..'|c]]' end -- Strings for results using plain text. -- The pre tags used are html which do not provide "nowiki", -- but that is not required by the text used. localplaintext={ header=[=[ <pre> Total Affected Given Range]=], footer='</pre>', sumfirst=[=[ ---------------------------------------------------------- %s%-12s %-12s %-11d %s%s]=], -- %s = empty string (dummy for compatibility) -- %s = total affected -- %s = affected -- %d = given (number of addresses given in input covered by this range) -- %s = IP address range -- %s = empty string sumnext=[=[ %-12s %-11d %s%s]=], -- %s = affected -- %d = given -- %s = IP address range -- %s = empty string } -- Strings for results using a table in wikitext. localwikitable={ header=[=[ {| class="wikitable" ! Total<br />affected !! Affected<br />addresses !! Given<br />addresses !! Range !! Contribs]=], footer='|}', sumfirst=[=[ |- style="background: darkgray; height: 6px;" |colspan="5" | |- style="vertical-align: top;" |rowspan="%s" |%s ||%s ||%d ||%s ||%s]=], -- %s = string of number of ranges in summary (number of rows) -- %s = total affected -- %s = affected -- %d = given -- %s = IP address range -- %s = contributions link sumnext=[=[ |- |%s ||%d ||%s ||%s]=], -- %s = affected -- %d = given -- %s = IP address range -- %s = contributions link } localfunctionshow_summary(lines,strings,iplist,summary) -- Show the summary by adding table wikitext or plain text to lines. localwant_plain=iplist.want_plain localtotal=0 for_,rangeinipairs(summary)do -- A number is a double which easily handles 2^128 = 3.4e38. total=total+2^(iplist.ipsize-range.size) end fori,rangeinipairs(summary)do localprefix=ip_string(range.prefix) localsize=range.size localaffected=describe_size(iplist.ipsize,size) localgiven=range.ilast-range.ifirst+1 localaddress locallink='' ifsize==iplist.ipsizethen address=prefix ifnotwant_plainthen link=contribs(address,strings) end else address=prefix..'/'..size ifnotwant_plainthen link=contribs(address,strings,range.prefix,size) end end locals ifi==1then s=string.format(strings.sumfirst, want_plainand''ortostring(#summary), describe_total(total), affected,given,address,link) else s=string.format(strings.sumnext, affected,given,address,link) end -- Pre tags returned by a module are html tags, not like wikitext <pre>...</pre>. lines:add(want_plainandmw.text.nowiki(s)ors) end end localfunctionprocess_ips(lines,iplist,omitted) -- Process a list of IP addresses, adding text of results to lines. -- The list should contain either all IPv4 addresses, or all IPv6 (not a mixture). localseq1,seq2,seqmany localfunctionshow_sequence() ifseq1andseq2then localtext=ip_string(seq1) ifseqmanythen seqmany=false text=text..' – '..ip_string(seq2) end seq1=nil seq2=nil localmarkup=text:sub(1,1)==':'and':<nowiki/>'or':' lines:add(markup..text) end end localfunctionshow_ip(ip) -- Show IP or record it to be included in a "from to" sequence of IPs. ifis_next_ip(seq2,ip)then seq2=ip seqmany=true else show_sequence() seq1=ip seq2=ip seqmany=false end end ifiplist.n<1then return end iflines.n>0then lines:add('') end ifomitted.n>0then lines:add('Warning, omitted as invalid: '..omitted:join(' ')) lines:add('') end lines:add('')-- this blank line is replaced with a heading localheading_line=lines.n localduplicates=Collection.new() localprevious iplist:sort() -- Check for duplicates which can interfere with method to get ranges. fori,ipinipairs(iplist)do ifprevious==ipthen duplicates:add(i)-- index to omit duplicate later elseifnotiplist.nolistthen show_ip(ip) end previous=ip end show_sequence() localduplicate_text='' ifduplicates.n>0then duplicate_text=' (after omitting some duplicates)' fori=duplicates.n,1,-1do iplist:remove(duplicates[i]) end end localheading_text=string.format('Sorted %d %s address%s', iplist.n, iplist.ipname, iplist.n==1and''or'es' ) lines[heading_line]=heading_text..duplicate_text..':' localstrings=iplist.want_plainandplaintextorwikitable strings.notes=nil-- needed when module is kept loaded for multiple tests strings.want_old=iplist.want_old strings.nonote=iplist.nonote strings.months=iplist.months lines:add(strings.header) localupto=lines.n for_,summaryinipairs(make_summaries(iplist))do show_summary(lines,strings,iplist,summary) end lines:add(strings.footer) ifupto+1==lines.nthen -- Show message in the very unlikely event that no results are found. lines:add('----') lines:add('No suitable ranges found; use <code>|results=all</code> to see all ranges.') end ifstrings.notesthen lines:add('') lines:add("'''Notes'''") for_,keyinipairs({'range','gadget'})do ifstrings.notes[key]then lines:add(strings.notes[key]) end end end end localfunctionmake_options(args) -- Return table of options from validated args or throw error. localoptions={} ifnotempty(args.comment)then options.comment=args.comment end -- Parameter 'months' is only used if 'old' is also used. localmonths=math.floor(tonumber(args.months)ortonumber(args.month)or1) ifmonths<1then months=1 elseifmonths>12then months=12 end options.months=months-- silently ignore invalid input localallocation ifnotempty(args.allocation)then allocation=tonumber(args.allocation) ifnot(allocationand48<=allocationandallocation<=128)then error('Invalid allocation "'..args.allocation..'" (should be 48 to 128; default is 64)',0) end end localmaxresults ifnotempty(args.results)then ifargs.results=='all'then options.all=true allocation=allocationor128 maxresults=1000 else maxresults=tonumber(args.results) ifnot(maxresultsand1<=maxresultsandmaxresults<=100)then error('Invalid results "'..args.results..'" (should be 1 to 100)',0) end end end options.allocation=allocationor64 options.maxresults=maxresultsor10 localkeywords={ -- Table of k, v strings. -- If an argument matches k, an option named v is set to true. ok='noannounce', old='want_old', nolist='nolist', nonote='nonote', text='text', } localwant_old fori,arginipairs(args)do localflag=keywords[arg:match('^%s*(%w+)%s*$')] ifflagthen options[i]='skip' options[flag]=true ifflag=='want_old'then want_old=true end end end ifnotwant_oldthen options.nonote=true end returnoptions end localfunction_IPblock(args) -- Process given args; can be called from another module. -- Throw an error if need to report a problem. localoptions=make_options(args) localv4list,v4omitted=Collection.new(),Collection.new() localv6list,v6omitted=Collection.new(),Collection.new() v4list.ipsize=32 v4list.ipname='IPv4' v6list.ipsize=128 v6list.ipname='IPv6' v4list.allocation=32 v6list.allocation=options.allocation ifoptions.allthen v4list.minsize=0 v6list.minsize=0 else v4list.minsize=16-- cannot block more IPs than /16 for IPv4 v6list.minsize=19-- or /19 for IPv6 ($wgBlockCIDRLimit) end for_,kinipairs({'maxresults','months','want_old','nolist','nonote'})do v4list[k]=options[k] v6list[k]=options[k] end ifoptions.textthen v4list.want_plain=true v6list.want_plain=true end fori,arginipairs(args)do ifoptions[i]~='skip'then forlineinstring.gmatch(arg..'\n','[\t ]*(.-)[\t\r ]*\n')do -- Skip line if is empty or a comment. ifline~=''then localcomment=options.comment ifnot(commentandline:sub(1,#comment)==comment)then -- Replace accepted delimiters with a space. line=line:gsub('[!"#&\'()+,%-;<=>?[%]_{|}]',' ') extract_ipv4(v4list,v4omitted,line) extract_ipv6(v6list,v6omitted,line) end end end end end ifv4list.n<1andv6list.n<1then error('No valid IPv4 or IPv6 address in arguments',0) end locallines=Collection.new() ifnotoptions.noannouncethen -- 1: Commented out April 2016 as expired. -- 1: lines:add("'''Please see [[Template talk:Blockcalc#Version February 2016|this announcement]].'''") lines:add("'''By default, links now use [[Special:Contributions]] per [[Template talk:IP range calculator#Version November 2017|this announcement]].'''") end process_ips(lines,v4list,v4omitted) process_ips(lines,v6list,v6omitted) returnlines:join('\n') end localfunctionIPblock(frame) -- Return wikitext to display the smallest IPv4 or IPv6 CIDR range that -- covers each address given in the arguments, or return error text. -- Input can have any mixture of IPs; IPv4 and IPv6 are processed separately. localok,msg=pcall(_IPblock,frame:getParent().args) ifokthen returnmsg end return'<strong class="error">Error: '..msg..'</strong>' end return{IPblock=IPblock,_IPblock=_IPblock}