#!/usr/bin/pythonimportpywikibotimportrefromdatetimeimportdateimportdatetime######### Changing this to 1 makes your changes live on the report page, do not set to# live mode unless you have been approved for bot usage. Do not merge commits # where this is not default to 0########live=0########'''Copyright (c) 2016 WugpodesPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''defmonthConvert(name):''' Takes in either the name of the month or the number of the month and returns the opposite. An input of str(July) would return int(7) while an input of int(6) would return str(June). Takes: int OR string Returns: string OR int '''iftype(name)isstr:ifname=="January":return1elifname=="February":return2elifname=="March":return3elifname=="April":return4elifname=="May":return5elifname=="June":return6elifname=="July":return7elifname=="August":return8elifname=="September":return9elifname=="October":return10elifname=="November":return11elifname=="December":return12else:raiseValueErroreliftype(name)isint:ifname==1:return('January')elifname==2:return('February')elifname==3:return('March')elifname==4:return('April')elifname==5:return('May')elifname==6:return('June')elifname==7:return('July')elifname==8:return('August')elifname==9:return('September')elifname==10:return('October')elifname==11:return('November')elifname==12:return('December')else:raiseValueErrordefappendUpdates(toprint,updates,index=3,rev=True):''' Takes an iterable array and the output array and returns teh output array appended with the marked up iterable array. '''foriteminupdates:ifitem[4]!=None:i=3else:i=2text='# '+sectionLink(item[i],item[0])+" ('''"\
+str(item[index])+"''' days)\n"toprint.append(text)return(toprint)defdateActions(nominList,index):''' For a given list, it finds the embedded date, how many days ago that date is from today, and appends that number to the list. It then returns the list '''foriteminnominList:iMatch=datRegex.search(item[index])day=int(iMatch.group(1))month=monthConvert(str(iMatch.group(2)))year=int(iMatch.group(3))d0=date(year,month,day)delta=today-d0item.append(delta.days)#print(delta.days,'days')return(nominList)defsortByKey(nominList,index):''' Sorts a given list by a given index in a sublist, largest first '''nominList.sort(key=lambdax:x[index],reverse=True)return(nominList)defwikiTimeStamp():''' Returns the current time stamp in the style of wikipedia signatures. '''stamp=str(datetime.datetime.utcnow().hour).zfill(2)+':'\
+str(datetime.datetime.utcnow().minute).zfill(2)+', '\
+str(datetime.datetime.utcnow().day)+' '\
+monthConvert(datetime.datetime.utcnow().month)+' '\
+str(datetime.datetime.utcnow().year)+' (UTC)'return(stamp)defupdateSummary(section,subsection=False):globalnomsBySectionglobalsubSectDictifsubsection:i=4n=str(nomsBySection[section][i][subsection][0])h=str(nomsBySection[section][i][subsection][1])r=str(nomsBySection[section][i][subsection][2])s=str(nomsBySection[section][i][subsection][3])text=":'''"+sectionLink(subsection,subsection)+"''' ("+n+")"else:n=str(nomsBySection[section][0])h=str(nomsBySection[section][1])r=str(nomsBySection[section][2])s=str(nomsBySection[section][3])text="'''"+sectionLink(section,section)+"''' ("+n+")"ifsection=='Miscellaneous':text+='\n'ifint(h)+int(r)+int(s)>0:text+=": "else:text+="\n"ifint(h)>0:text+='[[Image:Symbol wait.svg|15px|On Hold]] x '+hifint(r)>0orint(s)>0:text+='; 'else:text+='\n'ifint(r)>0:text+='[[Image:Searchtool.svg|15px|Under Review]] x '+rifint(s)>0:text+='; 'else:text+='\n'ifint(s)>0:text+='[[Image:Symbol neutral vote.svg|15px|2nd Opinion Requested]]'\
+' x '+s+'\n'return(text)defsectionLink(section,title):text='[[Wikipedia:Good article nominations#'+section+'|'+title+']]'return(text)

