User:AnomieBOT/source/tasks/BAGBot.pm

with a wikilink to the User or User talk namespace on the same line. Please fix it! Thanks.");               }                $b->{'operator'}=$1;                $b->{'notify_botedited'}|=4 if ($b->{'operator'}//'?') eq $b->{'bot'};            }

# Update the last edit info if($b->{'realrevid'}!=$r->{'revisions'}[0]{'revid'}){ my $rv=$b->{'realrevid'}; $b->{'realrevid'}=$r->{'revisions'}[0]{'revid'};

my ($needOp,$needUser,$needBAG)=(1,1,1);

if($r->{'revisions'}[0]{'user'} ne $api->user){ $b->{'revid'}=$r->{'revisions'}[0]{'revid'}; $b->{'editby'}=$r->{'revisions'}[0]{'user'}; $b->{'edittime'}=strftime('%F, %T', gmtime $api->ISO2timestamp($r->{'revisions'}[0]{'timestamp'})); $needUser=0; }               if(!exists($r->{'revisions'}[0]{'minor'}) && $b->{'editby'} eq ($b->{'operator'}//'?')){ $b->{'oprevid'}=$b->{'revid'}; $needOp=0; }               if(!exists($r->{'revisions'}[0]{'minor'}) && $b->{'editby'} ne ($b->{'operator'}//'?') && grep($_ eq $b->{'editby'}, @BAG)){ $b->{'bagrevid'}=$b->{'revid'}; $b->{'bageditby'}=$b->{'editby'}; $b->{'bagedittime'}=$b->{'edittime'}; $needBAG=0; }               my %q=(                    prop=>'revisions',                    titles=>$pg,                    rvprop=>'user|ids|timestamp|flags',                    rvlimit=>'10',                    $rv ? (rvendid=>$rv) :                 ); my @rr=; while($needUser || $needBAG || $needOp){ if(!@rr){ last unless %q; my $res=$api->query(%q); if($res->{'code'} ne 'success'){ $api->warn("Failed to retrieve revisions for $pg: ".$res->{'error'}."\n"); return 60; }                       @rr=@{(values %{$res->{'query'}{'pages'}})[0]{'revisions'}}; if(exists($res->{'query-continue'}{'revisions'})){ while(my ($k,$v)=each(%{$res->{'query-continue'}{'revisions'}})){ $q{$k}=$v; }                       } else { %q=; }                   }                    my $r=shift @rr; $r->{'user'} //= ''; # If revdeled if($needUser && $r->{'user'} ne $api->user){ $b->{'revid'}=$r->{'revid'}; $b->{'editby'}=$r->{'user'}; $b->{'edittime'}=strftime('%F, %T', gmtime $api->ISO2timestamp($r->{'timestamp'})); $needUser=0; }                   if($needBAG && !exists($r->{'minor'}) && $r->{'user'} ne ($b->{'operator'}//'?') && grep($_ eq $r->{'user'}, @BAG)){ $b->{'bagrevid'}=$r->{'revid'}; $b->{'bageditby'}=$r->{'user'}; $b->{'bagedittime'}=strftime('%F, %T', gmtime $api->ISO2timestamp($r->{'timestamp'})); $needBAG=0; }                   if($needOp && !exists($r->{'minor'}) && $r->{'user'} eq ($b->{'operator'}//'?')){ $b->{'oprevid'}=$r->{'revid'}; $needOp=0; }               }            }

# To determine the BRFA status... my %cats=map { $_->{'title'}, 1 } @{$r->{'categories'}}; my %tmpl=map { $_->{'title'}, 1 } @{$r->{'templates'}}; my $c=''; my $need_user=($tmpl{$tr{'Template:OperatorAssistanceNeeded'}} // 0); push @need_edit_brfa, $pg if($need_user && !$b->{'optout-operatorassistanceneeded'} || $b->{'check_newbot'} || ($b->{'notify_botedited'} & ~$b->{'notified_botedited'})); my $need_bag=($tmpl{$tr{'Template:BAGAssistanceNeeded'}} // 0); if($cats{'Category:Revoked Wikipedia bot requests for approval'} // 0){ $need_user=$need_bag=0; $c='style="background-color:orange"'; $b->{'status'}='Revoked'; $b->{'targettimestamp'}=time if $b->{'targetsection'}!=-1; $b->{'targetsection'}=-1; } elsif($cats{'Category:Approved Wikipedia bot requests for approval'} // 0){ $need_user=$need_bag=0; $c='style="background-color:lightblue"'; $b->{'targettimestamp'}=time if $b->{'targetsection'}!=3; $b->{'targetsection'}=3; if($tmpl{$tr{'Template:BotSpeedy'}} // 0){ $b->{'status'}='Speedy Approved'; } else { $b->{'status'}='Approved'; }           } elsif($cats{'Category:Denied Wikipedia bot requests for approval'} // 0){ $need_user=$need_bag=0; $c='style="background-color:orange"'; $b->{'status'}='Denied'; $b->{'targettimestamp'}=time if $b->{'targetsection'}!=4; $b->{'targetsection'}=4; } elsif($cats{'Category:Withdrawn Wikipedia bot requests for approval'} // 0){ $need_user=$need_bag=0; $c='style="background-color:gray"'; $b->{'status'}='Withdrawn'; $b->{'targettimestamp'}=time if $b->{'targetsection'}!=5; $b->{'targetsection'}=5; } elsif($cats{'Category:Expired Wikipedia bot requests for approval'} // 0){ $need_user=$need_bag=0; $c='style="background-color:yellow"'; $b->{'status'}='Expired'; $b->{'targettimestamp'}=time if $b->{'targetsection'}!=5; $b->{'targetsection'}=5; } elsif($cats{'Category:Open Wikipedia bot requests for approval'} // 0){ $active++; if($tmpl{$tr{'Template:BotTrialComplete'}} // $tmpl{$tr{'Template:BotExtendedTrial'}} // $tmpl{$tr{'Template:BotTrial'}} // $tmpl{$tr{'Template:BotOnHold'}} // 0){ # "Trial complete" may have been superseded by a later new # "Trial". Assume the comments are in chronological order, # and use the last found. my $cc=$api->rawpage($pg); if($cc->{'code'} ne 'success'){ $api->warn("Failed to retrieve page data for $pg: ".$cc->{'error'}."\n"); return 60; }                   my $ts=0; $api->process_templates($cc->{'content'}, sub {                       my $name=shift;                        $name =~ s/^(Template|msg)://i;                        $name=$tr{"Template:$name"} // "Template:$name";                        if($name eq $tr{'Template:BotTrialComplete'}){                            $c='style="background-color:lightblue"';                            $b->{'status'}='Trial complete';                            $ts=2;                        } elsif($name eq $tr{'Template:BotExtendedTrial'}){                            $c='style="background-color:lightgreen"';                            $b->{'status'}='Extended trial';                            $ts=1;                        } elsif($name eq $tr{'Template:BotTrial'}){                            $c='style="background-color:lightgreen"';                            $b->{'status'}='In trial';                            $ts=1; } elsif($name eq $tr{'Template:BotOnHold'}){ $c='style="background-color:lightgray"'; $b->{'status'}='On hold'; # For now, leave in whichever section other templates want to put it in. }                   });                    $b->{'targettimestamp'}=time if $b->{'targetsection'}!=$ts;                    $b->{'targetsection'}=$ts;                } else {                    $c='';                    $b->{'status'}='Open';                    $b->{'targettimestamp'}=time if $b->{'targetsection'}!=0;                    $b->{'targetsection'}=0;                }            } else {                $c='style="background-color:#f88"';                $b->{'status'}='Unknown';                $b->{'status'}.=': BAG assistance requested!' if $need_bag;                $b->{'targettimestamp'}=time if $b->{'targetsection'}!=-1;                $b->{'targetsection'}=-1;                $need_user=$need_bag=0;            }            if(!($cats{'Category:Revoked Wikipedia bot requests for approval'} // 0)) {                my $inconsistent=0;                $inconsistent=1 if ($tmpl{$tr{'Template:BotRevoked'}} // 0); $inconsistent=1 if(!($cats{'Category:Approved Wikipedia bot requests for approval'} // 0) && ($tmpl{$tr{'Template:BotApproved'}} // $tmpl{$tr{'Template:BotSpeedy'}} // 0)); $inconsistent=1 if(!($cats{'Category:Denied Wikipedia bot requests for approval'} // 0) && ($tmpl{$tr{'Template:BotDenied'}} // 0)); $inconsistent=1 if(!($cats{'Category:Withdrawn Wikipedia bot requests for approval'} // 0) && ($tmpl{$tr{'Template:BotWithdrawn'}} // 0)); $inconsistent=1 if(!($cats{'Category:Expired Wikipedia bot requests for approval'} // 0) && ($tmpl{$tr{'Template:BotExpired'}} // 0)); if($inconsistent){ $need_user=$need_bag=0; $b->{'status'}.=': Inconsistent categories/tags!'; $c='style="background-color:#f88"'; $needbag++; }           }            $b->{'status'}.=': User response needed!' if $need_user; $b->{'status'}.=': BAG assistance requested!' if $need_bag; $c='style="background-color:lightgreen"' if $need_user; $c='style="background-color:#f88"' if $need_bag; $needbag++ if $need_bag; $b->{'color'}=$c; $BRFA{$key}={ %$b, redirected=>1 }; $BRFA{$pg}=$b; $needmoving++ if($b->{'targetsection'}>0 && $b->{'targetsection'}>$b->{'secnum'});

# If a bot has been approved for a trial, remove from the # "check unflagged" list. delete $check_unflagged{ucfirst($b->{'bot'})} if $b->{'targetsection'} > 0; }   }    $api->store->{'BRFA'}=\%BRFA;

my $iter=$api->iterator(       generator    => 'categorymembers',        gcmtitle     => 'Category:Open Wikipedia bot requests for approval',        gcmnamespace => 4,        gcmlimit     => 500,        prop         => 'categories',        cllimit      => 'max',        clcategories => join('|', 'Category:Approved Wikipedia bot requests for approval', 'Category:Denied Wikipedia bot requests for approval', 'Category:Expired Wikipedia bot requests for approval', 'Category:Revoked Wikipedia bot requests for approval', 'Category:Withdrawn Wikipedia bot requests for approval', )   );    while(my $p=$iter->next){ if(!$p->{'_ok_'}){ $api->warn("Failed to retrieve members of Category:Open Wikipedia bot requests for approval: ".$p->{'error'}."\n"); return 60; }       next unless $p->{'title'}=~m{^Wikipedia:Bots/Requests for approval/}; next unless(exists($p->{'categories'}) && @{$p->{'categories'}}>0); my $tok=$api->edittoken($p->{'title'}, EditRedir=>1); if($tok->{'code'} eq 'shutoff'){ $api->warn("Task disabled: ".$tok->{'content'}."\n"); return 300; }       if($tok->{'code'} ne 'success'){ $api->warn("Failed to get edit token for $p->{title}: ".$tok->{'error'}."\n"); next; }       my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'}; my $outtxt=$intxt; my $re=qr# \s*\[\[\s*(?i:Category)\s*:\s*Open Wikipedia bot requests for approval\s*(?>\|.*?(?=\]\]))?\]\]\s* #; $outtxt=~s/\n$re *\n/\n/g; $outtxt=~s/$re//g; if($intxt eq $outtxt){ $api->warn("Couldn't find in $p->{title}, despite it being in the category!"); next; }       my $summary="Removing  from closed BRFA"; $api->log("$summary in $p->{title}"); my $r=$api->edit($tok, $outtxt, $summary, 0, 0); if($r->{'code'} ne 'success'){ $api->warn("Write failed on $p->{title}: ".$r->{'error'}."\n"); next; }   }

# Construct the table @brfas=sort { $a->{'sort'} <=> $b->{'sort'} } values %BRFA; my $txt='{| border="1" class="sortable wikitable plainlinks"'."\n"; $txt.="!Bot Name !! Status !! Created !! Last editor !! Date/Time !! Last BAG editor !! Date/Time\n"; my $listed=0; foreach (@brfas){ next if $_->{'redirected'}; my $bt=uri_escape_utf8(ucfirst($_->{'bot'})); $txt.="|-\n"; $txt.="| $_->{name} (T|C|[/wiki/Special:Log/block?page=User:$bt B]|[/wiki/Special:Log/rights?page=User:$bt F]) \n"; $txt.="|$_->{color}|$_->{status}\n"; $txt.=defined($_->{'created'})?strftime("| %F, %T\n",gmtime $_->{'created'}):"| unknown\n"; my $cls=''; $cls = 'class="MostRecentIsOp"' if $_->{'oprevid'} == $_->{'revid'}; $cls = 'class="MostRecentIsBAG"' if $_->{'bagrevid'} == $_->{'revid'}; $txt.=$_->{'revid'} ? "|$cls| $_->{editby} ||$cls| $_->{edittime}\n" : "| Never edited || n/a\n"; $txt.=$_->{'bagrevid'} ? "| $_->{bageditby} || $_->{bagedittime}\n" : "| Never edited by BAG || n/a\n"; $listed++; }   $txt.="|}";

# Edit the page, if necessary my $tok=$api->edittoken('Wikipedia:BAG/Status', EditRedir=>1); if($tok->{'code'} eq 'shutoff'){ $api->warn("Task disabled: ".$tok->{'content'}."\n"); return 300; }   if($tok->{'code'} ne 'success'){ $api->warn("Failed to get edit token for WP:BAG/Status: ".$tok->{'error'}."\n"); return 60; }   if($txt ne ($tok->{'revisions'}[0]{'slots'}{'main'}{'*'} // '')){ my $summary="Updating table: $listed BRFAs listed, $active active"; $summary.=", 1 needs BAG attention!" if $needbag==1; $summary.=", $needbag need BAG attention!" if $needbag>1; $api->log($summary); my $r=$api->edit($tok, $txt, $summary, 0, 0); if($r->{'code'} ne 'success'){ $api->warn("Write failed on WP:BAG/Status: ".$r->{'error'}."\n"); return 60; }   }

# Any unflagged bots that haven't been approved for a trial yet should be   # checked to see if they are editing without approval. foreach my $u (keys %check_unflagged){ # Damn global interwiki bots. my $res=$api->query(meta=>'globaluserinfo',guiuser=>$u,guiprop=>'groups|merged'); if($res->{'code'} ne 'success'){ $api->warn("Failed to retrieve global user info for $u: ".$res->{'error'}."\n"); return 60; }       $res=$res->{'query'}{'globaluserinfo'}; my $globalbot = (grep($_->{'wiki'} eq 'enwiki', @{$res->{'merged'}//[]}) && grep($_ eq 'Global_bot', @{$res->{'groups'}//[]})) ? 1 : 0;

my $sts=time; my $ts=time; my %u=(quotemeta(ucfirst($u))=>1); my @brfas=; foreach my $b (values %BRFA){ next unless ucfirst($b->{'bot'}) eq $u; push @brfas, $b; $u{quotemeta(ucfirst($b->{'operator'}//'?'))}=1; my $t=$b->{'unflagged edit checked'} // $b->{'created'}; $ts=$t if $t < $ts; }       my $uu=join('|', keys %u); my $re=qr/^User(?: talk)?:(?:$uu)(?:\/|$)/; my $iter=$api->iterator(list=>'usercontribs', ucend=>timestamp2ISO($ts), ucprop=>"title", ucuser=>$u); while(my $p=$iter->next){ if(!$p->{'_ok_'}){ $api->warn("Failed to retrieve contribs for $u: ".$p->{'error'}."\n"); return 60; }            next if $p->{'title'}=~/$re/;

foreach my $b (@brfas){ $b->{'notify_botedited'}|=($b->{'page'} eq $p->{'title'} ? 2 : ($globalbot?0:1)); if($b->{'notify_botedited'} & ~$b->{'notified_botedited'}){ push @need_edit_brfa, $b->{'page'} unless grep $_ eq $b->{'page'}, @need_edit_brfa; }            }             last; }       foreach my $b (@brfas){ $b->{'unflagged edit checked'}=$sts; }   }    $api->store->{'BRFA'}=\%BRFA;

# Notify users and check as necessary foreach my $pg (@need_edit_brfa){ my $n=$pg; $n=~s!^.*/!!;

my $tok=$api->edittoken($pg); if($tok->{'code'} eq 'shutoff'){ $api->warn("Task disabled: ".$tok->{'content'}."\n"); return 300; }       if($tok->{'code'} ne 'success'){ $api->warn("Failed to get edit token for $pg: ".$tok->{'error'}."\n"); next; }       next if exists($tok->{'missing'});

my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'}; my ($fixed_newbot,$notified)=(0,0); my $outtxt=$api->process_templates($intxt, sub {           my $name=shift;            my $params=shift;            shift; # $wikitext            shift; # $data            my $oname=shift;

if($name eq 'Newbot'){ my $have2=0; foreach ($api->process_paramlist(@$params)){ $have2=1 if $_->{'name'} eq '2'; }

my $diff=0; my $ret=""; return undef unless $diff; $fixed_newbot=1; return $ret; }

if($name eq 'Operator assistance needed' || $name eq 'OperatorAssistanceNeeded'){ foreach ($api->process_paramlist(@$params)){ return undef if($_->{'name'} eq '1' && $_->{'value'} ne ''); }               $notified=1; return ""; }

return undef; });       my $notified_botedited=0;        my $nbe = ($api->store->{'BRFA'}{$pg}{'notify_botedited'} & ~$api->store->{'BRFA'}{$pg}{'notified_botedited'});        my @nbesummary=;        if($nbe&4){            if($api->store->{'BRFA'}{$pg}{'bot'}=~/bot(?:\s*\d+|\s+[XVI]+)?$/i){                $outtxt.="\n*  This request specifies the bot account as the operator. A bot may not operate itself; please update the \"Operator\" field to indicate the account of the human running this bot. ~\n";                push @nbesummary, 'noted that the bot is listed as its own operator';                $notified_botedited|=6;            } else {                my $u=$api->store->{'BRFA'}{$pg}{'bot'};                $res=$api->query(list=>'users', ususers=>$u, usprop=>'editcount');                if($res->{'code'} ne 'success'){                    $api->warn("Failed to get edit count for $u: ".$res->{'error'}."\n"); next; }               if(($res->{'query'}{'users'}[0]{'editcount'}//0) < 500){ $outtxt.="\n* The user account this request is for is also listed as the Operator, but the account name does not clearly indicate that the account is a bot and the account has very few edits. Please note that WP:Bot policy states that a bot account's username should make it immediately clear that the account is in fact a bot, which is normally done by having the account name end with the word \"Bot\". Also note that a bot may not operate itself, so the Operator field should identify the account of the human running the bot.  ~\n"; push @nbesummary, 'noted that the request appears to be for a non-bot account from a relatively new editor'; }               $notified_botedited|=7; }           $nbe&=~$notified_botedited;; }       if($nbe&2){ $outtxt.="\n* This bot has edited its own BRFA page. Bot policy states that the bot account is only for edits on approved tasks or trials approved by BAG; the operator must log into their normal account to make any non-bot edits. ~\n"; push @nbesummary, 'noted that the bot has edited its own BRFA page'; $notified_botedited|=2; }       if($nbe&1){ $outtxt.="\n* This bot appears to have edited since this BRFA was filed. Bots may not edit outside their own or their operator's userspace unless approved or approved for trial. ~\n"; push @nbesummary, 'noted that the bot has edited before being approved'; $notified_botedited|=1; }

if($outtxt ne $intxt){ my @summary=; if($notified){ unless($intxt=~/Operator:\s*.*?(?:\[\[\s*(?::\s*)*(?i:User|User[ _]talk)\s*:|\{\{(?:[uU]ser|[bB]otop)\s*\|)\s*([^\]}|]+?)\s*[\]}|]/){ $api->warn("Failed to find operator name in $pg\n"); $api->whine("Cannot notify operator of $n", "I could not find the operator of the bot in $pg in order to notify them of the Operator assistance needed on that BRFA. I look for  with a wikilink to the User or User talk namespace on the same line. Please fix it! Thanks."); next; }               my $user=$1;

push @summary, "notified $user about "; my $r=$api->whine("Your bot request $n", "Someone has marked $pg as needing your input. Please visit that page to reply to the requests. Thanks! ~ To opt out of these notifications, place anywhere on this page. ", Summary=>"Your assistance is needed at $n", Pagename=>"User talk:$user", NoSmallPrint=>1, OptOut=>'operatorassistanceneeded', NoSig=>1); if($r->{'code'} eq 'shutoff'){ $api->warn("Task disabled: ".$r->{'content'}."\n"); return 300; }               if($r->{'code'} eq 'botexcluded'){ $api->warn("Excluded from User talk:$user: ".$r->{'error'}."\n"); my $b=$api->store->{'BRFA'}; $b->{$pg}{'optout-operatorassistanceneeded'}=1; $api->store->{'BRFA'}=$b; next; }               if($r->{'code'} ne 'success'){ $api->warn("Failed to post to User talk:$user: ".$r->{'error'}."\n"); next; }               $api->log("Notified $user about Operator assistance needed on $pg"); }           push @summary, "corrected " if $fixed_newbot; push @summary, @nbesummary; $summary[0]=ucfirst($summary[0]); $summary[$#summary]='and '.$summary[$#summary] if @summary>1; my $summary=join(@summary>2?', ':' ', @summary); $api->log("$summary in $pg"); my $r=$api->edit($tok, $outtxt, $summary, 1, 1); if($r->{'code'} ne 'success'){ $api->warn("Failed to write $pg: ".$r->{'error'}."\n"); next; }           my $b=$api->store->{'BRFA'}; $b->{$pg}{'check_newbot'}=0; $b->{$pg}{'notified_botedited'}|=$notified_botedited; $api->store->{'BRFA'}=$b; }       my $b=$api->store->{'BRFA'}; $api->store->{'BRFA'}=$b; }

if($needmoving){ my $tok=$api->edittoken('Wikipedia:Bots/Requests for approval', EditRedir=>1); if($tok->{'code'} eq 'shutoff'){ $api->warn("Task disabled: ".$tok->{'content'}."\n"); return 300; }       if($tok->{'code'} ne 'success'){ $api->warn("Failed to get edit token for WP:Bots/Requests for approval: ".$tok->{'error'}."\n"); return 60; }       my $txt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'} // ''; my @sec=$api->split_sections($txt, "1"); if(@sec!=6 ||          $sec[1]{'title'} ne 'Current requests for approval' ||           $sec[2]{'title'} ne 'Bots in a trial period' ||           $sec[3]{'title'} ne 'Bots that have completed the trial period' ||           $sec[4]{'title'} ne 'Denied requests' ||           $sec[5]{'title'} ne 'Expired/withdrawn requests'){ $api->warn("Failed to parse WP:BRFA\n"); $api->whine("WP:BRFA cannot be processed", "The BRFA list cannot be processed. Most likely, someone has screwed around with the section headers. Either fix it back to the old layout, or update me to handle the new version. Thanks."); return 60; }

my %BRFA=; foreach my $b (values %{$api->store->{'BRFA'}}){ $BRFA{$b->{'page'}}=$b; }       my %tosec=; my @s=(           0,$sec[1],            1,$sec[2],            2,$sec[3],        ); my @summary=; my $archived_denied=0; my $archived_expired=0; my %cts=; while(@s){ my $secnum=shift @s; my $s=shift @s; $s->{'body'}=$api->process_templates($s->{'body'}, sub {               my $name=shift;                my $params=shift;                shift; # $wikitext                shift; # $data                my $oname=shift;

return undef unless $name eq 'BRFA';

my %p=; foreach ($api->process_paramlist(@$params)){ $p{$_->{'name'}}=$_->{'value'}; }               $p{1}=~s/[\s_]/ /g; $p{1}=~s/^ | $//g; $p{2}=~s/[\s_]/ /g; $p{2}=~s/^ | $//g; my $nm=$p{1}.(($p{2} ne )?' '.$p{2}:); my $pg="Wikipedia:Bots/Requests for approval/$nm"; my $b=$BRFA{$pg} // return undef;

my $ret=undef; if($p{1} ne $b->{'bot'} || $p{2} ne $b->{'reqnum'}){ $ret=""; $cts{'corrected s'}++; push @summary, "correct for $nm"; }

# No need to move if it's already where it belongs return $ret if $b->{'targetsection'}==$secnum; # Never move to "Open" return $ret if $b->{'targetsection'}<=0; # Give a human time to move it? #XXX return $ret if $b->{'targettimestamp'}>=time-3600; push @{$tosec{$b->{'targetsection'}}},$b; return ''; });       }

if(exists($tosec{1})){ unless($sec[2]{'body'}=~/-->\s*\n/){ $api->warn("Failed to find trial insertion point in WP:BRFA\n"); $api->whine("WP:BRFA cannot be processed", "I could not find the insertion point in WP:BRFA. Please fix it. Thanks."); return 60; }           foreach my $b (@{$tosec{1}}) { $sec[2]->{'body'}=~s/-->\s*\n/-->\n\n/; my $back=($b->{'secnum'}>1)?' back':''; push @summary, $b->{'bot'}.' '.$b->{'reqnum'}.$back.' to trial'; $cts{'to trial'}++; }       }        if(exists($tosec{2})){ unless($sec[3]{'body'}=~/-->\s*\n/){ $api->warn("Failed to find trial-complete insertion point in WP:BRFA\n"); $api->whine("WP:BRFA cannot be processed", "I could not find the insertion point in WP:BRFA. Please fix it. Thanks."); return 60; }           foreach my $b (@{$tosec{2}}) { $sec[3]->{'body'}=~s/-->\s*\n/-->\n\n/; push @summary, $b->{'bot'}.' '.$b->{'reqnum'}.' trial complete'; $cts{'trial complete'}++; }       }        if(exists($tosec{4})){ unless($sec[4]{'body'}=~/-->\s*\n/){ $api->warn("Failed to find denied insertion point in WP:BRFA\n"); $api->whine("WP:BRFA cannot be processed", "I could not find the insertion point in WP:BRFA. Please fix it. Thanks."); return 60; }           foreach my $b (@{$tosec{4}}) { $sec[4]->{'body'}=~s/-->\s*\n/-->\n\n/; push @summary, $b->{'bot'}.' '.$b->{'reqnum'}.' denied'; $cts{'denied'}++; }

my $seen=0; $sec[4]->{'body'}=$api->process_templates($sec[4]->{'body'}, sub {               my $name=shift;                my $params=shift;

return undef unless $name eq 'BRFA'; return undef if $seen++<15; my $ts=0; foreach ($api->process_paramlist(@$params)){ $ts=str2time($_->{'value'})//0 if $_->{'name'} eq 4; }               return undef if $ts >= time-7*86400; $archived_denied++; return ''; });       }        if(exists($tosec{5})){            unless($sec[5]{'body'}=~/-->\s*\n/){                $api->warn("Failed to find withdrawn/expired insertion point in WP:BRFA\n");                $api->whine("WP:BRFA cannot be processed", "I could not find the insertion point in WP:BRFA. Please fix it. Thanks.");                return 60;            }            foreach my $b (@{$tosec{5}}) {                my $s = $b->{'status'};                $s =~ s/:.*//; # Avoid Special:Diff/1094004967                $sec[5]->{'body'}=~s/-->\s*\n/-->\n\n/;                push @summary, $b->{'bot'}.' '.$b->{'reqnum'}.' '.lc($b->{'status'});                $cts{lc($b->{'status'})}++;            }

my $seen=0; $sec[5]->{'body'}=$api->process_templates($sec[5]->{'body'}, sub {               my $name=shift;                my $params=shift;

return undef unless $name eq 'BRFA'; return undef if $seen++<15; my $ts=0; foreach ($api->process_paramlist(@$params)){ $ts=str2time($_->{'value'})//0 if $_->{'name'} eq 4; }               return undef if $ts >= time-7*86400; $archived_expired++; return ''; });       }

if(exists($tosec{3})){ my $tok2=$api->edittoken('Wikipedia:Bots/Requests for approval/Approved', EditRedir=>1, links=>{namespace=>4}); if($tok2->{'code'} eq 'shutoff'){ $api->warn("Task disabled: ".$tok2->{'content'}."\n"); return 300; }           if($tok2->{'code'} ne 'success'){ $api->warn("Failed to get edit token for WP:Bots/Requests for approval/Approved: ".$tok2->{'error'}."\n"); return 60; }           my $txt2=$tok2->{'revisions'}[0]{'slots'}{'main'}{'*'} // ''; unless($txt2=~/-->\s*\n/){ $api->warn("Failed to find insertion point in WP:Bots/Requests for approval/Approved\n"); $api->whine("WP:Bots/Requests for approval/Approved cannot be processed", "I cannot find the insertion point in WP:Bots/Requests for approval/Approved. Please fix it. Thanks."); return 60; }           my %l=map { $_->{'title'}=>1 } @{$tok2->{'links'}}; my %u=; foreach my $b (@{$tosec{3}}) { push @summary, $b->{'bot'}.' '.$b->{'reqnum'}.' '.lc($b->{'status'}); $cts{lc($b->{'status'})}++; $u{ucfirst($b->{'bot'})}=0 unless exists($l{$b->{'page'}}); }           $api->process_templates($txt2, sub {                my $name=shift;                my $params=shift;                return undef unless $name eq 'BRFA';                foreach ($api->process_paramlist(@$params)){                    $u{ucfirst($_->{'value'})}=0 if $_->{'name'} eq '1';                }                return undef;            }); my $res=$api->query(list=>'users',ususers=>join('|',keys %u),usprop=>'groups'); if($res->{'code'} ne 'success'){ $api->warn("Failed to get user info for users ".join(', ',keys %u).": ".$res->{'error'}."\n"); return 60; }           foreach my $u (@{$res->{'query'}{'users'}}) { $u{$u->{'name'}}=grep $_ eq 'bot', @{$u->{'groups'}//[]}; }           my $new_brfas=''; my @summary2=; my ($totalct,$needct,$nowct)=(0,0,0); foreach my $b (@{$tosec{3}}) { next if exists($l{$b->{'page'}}); $totalct++; if($u{ucfirst($b->{'bot'})} // 0){ $new_brfas.="$b->{bot}\n"; push @summary2, $b->{'bot'}.' '.$b->{'reqnum'}.' '.lc($b->{'status'}).', has flag'; } else { $new_brfas.="\n"; push @summary2, $b->{'bot'}.' '.$b->{'reqnum'}.' '.lc($b->{'status'}).', NEEDS FLAG'; $needct++; }           }            $txt2=$api->process_templates($txt2, sub {                my $name=shift;                my $params=shift;                return undef unless $name eq 'BRFA';                my ($u,$rn)=(,);                foreach ($api->process_paramlist(@$params)){                    $u=$_->{'value'} if $_->{'name'} eq '1';                    $rn=$_->{'value'} if $_->{'name'} eq '2';                }                return undef unless($u{$u} // 0);                $nowct++;                my $s="$u now flagged";                push @summary2, $s unless grep $_ eq $s, @summary2;                return "$u";            });

$txt2=~s/\s*\n\*\s*\n/\n/g; $txt2=~s/-->\s*\n/-->\n$new_brfas/;

# Archive any BRFAs that need archiving my %archive=; my $archivect=0; my $seen=0; my $didnoinclude=0; my @lines=split(/\n/, $txt2); $txt2=''; foreach my $l (@lines){ next if $seen && $l=~/^ \s*$/; my $archive=0; if($l=~/^(?:\s*\{\{(?:BRFA|subst:BRFAA)\s*(?:\|[^|]*){3}\|\s*|\*\s*\{\{botlinks\|[^\}]*\}\} ''Approved\s+)(|\d{2}:\d{2}, \d{1,2} [A-Z][a-z]+ (\d{4}) \(UTC\))/){ my ($t,$y)=($1,$2); $t=($t eq '' ? time : str2time($t)); if($seen++>=30 && defined($t) && $t<time-7*86400){ if(!$didnoinclude){ $txt2.=" \n"; $didnoinclude=1; }                       $archive=$y-2004 if $tlog("$summary in WP:Bots/Requests for approval/Approved"); if(length($summary)>500){ @summary2=; push @summary2, "$totalct approved, ".($needct?"$needct NEED FLAG":"all are flagged") if $totalct; push @summary2, "$nowct now flagged" if $nowct; push @summary2, "$archivect archived" if $archivect; $summary=ucfirst(join('; ', @summary2))." [too many to list]"; }               my $r=$api->edit($tok2, $txt2, $summary, 0, 0); if($r->{'code'} ne 'success'){ $api->warn("Failed to write WP:Bots/Requests for approval/Approved: ".$r->{'error'}."\n"); return 60; }           }

# Save to-be-archived BRFAs for later if($archivect){ my $a=$api->store->{'BRFAA archives'}; while(my ($k,$v)=each(%archive)){ $a->{$k}=$v.($a->{$k}//''); }               $api->store->{'BRFAA archives'}=$a; }       }

push @summary, "archived $archived_denied denied request".($archived_denied==1?'':'s') if $archived_denied; push @summary, "archived $archived_expired expired/withdrawn request".($archived_expired==1?'':'s') if $archived_expired;

if(@summary){ $txt=$api->join_sections(@sec); my $summary=join('; ', @summary); $summary=~s/\s+/ /g; if(length($summary)>500){ @summary=; for my $k (sort keys %cts) { push @summary, $cts{$k}.' '.$k; }               push @summary, "archived $archived_denied denied request".($archived_denied==1?'':'s') if $archived_denied; push @summary, "archived $archived_expired expired/withdrawn request".($archived_expired==1?'':'s') if $archived_expired; $summary=join(', ', @summary)." [too many to list]"; }           $api->log("$summary in WP:Bots/Requests for approval"); my $r=$api->edit($tok, $txt, $summary, 0, 1); if($r->{'code'} ne 'success'){ $api->warn("Failed to write WP:Bots/Requests for approval: ".$r->{'error'}."\n"); return 60; }       }        my $b=$api->store->{'BRFA'}; $api->store->{'BRFA'}=$b; }

# Check for newly-flagged bots in BRFA/A {       my $tok2=$api->edittoken('Wikipedia:Bots/Requests for approval/Approved', EditRedir=>1); if($tok2->{'code'} eq 'shutoff'){ $api->warn("Task disabled: ".$tok2->{'content'}."\n"); return 300; }       if($tok2->{'code'} ne 'success'){ $api->warn("Failed to get edit token for WP:Bots/Requests for approval/Approved: ".$tok2->{'error'}."\n"); return 60; }       my $txt2=$tok2->{'revisions'}[0]{'slots'}{'main'}{'*'} // ''; my %u=; $api->process_templates($txt2, sub {           my $name=shift;            my $params=shift;            return undef unless $name eq 'BRFA';            foreach ($api->process_paramlist(@$params)){                $u{ucfirst($_->{'value'})}=0 if $_->{'name'} eq '1';            }            return undef;        }); last unless %u; my $res=$api->query(list=>'users',ususers=>join('|',keys %u),usprop=>'groups'); if($res->{'code'} ne 'success'){ $api->warn("Failed to get user info for users ".join(', ',keys %u).": ".$res->{'error'}."\n"); return 60; }       foreach my $u (@{$res->{'query'}{'users'}}) { $u{$u->{'name'}}=grep $_ eq 'bot', @{$u->{'groups'}//[]}; }       my @summary2=; my $nowct=0; $txt2=$api->process_templates($txt2, sub {           my $name=shift;            my $params=shift;            return undef unless $name eq 'BRFA';            my ($u,$rn,$dt)=(,,'');            foreach ($api->process_paramlist(@$params)){                $u=ucfirst($_->{'value'}) if $_->{'name'} eq '1';                $rn=$_->{'value'} if $_->{'name'} eq '2';                $dt=$_->{'value'} if $_->{'name'} eq '4';            }            return undef unless($u{$u} // 0);            $nowct++;            my $s="$u now flagged";            push @summary2, $s unless grep $_ eq $s, @summary2;            return "$u";        }); if(@summary2){ my $summary=join('; ', @summary2); $summary=~s/\s+/ /g; $summary="$nowct now flagged [too many to list]" if length($summary)>500; $api->log("$summary in WP:Bots/Requests for approval/Approved"); my $r=$api->edit($tok2, $txt2, $summary, 0, 0); if($r->{'code'} ne 'success'){ $api->warn("Failed to write WP:Bots/Requests for approval/Approved: ".$r->{'error'}."\n"); return 60; }       }    }

# Archive from WP:BRFA/A {       my $a=$api->store->{'BRFAA archives'}; foreach my $k (keys %$a) { my $tok2=$api->edittoken("Wikipedia:Bots/Requests for approval/Approved/Archive $k", EditRedir=>1); if($tok2->{'code'} eq 'shutoff'){ $api->warn("Task disabled: ".$tok2->{'content'}."\n"); return 300; }           if($tok2->{'code'} ne 'success'){ $api->warn("Failed to get edit token for WP:Bots/Requests for approval/Approved/Archive $k: ".$tok2->{'error'}."\n"); return 60; }           my $txt2=$tok2->{'revisions'}[0]{'slots'}{'main'}{'*'} // "\n"; my $v=$a->{$k}; unless($txt2=~s/\{\{archive\}\}\n/\n$v/){ $api->warn("Failed to find insertion point in WP:Bots/Requests for approval/Approved/Archive $k\n"); $api->whine("WP:Bots/Requests for approval/Approved/Archive $k cannot be processed", "I cannot find the insertion point in WP:Bots/Requests for approval/Approved/Archive $k. Please fix it, or fix me. Thanks."); next; }           my $summary='Archiving old approvals'; $api->log("$summary in WP:Bots/Requests for approval/Approved/Archive $k"); my $r=$api->edit($tok2, $txt2, $summary, 0, 0); if($r->{'code'} eq 'success'){ delete $a->{$k}; $api->store->{'BRFAA archives'}=$a; } else { $api->warn("Failed to write WP:Bots/Requests for approval/Approved/Archive $k: ".$r->{'error'}."\n"); }       }    }

# Check if WP:BRFAAA needs updating {       $res=$api->query([],            list        =>'allpages',             apprefix    => 'Bots/Requests for approval/Approved/Archive ',            apnamespace => 4,            aplimit     => 'max',        ); if($res->{'code'} ne 'success'){ $api->warn("Failed to get list of WP:BRFA/A archives: ".$res->{'error'}."\n"); return 60; }       my @pages=; foreach my $p (map $_->{'title'}, @{$res->{'query'}{'allpages'}}) { push @pages, $1 if $p=~m!^Wikipedia:Bots/Requests for approval/Approved/Archive (\d+)$!; }       @pages=sort { $a <=> $b } @pages;

$tok=$api->edittoken('Wikipedia:Bots/Requests for approval/Approved/Archives', links => { namespace => 4 }); if($tok->{'code'} ne 'success'){ $api->warn("Failed to get edit token and links for WP:BRFAAA: ".$tok->{'error'}."\n"); return 60; }       my $txt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'}; my @links=map $_->{'title'}, @{$tok->{'links'}};

my $any=0; $txt=~s/\s*$/\n/; foreach my $p (@pages){ my $pg="Wikipedia:Bots/Requests for approval/Approved/Archive $p"; next if grep $_ eq $pg, @links; my $y=$p+2004; $txt =~ s{}{*Archive $p: 1 January $y – 31 December $y\n$&}; $any=1; }

if($any){ my $r=$api->edit($tok, $txt, "Updating list of archives", 0, 0); if($r->{'code'} ne 'success'){ $api->warn("Failed to write WP:Bots/Requests for approval/Approved/Archives: ".$r->{'error'}."\n"); }       }    }

# Check again in 5 minutes. return 300; }

1;