User:AnomieBOT/source/tasks/IFDCloser.pm

\" with the actual name of the file. You'll also want to put the name of the uploader just after \" \", and your reason just after \" \". Feel free to just replace this entire section with the corrected template. If you are still having trouble, ask for help at WT:FFD or at my talk page. ~}}\n".$s->{'body'}."\n\n"; $ct--; $s->{'isopen'} = 0; push @closed, "File:$img"; } elsif($img eq ''){ $s->{'body'}=~s/^\s*|\s*$//g; $s->{'body'}="Erroneous Nomination. No image name is specified. Feel free to just replace this entire section with the corrected template. If you are still having trouble, ask for help at WT:FFD or at my talk page. ~\n".$s->{'body'}."\n\n"; $ct--; $s->{'isopen'} = 0; push @closed, "File:$img"; } else { $img{"File:$img"}=$s; }       }        next if($ct==0 && @closed==0 && !$fixedhead && !$fixed);

# Check if the unclosed files still exist. If not, close the # discussion. my @titles=keys %img; while(@titles){ my @img=splice(@titles, 0, 500); $res=$api->query(               titles => join('|', @img),                prop   => 'info|imageinfo',                iiprop => 'canonicaltitle',            ); if($res->{'code'} ne 'success'){ $api->warn("Failed to retrieve file info for $title: ".$res->{'error'}."\n"); return 60; }           my %map=; %map=map { $_->{'to'}, $_->{'from'} } @{$res->{'query'}{'normalized'}} if exists($res->{'query'}{'normalized'}); PAGE: foreach (values %{$res->{'query'}{'pages'}}){ # File exists here? next PAGE if $_->{'imagerepository'} eq 'local' && $_->{'imageinfo'}[0]{'canonicaltitle'} eq $_->{'title'};

my $img=$api->apply_redirect_map( $_->{'title'}, \%map ); if(!exists($img{$img})){ my $d=Dumper($_); $d=~s/\s+/ /g; $api->warn("How odd, this was apparently returned even though it wasn't requested: $d\n"); next PAGE; }

my $msg=undef; # First, check for a deletion. my $r=$api->query(                   letitle => $_->{'title'},                    list    => 'logevents',                    letype  => 'delete',                    lelimit => 1,                    leprop  => 'user|timestamp|comment',                ); if($r->{'code'} ne 'success'){ $api->warn("Failed to retrieve logs for ".$_->{'title'}.": ".$r->{'error'}."\n"); return 60; }               if(exists($r->{'query'}{'logevents'}[0])){ my $log=$r->{'query'}{'logevents'}[0];

# Skip for now if it was deleted within the past hour, to                   # give the closing admin a chance to close it themself. my $t=$api->ISO2timestamp($log->{'timestamp'}); next PAGE if(time-$t<3600);

# Check whether the deletion log entry could reasonably # belong to this FFD: the deletion log entry should be                   # dated on or after the FFD page date. Give them a day of                   # leeway just in case. $t=_date_add(_make_date($t),1,0,0); if(_cmp_date($t,$date)>=0){ # Close it! $msg="Delete; deleted"; if($log->{'comment'}=~/CSD(?:#|\]\] | )([GIF]\d+)/i ||                          $log->{'comment'}=~/db-([gif]\d+)/){ my $c=uc($1); $msg.=" as $c"; } elsif($log->{'comment'}=~/db-([a-z])/ && exists($db{$1})){ my $c=$db{$1}; $msg.=" as $c"; } elsif($log->{'comment'}=~/\x7b\x7b\s*isd\s*[|\x7d]/){ $msg.=" as F1"; }                       $msg.=" by "; $msg.=' A file with this name on Commons is now visible.' if $_->{'imagerepository'} eq 'shared'; }               }                if(!defined($msg) && $_->{'imageinfo'}[0]{'canonicaltitle'} ne $_->{'title'} && exists($_->{'redirect'})){ # It's a redirect. Is it because it was moved, or because # it was created that way?

# Get target my %targets=$api->resolve_redirects($_->{'title'}); if(exists($targets{''})){ $api->log("Could not resolve redirect ".$_->{'title'}.": ".$targets{''}{'error'}); next PAGE; }                   my $target=$targets{$_->{'title'}};

my $r=$api->query(                       letitle => $_->{'title'},                        list    => 'logevents',                        letype  => 'move',                        lelimit => 1,                    ); if($r->{'code'} ne 'success'){ $api->warn("Failed to retrieve logs for ".$_->{'title'}.": ".$r->{'error'}."\n"); return 60; }                   if(exists($r->{'query'}{'logevents'}[0])){ my $log=$r->{'query'}{'logevents'}[0];

# Skip for now if it was moved within the past hour, # to see if the mover closes it themself. my $ts=$api->ISO2timestamp($log->{'timestamp'}); next PAGE if(time-$ts<3600);

# Check whether the move log entry could reasonably # belong to this FFD: the move log entry should be                       # dated on or after the FFD page date. Give them a day # of leeway just in case. my $t=_date_add(_make_date($ts),1,0,0); if(_cmp_date($t,$date)>=0){ # Yes: comment and change the title, but don't                           # actually close. my $from=$log->{'title'}; my $to=$log->{'params'}{'target_title'}; my $user=$log->{'user'}; my $ts=strftime("%H:%M, %d %B %Y (UTC)", gmtime $ts); $ts=~s/, 0/, /; $img{$img}{'title'} = "$target"; $img{$img}{'body'}=~s/\s*$//; $img{$img}{'body'}.="\n* Note: The file was moved from $from to $to by at $ts"; $img{$img}{'body'}.="; it now redirects to $target" if $target ne $to; $img{$img}{'body'}.=". ~\n"; push @moved, "$from→$target"; next PAGE; }                   }

# Redirect, but not because of a move. Close it. my $iter=$api->iterator(                       titles    => $_->{'title'},                        prop      => 'revisions',                        rvprop    => 'timestamp|content',                        rvslots   => 'main',                        rvsection => 0,                        rvlimit   => 5,                    ); my $ts=0; my $redir_re=$api->redirect_regex; while(my $res=$iter->next){ if(!$res->{'_ok_'}){ $api->warn("Could not retrieve revisions from iterator: ".$res->{'error'}."\n"); return 60; }                       $res=$res->{'revisions'}[0]; last unless $res->{'slots'}{'main'}{'*'}=~$redir_re; $ts=$api->ISO2timestamp($res->{'timestamp'}); }

# WTF? if($ts==0){ my $t=$_->{'title'}; $api->warn("$t claims it's a redirect, but no #REDIRECT found?\n"); $api->whine("$t confuses me", "When checking $t, the API prop=info reported it's a redirect, but the top revision does not match $redir_re. Which probably means my code needs fixing."); next PAGE; }

# Skip if it was redirectified too recently next PAGE if(time-$ts<3600);

if($target=~/^(?:File|Image):/i){ $msg="Redirect. The file nominated is a redirect. If you are trying to nominate the redirect for deletion, list it at WP:RFD. If you are trying to nominate $target for discussion, please nominate it by that name."; } else { $msg="Bad Redirect. The file nominated is a redirect to $target, which is outside the File namespace. Please fix it, or tag it with db-imagepage if it cannot be fixed."; }               }                if(!defined($msg)){ # If we get here, there was no (valid) deletion log for # this file, but it still doesn't exist locally. if(!exists($_->{'missing'}) && $_->{'imagerepository'} eq 'shared'){ $msg="Wrong forum. The file is on Commons, but a local image description page exists. If you are wanting the image deleted, please nominate it for deletion on Commons. If you are requesting deletion of the local image description page only, use db-nofile if possible; if that is not possible, list it at WP:MFD."; } elsif($_->{'imagerepository'} eq 'shared'){ $msg="Wrong forum. The file is on Commons. Please nominate it for deletion there if you still feel it should be deleted."; } elsif(!exists($_->{'missing'})){ $msg="Wrong forum. No file exists by this name, but a local image description page does exist. If you are trying to nominate this description page for deletion, use db-imagepage if possible; if that is not possible, list it at WP:MFD. If the file name in the header contains a typo, feel free to correct the typo and un-close this discussion."; } else { $msg="Image does not exist. If the file name in the header contains a typo, feel free to correct the typo and un-close this discussion."; }               }                next if !defined($msg); $img{$img}{'body'}=~s/^\s+|\s+$//g; $img{$img}{'body'}="$msg ~\n".$img{$img}{'body'}."\n\n"; $ct--; $img{$img}{'isopen'} = 0; push @closed, "".$_->{'title'}.""; }       }

# Mark for entry on the list of old FFDs, if applicable if($ct>0){ if(_cmp_date($date,$sevendays)<0){ unshift @old, map { "* $title\n" } grep { $_->{'isopen'} // 0 } @sections; unshift @oldsumm, [@$date]; }           $new_start=[@$date]; }

# Need to edit? next unless(@closed || @moved || $fixed || $fixedhead);

# Processed, now reconstruct the page $outtxt=$api->join_sections(@sections);

# Subst templates, if necessary my $subst=0; $outtxt=$api->process_templates($outtxt, sub {           my $name=shift;            shift; #$params            my $wikitext=shift;

return undef unless exists($tosubst{"Template:$name"}); $subst++; $wikitext=~s/^\{\{\s*/\{\{subst:/; return $wikitext; });

# Create summary my @summary=; if($fixedhead){ if(exists($tok->{'missing'})){ push @summary, "new discussion page: ".$date->[2].' '.$months[$date->[1]].' '.$date->[0]; } else { push @summary, "fix page header"; }       }        push @summary, "subst  and/or " if $subst>0; push @summary, 'move closing box'.(($fixed>1)?'es':'').' per WP:DPR' if $fixed; push @summary, 'close discussions for deleted/nonexistent files: '.join(', ', @closed) if @closed; push @summary, 'rename discussions for moved files: '.join(', ', @moved) if @moved; my $summary='(BOT) '.ucfirst(join('; ', @summary)).$screwup; $api->log("$summary in $title"); if(length($summary)>500){ @summary=; if($fixedhead){ if(exists($tok->{'missing'})){ push @summary, "new discussion page: ".$date->[2].' '.$months[$date->[1]].' '.$date->[0]; } else { push @summary, "fix page header"; }           }            push @summary, "subst  and/or " if $subst>0; push @summary, 'move closing box'.(($fixed>1)?'es':'').' per WP:DPR' if $fixed; push @summary, 'close discussions for deleted/nonexistent files: [too many to list]' if @closed; push @summary, 'rename discussions for moved files: [too many to list]' if @moved; $summary='(BOT) '.ucfirst(join('; ', @summary)).$screwup; }       my $r=$api->edit($tok, $outtxt, $summary, 0, 1); if($r->{'code'} ne 'success'){ $api->warn("Write failed on $title: ".$r->{'error'}."\n"); return 60; }   }

# Ok, we've processed all the subpages. Now update the list of old FFDs on   # the main page. my $title='Wikipedia:Files for discussion/Old unclosed discussions'; $api->log("Updating Old discussions list on $title"); my $tok=$api->edittoken($title); 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 $title: ".$tok->{'error'}."\n"); return 60; }   my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'} // ''; $intxt=~s/Last updated .*?/Last updated /; $intxt=~s/\s*$/\n/; my $outtxt="This is a list of unclosed FfDs over 7 days old. It is automatically maintained by a bot, but humans are free to remove lines when closing discussions if they'd like. Last updated .\n\n"; if ( @old ) { $outtxt .= join( '', @old ); } else { $outtxt .= "* None at this time\n"; }   if($intxt ne $outtxt){ my $summary; if(@oldsumm){ my $m=0; my @oldsumm2=map { my $ret; if($_->[1]!=$m){ $m=$_->[1]; $ret=substr($months[$_->[1]],0,3).' '.$_->[0]; } else { $ret=$_->[0]; }               $ret } @oldsumm; $oldsumm2[-1].='.'; $summary='(BOT) Updating discussions: '.join(', ', @oldsumm2).$screwup; $api->log("$summary in $title"); $summary='(BOT) Updating discussions: major backlog!'.$screwup if length($summary)>500; } else { $summary='(BOT) Updating discussions: no old discussions'.$screwup; }       my $r=$api->edit($tok, $outtxt, $summary, 0, 1); if($r->{'code'} ne 'success'){ $api->warn("Write failed on $title: ".$r->{'error'}."\n"); return 60; }   }

# Create redirect after rename {       my $date = _make_date(time+3600); my $enddate=[17,11,2015]; if(_cmp_date($enddate,$date)>=0){ my $re=$api->redirect_regex; my $redir='Wikipedia:Files for deletion/'.$date->[2].' '.$months[$date->[1]].' '.$date->[0]; my $target='Wikipedia:Files for discussion/'.$date->[2].' '.$months[$date->[1]].' '.$date->[0]; my $tok=$api->edittoken($redir, 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 $redir: ".$tok->{'error'}."\n"); return 60; }           next unless exists($tok->{'missing'}); $api->log("Creating FFD redirect for recent rename: $redir → $target"); my $outtxt = "#REDIRECT $target\n\nPer consensus, \"Files for deletion\" has been renamed \"Files for discussion\". These redirects will be created by the bot until 2015-11-17 to ease the transition."; my $r=$api->edit($tok, $outtxt, "Creating FFD redirect for recent rename", 0, 1); if($r->{'code'} ne 'success'){ $api->warn("Write failed on $redir: ".$r->{'error'}."\n"); return 60; }       }    }

# Save checked revision $self->{'lasttime'}=$starttime; $self->{'broken'}=$broken; $api->store->{'startdate'}=$new_start; $api->store->{'lasttime'}=$starttime; $api->store->{'broken'}=$broken;

my $dt=($self->{'broken'}?300:3600); $t=82800-($starttime%86400); return $starttime+$t-time if($t>0 && $t<$dt); $t=86400-($starttime%86400); return $starttime+$t-time if($t>0 && $t<$dt); return $starttime+$dt-time; }

sub _make_date { my $t=shift || time; if(ref($t) eq 'ARRAY'){ return _fix_date([@$t]); } else { my @t=gmtime($t); @t=@t[3..5]; $t[1]+=1; $t[2]+=1900; return [@t]; } }

sub _date_add { my @t=@{$_[0]}; $t[0]+=$_[1]; $t[1]+=$_[2]; $t[2]+=$_[3]; return _fix_date([@t]); }

sub _fix_date { my $t=shift; my @t=gmtime(timegm(0,0,0,$t->[0],$t->[1]-1,$t->[2]-1900)); @t=@t[3..5]; $t[1]+=1; $t[2]+=1900; return [@t]; }

sub _cmp_date { my $a=shift; my $b=shift; my $x;

$x=$a->[2]-$b->[2]; $x=$a->[1]-$b->[1] if $x==0; $x=$a->[0]-$b->[0] if $x==0; return $x; }

sub _makepagehead { my $api = shift; my $title = shift; my $date = shift;

my $res = $api->query(       action => 'parse',        title => $title,        text =>  . $date->[2] . ,        onlypst => 1,        formatversion => 2,    ); return ( undef, $res->{'error'} ) if $res->{'code'} ne 'success';

my $txt = $res->{'parse'}{'text'}; $txt =~ s/\s*$/\n/s;

return ($txt, undef); }

1;