site=pywikibot.Site('en','wikipedia')page=pywikibot.Page(site,'Wikipedia:Good article nominations')fullText=page.textfullText=fullText.split('\n')#Compile regexes##Finds GAN entries and returns time stamp, title, and the following lineentRegex=re.compile(r'\{\{GANentry.*?\|1\=(.*?)\|2=(\d+)'\
+r'(?:.*?\[\[(?:(?:U|u)ser|(?:U|u)ser talk)\:(.*?)\|.*)?'\
+r'(?:\}\} (.*?) )?(\d\d\:\d\d, \d+ .*? \d\d\d\d) \(UTC\)')sctRegex=re.compile(r'==+ (.*?) (==+)')##Finds the Wikipedia UTC TimestampdatRegex=re.compile(r', (\d+) (.*?) (\d\d\d\d)')entry=[]nomin=[]onRev=[]onHld=[]waitg=[]scnOp=[]badNoms=[]toPrint=[]nomsBySection={}subSectDict={}nomsByNominator={}'''FOR LOOP DOCUMENTATIONThe following for loop goes line by line through the nomination page. It checks to see: If the line is a section header: If the line is a subsection header: then it updates the subsection dictionaries If it is not a subsection (i.e. only a section): then it updates the section dictionary Else if it is a GANEntry template: stores the regex output in matches Else if it is none of the above nor a GAReview it skips the lineIt then checks (see note below): If it is a GAReview template: then it sorts the associated nomination Else it updates the entry data and adds it to entry and nominNOTE: This runs backwards. GAReview templates only come /after/ a nomination so a nomination line will be added to entry and nomin and the next line, if it is not being worked on (ie no GAReview template) then it will overwrite the previous nomination data. But if there is a GAReview template it is /not/ overwritten and is used to sort the previous nomination into the proper place and removes it from nomin (because it's on review)'''forlineinfullText:if'=='inline:if'==='inline:subSectName=re.search(sctRegex,line).group(1)nomsBySection[sectName][4][subSectName]=[0,0,0,0]subSectDict[subSectName]=len(nomsBySection[sectName])-1else:result=re.search(sctRegex,line)sectName=result.group(1)nomsBySection[sectName]=[0,0,0,0,{}]subSectName=Nonecontinueelif'GANentry'inline:matches=entRegex.search(line)ifmatches.group(3)!=None:username=matches.group(3)elifmatches.group(3)==Noneandmatches.group(4)!=None:username=matches.group(4)else:badNoms.append([matches.group(1),subSectName])username='Unknown'ifusernamenotinnomsByNominator:nomsByNominator[username]=[]elif'GAReview'notinline:continueif'GAReview'inline:nomin.pop()entryData.append(line)if'on hold'inline:onHld.append(entryData)elif'2nd opinion'inline:scnOp.append(entryData)else:onRev.append(entryData)else:entryData=[matches.group(1),# Title of the nominated articlematches.group(2),# Nomination numbersectName,# Section namesubSectName,# Subsection namematches.group(5),# Timestampusername# Nominator's name]entry.append(entryData)nomin.append(entryData)ifsubSectName!=None:sec=subSectNameelse:sec=sectNamenomsByNominator[username].append([matches.group(1),sec])########################################################################## DATA FORMAT FOR ITEMS IN ARRAYS PRODUCED ABOVE# (entry,nomin,onHld,onRev,scnOp)# entryData[0] = Title of the nominated article# entryData[1] = Nomination number# entryData[2] = Section name# entryData[3] = Subsection name# entryData[4] = Timestamp# entryData[5] = Nominator's name# entryData[6] = The line following (not present in nomin or entry)##########################################################################Get the datetoday=date.today()#Get backlog statsnoms=len(entry)inac=len(nomin)ohld=len(onHld)orev=len(onRev)scnd=len(scnOp)rIndex=8# index number for appended days since action#Get all unreviewed nominations older than 30 daysoldestnoms=nominoldestnoms=dateActions(oldestnoms,4)topTen=[]oldestnoms=sortByKey(oldestnoms,6)whilelen(topTen)<10:topTen.append(oldestnoms.pop(0))#Get all nominations older than 30 daysentry=dateActions(entry,4)oThirty=[]foriteminentry:ifint(item[7])>=30:oThirty.append(item)oThirty=sortByKey(oThirty,7)#Get the nominations ON HOLD 7 days or longeronHld=dateActions(onHld,rIndex-2)oldOnHold=[]foriteminonHld:ifint(item[rIndex])>=7:oldOnHold.append(item)oldOnHold=sortByKey(oldOnHold,rIndex)#Get the nominations ON REVIEW for 7 days or longeronRev=dateActions(onRev,rIndex-2)oldOnRev=[]foriteminonRev:ifint(item[rIndex])>=7:oldOnRev.append(item)oldOnRev=sortByKey(oldOnRev,rIndex)#Get the nominations ON SECOND OPINION for 7 days or longerscnOp=dateActions(scnOp,rIndex-2)oldScnOp=[]foriteminscnOp:ifint(item[rIndex])>=7:oldScnOp.append(item)oldScnOp=sortByKey(oldScnOp,rIndex)#Load report page (must be here because backlog report requires it be loaded)page=pywikibot.Page(site,'Wikipedia:Good article nominations/Report')archive=pywikibot.Page(site,'Wikipedia:Good article nominations/Report/'\
+'Backlog archive')#Make Backlog reportbacklogReport=[]formatchinre.findall(r'(\d.*?\/>)',page.text):backlogReport.append(match+'\n')curEntry=wikiTimeStamp()+' &ndash; '+str(noms)+' nominations outstanding; ' \
+str(inac)+' not reviewed; [[Image:Symbol wait.svg|15px|On Hold]] x ' \
+str(ohld)+'; [[Image:Searchtool.svg|15px|Under Review]] x '+str(orev) \
+'; [[Image:Symbol neutral vote.svg|15px|2nd Opinion Requested]] x ' \
+str(scnd)+'<br />'backlogReport.insert(0,curEntry)oldLine=backlogReport.pop()################## Make the Page################## Write Oldest nominationsreport=['{{/top}}\n\n','== Oldest nominations ==\n',":''List of the oldest ten nominations that have had no activity" \
+"(placed on hold, under review or requesting a 2nd opinion)''\n",]report=appendUpdates(report,topTen,index=6,rev=False)report+=['\n','== Backlog report ==\n',]# Write backlog reportforiteminbacklogReport:report.append(item)report.append(":''Previous daily backlogs can be viewed at the" \
+"[[/Backlog archive|backlog archive]].''\n\n")# Write the exceptions report# Write reviews on hold for over 7 days report+=['== Exceptions report ==\n','=== Holds over 7 days old ===\n']report=appendUpdates(report,oldOnHold,rIndex)# Write nominations marked on review for over 7 daysreport+=['\n','=== Old reviews ===\n',":''Nominations that have been marked under review for 7 days or "\
+"longer.''\n"]report=appendUpdates(report,oldOnRev,rIndex)# Write requests for second opinion older than 7 daysreport+=['\n','=== Old requests for 2nd opinion ===\n',":''Nominations that have been marked requesting a second opinion for 7 "\
+"days or longer.''\n"]report=appendUpdates(report,oldScnOp,rIndex)#Write all nominations older than one monthreport+=['\n','=== Old nominations ===\n',":''All nominations that were added 30 days ago or longer, regardless of "\
+"other activity.''\n"]foriteminoThirty:ifitem[4]!=None:# If there is a subsectionj=3# use itelse:# otherwisej=2# use section name# Add icons if nomination is on hold, review, etcifany(item[0]iniforiinonHld):text='# [[Image:Symbol wait.svg|15px|On Hold]] '\
+sectionLink(item[j],item[0])+" ('''"\
+str(item[rIndex])+"''' days)\n"elifany(item[0]iniforiinonRev):text='# [[Image:Searchtool.svg|15px|Under Review]] '\
+sectionLink(item[j],item[0])+" ('''"\
+str(item[rIndex])+"''' days)\n"elifany(item[0]iniforiinscnOp):text='# [[Image:Symbol neutral vote.svg|15px|2nd Opinion Requested]]'\
+sectionLink(item[j],item[0])+" ('''"\
+str(item[rIndex])+"''' days)\n"else:text='# '+sectionLink(item[j],item[0])+" ('''"\
+str(item[rIndex-1])+"''' days)\n"report.append(text)# Malformed Nomsreport.append('=== Malformed nominations ===\n')iflen(badNoms)<1:report.append('None\n')else:iflen(badNoms)>1:report.append(":''There are currently "+str(len(badNoms))\
+" malformed nominations''\n")eliflen(badNoms)==1:report.append(":''There is currently 1 malformed nomination''\n")foriteminbadNoms:text='# '+sectionLink(item[1],item[0])+"\n"report.append(text)# Counts and outputs nominators with multiple nominationsreport.append('=== Nominators with multiple nominations ===\n')multipleNomsOut=[]mnOutput=[]foruserinnomsByNominator:iflen(nomsByNominator[user])>1:multipleNomsOut.append([user,len(nomsByNominator[user]),nomsByNominator[user]])line=';'+user+' ('+str(len(nomsByNominator[user]))+')\n'nomsSort=sortByKey(multipleNomsOut,1)foriteminnomsSort:line=';'+item[0]+' ('+str(item[1])+')'mnOutput.append(line)line=':'counter=0formnNominitem[2]:ifcounter!=0:line+=', 'line+=sectionLink(mnNom[1],mnNom[0])counter+=1line+='\n'mnOutput.append(line)report+=mnOutput# Counts up all the noms, holds, reviews, and 2nd opinions in each section and# iterates the counter in the nomsBySection datastructureforiteminentry:ifitem[2]innomsBySectionanditem[3]==None:nomsBySection[item[2]][0]+=1ifany(item[0]iniforiinonHld):nomsBySection[item[2]][1]+=1elifany(item[0]iniforiinonRev):nomsBySection[item[2]][2]+=1elifany(item[0]iniforiinscnOp):nomsBySection[item[2]][1]+=1elifitem[3]insubSectDict:index=4nomsBySection[item[2]][index][item[3]][0]+=1ifany(item[0]iniforiinonHld):nomsBySection[item[2]][index][item[3]][1]+=1elifany(item[0]iniforiinonRev):nomsBySection[item[2]][index][item[3]][2]+=1elifany(item[0]iniforiinscnOp):nomsBySection[item[2]][index][item[3]][3]+=1else:print(item)raiseTypeError('Nominations must have a section or subsection')# Creates the summary report by iterating over the nomsBySection data structuresectionNameList=[]subsectionDict={}forkeyinnomsBySection:sectionNameList.append(key)subsectionList=[]forsubkeyinnomsBySection[key][4]:subsectionList.append(subkey)ifsubsectionList!=[]:subsectionList.sort()subsectionDict[key]=subsectionListsummary=[]sectionNameList.sort()forsectioninsectionNameList:summary.append(updateSummary(section))ifsubsectionDict[section]!=[]:forsubsectioninsubsectionDict[section]:summary.append(updateSummary(section,subsection))# Writes the summary reportreport.append('== Summary ==\n')report+=summary# A relic of the old way, in memoriam WugBot-v0.0toPrint=report# Sign ittoPrint.append('<!-- Updated at '+wikiTimeStamp()+' by' \
+' WugBot-v1.0 -->\n')

File "<ipython-input-12-5098aaa098de>", line 59 if '===' in line:
^
IndentationError: unindent does not match any outer indentation level

#!/usr/bin/pythonimportpywikibotimportrefromdatetimeimportdateimportdatetime######### Changing this to 1 makes your changes live on the report page, do not set to# live mode unless you have been approved for bot usage. Do not merge commits # where this is not default to 0########live=0########'''Copyright (c) 2016 WugpodesPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''defmonthConvert(name):''' Takes in either the name of the month or the number of the month and returns the opposite. An input of str(July) would return int(7) while an input of int(6) would return str(June). Takes: int OR string Returns: string OR int '''iftype(name)isstr:ifname=="January":return1elifname=="February":return2elifname=="March":return3elifname=="April":return4elifname=="May":return5elifname=="June":return6elifname=="July":return7elifname=="August":return8elifname=="September":return9elifname=="October":return10elifname=="November":return11elifname=="December":return12else:raiseValueErroreliftype(name)isint:ifname==1:return('January')elifname==2:return('February')elifname==3:return('March')elifname==4:return('April')elifname==5:return('May')elifname==6:return('June')elifname==7:return('July')elifname==8:return('August')elifname==9:return('September')elifname==10:return('October')elifname==11:return('November')elifname==12:return('December')else:raiseValueErrordefappendUpdates(toprint,updates,index=3,rev=True):''' Takes an iterable array and the output array and returns teh output array appended with the marked up iterable array. '''foriteminupdates:ifitem[4]!=None:i=3else:i=2text='# '+sectionLink(item[i],item[0])+" ('''"\
+str(item[index])+"''' days)\n"toprint.append(text)return(toprint)defdateActions(nominList,index):''' For a given list, it finds the embedded date, how many days ago that date is from today, and appends that number to the list. It then returns the list '''foriteminnominList:iMatch=datRegex.search(item[index])day=int(iMatch.group(1))month=monthConvert(str(iMatch.group(2)))year=int(iMatch.group(3))d0=date(year,month,day)delta=today-d0item.append(delta.days)#print(delta.days,'days')return(nominList)defsortByKey(nominList,index):''' Sorts a given list by a given index in a sublist, largest first '''nominList.sort(key=lambdax:x[index],reverse=True)return(nominList)defwikiTimeStamp():''' Returns the current time stamp in the style of wikipedia signatures. '''stamp=str(datetime.datetime.utcnow().hour).zfill(2)+':'\
+str(datetime.datetime.utcnow().minute).zfill(2)+', '\
+str(datetime.datetime.utcnow().day)+' '\
+monthConvert(datetime.datetime.utcnow().month)+' '\
+str(datetime.datetime.utcnow().year)+' (UTC)'return(stamp)defupdateSummary(section,subsection=False):globalnomsBySectionglobalsubSectDictifsubsection:i=4n=str(nomsBySection[section][i][subsection][0])h=str(nomsBySection[section][i][subsection][1])r=str(nomsBySection[section][i][subsection][2])s=str(nomsBySection[section][i][subsection][3])text=":'''"+sectionLink(subsection,subsection)+"''' ("+n+")"else:n=str(nomsBySection[section][0])h=str(nomsBySection[section][1])r=str(nomsBySection[section][2])s=str(nomsBySection[section][3])text="'''"+sectionLink(section,section)+"''' ("+n+")"ifsection=='Miscellaneous':text+='\n'ifint(h)+int(r)+int(s)>0:text+=": "else:text+="\n"ifint(h)>0:text+='[[Image:Symbol wait.svg|15px|On Hold]] x '+hifint(r)>0orint(s)>0:text+='; 'else:text+='\n'ifint(r)>0:text+='[[Image:Searchtool.svg|15px|Under Review]] x '+rifint(s)>0:text+='; 'else:text+='\n'ifint(s)>0:text+='[[Image:Symbol neutral vote.svg|15px|2nd Opinion Requested]]'\
+' x '+s+'\n'return(text)defsectionLink(section,title):text='[[Wikipedia:Good article nominations#'+section+'|'+title+']]'return(text)site=pywikibot.Site('en','wikipedia')page=pywikibot.Page(site,'Wikipedia:Good article nominations')fullText=page.textfullText=fullText.split('\n')#Compile regexes##Finds GAN entries and returns time stamp, title, and the following lineentRegex=re.compile(r'\{\{GANentry.*?\|1\=(.*?)\|2=(\d+)'\
+r'(?:.*?\[\[(?:(?:U|u)ser|(?:U|u)ser talk)\:(.*?)\|.*)?'\
+r'(?:\}\} (.*?) )?(\d\d\:\d\d, \d+ .*? \d\d\d\d) \(UTC\)')sctRegex=re.compile(r'==+ (.*?) (==+)')##Finds the Wikipedia UTC TimestampdatRegex=re.compile(r', (\d+) (.*?) (\d\d\d\d)')entry=[]nomin=[]onRev=[]onHld=[]waitg=[]scnOp=[]badNoms=[]toPrint=[]nomsBySection={}subSectDict={}nomsByNominator={}'''FOR LOOP DOCUMENTATIONThe following for loop goes line by line through the nomination page. It checks to see: If the line is a section header: If the line is a subsection header: then it updates the subsection dictionaries If it is not a subsection (i.e. only a section): then it updates the section dictionary Else if it is a GANEntry template: stores the regex output in matches Else if it is none of the above nor a GAReview it skips the lineIt then checks (see note below): If it is a GAReview template: then it sorts the associated nomination Else it updates the entry data and adds it to entry and nominNOTE: This runs backwards. GAReview templates only come /after/ a nomination so a nomination line will be added to entry and nomin and the next line, if it is not being worked on (ie no GAReview template) then it will overwrite the previous nomination data. But if there is a GAReview template it is /not/ overwritten and is used to sort the previous nomination into the proper place and removes it from nomin (because it's on review)'''forlineinfullText:if'=='inline:#Checks to see if sectionif'==='inline:#Checks to see if subsectionsubSectName=re.search(sctRegex,line).group(1)# array nums represent: # [# of noms, # on hold, # on rev, # 2nd opinion]nomsBySection[sectName][4][subSectName]=[0,0,0,0]# keeps track of the indices of subsections in the data structuresubSectDict[subSectName]=len(nomsBySection[sectName])-1else:result=re.search(sctRegex,line)sectName=result.group(1)# array nums represent: # [# of noms, # on hold, # on rev, # 2nd opinion]nomsBySection[sectName]=[0,0,0,0,{}]subSectName=Nonecontinueelif'GANentry'inline:matches=entRegex.search(line)ifmatches.group(3)!=None:username=matches.group(3)elifmatches.group(3)==Noneandmatches.group(4)!=None:username=matches.group(4)else:badNoms.append([matches.group(1),subSectName])username='Unknown'ifusernamenotinnomsByNominator:nomsByNominator[username]=[]elif'GAReview'notinline:continueif'GAReview'inline:nomin.pop()entryData.append(line)if'on hold'inline:onHld.append(entryData)elif'2nd opinion'inline:scnOp.append(entryData)else:onRev.append(entryData)else:entryData=[matches.group(1),# Title of the nominated articlematches.group(2),# Nomination numbersectName,# Section namesubSectName,# Subsection namematches.group(5),# Timestampusername# Nominator's name]entry.append(entryData)nomin.append(entryData)ifsubSectName!=None:sec=subSectNameelse:sec=sectNamenomsByNominator[username].append([matches.group(1),sec])########################################################################## DATA FORMAT FOR ITEMS IN ARRAYS PRODUCED ABOVE# (entry,nomin,onHld,onRev,scnOp)# entryData[0] = Title of the nominated article# entryData[1] = Nomination number# entryData[2] = Section name# entryData[3] = Subsection name# entryData[4] = Timestamp# entryData[5] = Nominator's name# entryData[6] = The line following (not present in nomin or entry)##########################################################################Get the datetoday=date.today()#Get backlog statsnoms=len(entry)inac=len(nomin)ohld=len(onHld)orev=len(onRev)scnd=len(scnOp)rIndex=8# index number for appended days since action#Get all unreviewed nominations older than 30 daysoldestnoms=nominoldestnoms=dateActions(oldestnoms,4)topTen=[]oldestnoms=sortByKey(oldestnoms,6)foriinrange(10):topTen.append(oldestnoms[i])#Get all nominations older than 30 daysentry=dateActions(entry,4)oThirty=[]foriteminentry:ifint(item[7])>=30:oThirty.append(item)oThirty=sortByKey(oThirty,7)#Get the nominations ON HOLD 7 days or longeronHld=dateActions(onHld,rIndex-2)oldOnHold=[]foriteminonHld:ifint(item[rIndex])>=7:oldOnHold.append(item)oldOnHold=sortByKey(oldOnHold,rIndex)#Get the nominations ON REVIEW for 7 days or longeronRev=dateActions(onRev,rIndex-2)oldOnRev=[]foriteminonRev:ifint(item[rIndex])>=7:oldOnRev.append(item)oldOnRev=sortByKey(oldOnRev,rIndex)#Get the nominations ON SECOND OPINION for 7 days or longerscnOp=dateActions(scnOp,rIndex-2)oldScnOp=[]foriteminscnOp:ifint(item[rIndex])>=7:oldScnOp.append(item)oldScnOp=sortByKey(oldScnOp,rIndex)#Load report page (must be here because backlog report requires it be loaded)page=pywikibot.Page(site,'Wikipedia:Good article nominations/Report')#Make Backlog reportbacklogReport=[]formatchinre.findall(r'(\d.*?\/>)',page.text):backlogReport.append(match+'\n')curEntry=wikiTimeStamp()+' &ndash; '+str(noms)+' nominations outstanding; ' \
+str(inac)+' not reviewed; [[Image:Symbol wait.svg|15px|On Hold]] x ' \
+str(ohld)+'; [[Image:Searchtool.svg|15px|Under Review]] x '+str(orev) \
+'; [[Image:Symbol neutral vote.svg|15px|2nd Opinion Requested]] x ' \
+str(scnd)+'<br />'backlogReport.insert(0,curEntry)oldLine=backlogReport.pop()################## Make the Page################## Write Oldest nominationsreport=['{{/top}}\n\n','== Oldest nominations ==\n',":''List of the oldest ten nominations that have had no activity" \
+"(placed on hold, under review or requesting a 2nd opinion)''\n",]report=appendUpdates(report,topTen,index=6,rev=False)report+=['\n','== Backlog report ==\n',]# Write backlog reportforiteminbacklogReport:report.append(item)report.append(":''Previous daily backlogs can be viewed at the" \
+"[[/Backlog archive|backlog archive]].''\n\n")# Write the exceptions report# Write reviews on hold for over 7 days report+=['== Exceptions report ==\n','=== Holds over 7 days old ===\n']report=appendUpdates(report,oldOnHold,rIndex)# Write nominations marked on review for over 7 daysreport+=['\n','=== Old reviews ===\n',":''Nominations that have been marked under review for 7 days or "\
+"longer.''\n"]report=appendUpdates(report,oldOnRev,rIndex)# Write requests for second opinion older than 7 daysreport+=['\n','=== Old requests for 2nd opinion ===\n',":''Nominations that have been marked requesting a second opinion for 7 "\
+"days or longer.''\n"]report=appendUpdates(report,oldScnOp,rIndex)#Write all nominations older than one monthreport+=['\n','=== Old nominations ===\n',":''All nominations that were added 30 days ago or longer, regardless of "\
+"other activity.''\n"]foriteminoThirty:ifitem[4]!=None:# If there is a subsectionj=3# use itelse:# otherwisej=2# use section name# Add icons if nomination is on hold, review, etcifany(item[0]iniforiinonHld):text='# [[Image:Symbol wait.svg|15px|On Hold]] '\
+sectionLink(item[j],item[0])+" ('''"\
+str(item[rIndex])+"''' days)\n"elifany(item[0]iniforiinonRev):text='# [[Image:Searchtool.svg|15px|Under Review]] '\
+sectionLink(item[j],item[0])+" ('''"\
+str(item[rIndex])+"''' days)\n"elifany(item[0]iniforiinscnOp):text='# [[Image:Symbol neutral vote.svg|15px|2nd Opinion Requested]]'\
+sectionLink(item[j],item[0])+" ('''"\
+str(item[rIndex])+"''' days)\n"else:text='# '+sectionLink(item[j],item[0])+" ('''"\
+str(item[rIndex-1])+"''' days)\n"report.append(text)# Malformed Nomsreport.append('=== Malformed nominations ===\n')iflen(badNoms)<1:report.append('None\n')else:iflen(badNoms)>1:report.append(":''There are currently "+str(len(badNoms))\
+" malformed nominations''\n")eliflen(badNoms)==1:report.append(":''There is currently 1 malformed nomination''\n")foriteminbadNoms:text='# '+sectionLink(item[1],item[0])+"\n"report.append(text)# Counts and outputs nominators with multiple nominationsreport.append('=== Nominators with multiple nominations ===\n')multipleNomsOut=[]mnOutput=[]foruserinnomsByNominator:iflen(nomsByNominator[user])>1:multipleNomsOut.append([user,len(nomsByNominator[user]),nomsByNominator[user]])line=';'+user+' ('+str(len(nomsByNominator[user]))+')\n'nomsSort=sortByKey(multipleNomsOut,1)foriteminnomsSort:line=';'+item[0]+' ('+str(item[1])+')'mnOutput.append(line)line=':'counter=0formnNominitem[2]:ifcounter!=0:line+=', 'line+=sectionLink(mnNom[1],mnNom[0])counter+=1line+='\n'mnOutput.append(line)report+=mnOutput# Counts up all the noms, holds, reviews, and 2nd opinions in each section and# iterates the counter in the nomsBySection datastructureforiteminentry:ifitem[2]innomsBySectionanditem[3]==None:nomsBySection[item[2]][0]+=1ifany(item[0]iniforiinonHld):nomsBySection[item[2]][1]+=1elifany(item[0]iniforiinonRev):nomsBySection[item[2]][2]+=1elifany(item[0]iniforiinscnOp):nomsBySection[item[2]][1]+=1elifitem[3]insubSectDict:index=4nomsBySection[item[2]][index][item[3]][0]+=1ifany(item[0]iniforiinonHld):nomsBySection[item[2]][index][item[3]][1]+=1elifany(item[0]iniforiinonRev):nomsBySection[item[2]][index][item[3]][2]+=1elifany(item[0]iniforiinscnOp):nomsBySection[item[2]][index][item[3]][3]+=1else:print(item)raiseTypeError('Nominations must have a section or subsection')# Creates the summary report by iterating over the nomsBySection data structuresectionNameList=[]subsectionDict={}forkeyinnomsBySection:sectionNameList.append(key)subsectionList=[]forsubkeyinnomsBySection[key][4]:subsectionList.append(subkey)ifsubsectionList!=[]:subsectionList.sort()subsectionDict[key]=subsectionListsummary=[]sectionNameList.sort()forsectioninsectionNameList:summary.append(updateSummary(section))ifsubsectionDict[section]!=[]:forsubsectioninsubsectionDict[section]:summary.append(updateSummary(section,subsection))# Writes the summary reportreport.append('== Summary ==\n')report+=summary# A relic of the old way, in memoriam WugBot-v0.0toPrint=report# Sign ittoPrint.append('<!-- Updated at '+wikiTimeStamp()+' by' \
+' WugBot-v1.0 -->\n')'''# Determine if the bot should write to a live page or the test page. Defaults to # test page. Value of -1 tests backlog update (not standard because the file# size is very big).if live == 1: page.text=''.join(toPrint) page.save('Updating exceptions report') page = pywikibot.Page(site,'Wikipedia:Good article nominations/Report/'\ +'Backlog archive') page.text+=oldLine page.save('Update of GAN report backlog')else: page = pywikibot.Page(site,'User:Wugapodes/GANReportBotTest') page.text=''.join(toPrint) page.save('Testing expanded reporting') if live==-1: page = pywikibot.Page(site, 'User:Wugapodes/GANReportBotTest/Backlog archive') testText=page.text page.text=testText page.save('Testing backlog report updating')'''print(wikiTimeStamp())