User:AnomieBOT/source/tasks/POTDPageDeleter.pm

package tasks::POTDPageDeleter;

=pod

=begin metadata

Bot:     AnomieBOT III
Task:    POTDPageDeleter
BRFA:    Wikipedia:Bots/Requests for approval/AnomieBOT III 5
Status:  Approved 2019-03-17
Created: 2019-03-01

Delete Template:POTD protected/YYYY-MM-DD (per [[WP:CSD#G6]]) 30 days after the
listed date, and remove the "See also" link from the corresponding
Template:POTD/YYYY-MM-DD.

=end metadata

=cut

use utf8;
use strict;

use AnomieBOT::Task;
use vars qw/@ISA/;
@ISA=qw/AnomieBOT::Task/;

use POSIX qw/strftime/;
use Data::Dumper;

my $screwup;

my @reasons = (
    '[[WP:CSD#G6|G6]]: Obsolete POTD protected subpage (older than 30 days).',
    '[[WP:CSD#G8|G8]]: Subpage of a deleted page.',
    '[[WP:CSD#G8|G8]]: Talk page of a deleted page.',
);

sub new {
    my $class=shift;
    my $self=$class->SUPER::new;
    bless $self, $class;
    return $self;
}

=pod

=for info
Approved 2019-03-17<br />[[Wikipedia:Bots/Requests for approval/AnomieBOT III 5]]

=cut

sub approved {
    return 500;
}

sub run {
    my ($self, $api)=@_;

    $api->task('POTDPageDeleter', 0, 10, qw(d::Talk d::Nowiki));

    $screwup='If this bot is malfunctioning, please report it at [[User:'.$api->user.'/shutoff/POTDPageDeleter]]';

    my $endtime = time() + 300;

    my @when = gmtime;
    $when[3] -= 30;
    my $when = strftime( '%F', @when );

    # Main templates first, and their sub and talk pages.
    my $iter = $api->iterator(
        list => 'allpages',
        apnamespace => 10,
        apprefix => 'POTD protected/',
        aplimit => 'max',
    );
    while(my $p=$iter->next){
        return 0 if $api->halting;
        return 0 if time() >= $endtime; # Give other tasks a chance to run

        if(!$p->{'_ok_'}){
            $api->warn("Failed to retrieve POTD pages for deletion: ".$p->{'error'}."\n");
            return 60;
        }

        my $title = $p->{'title'};
        next unless $title =~ m{^Template:POTD protected/(\d{4}-\d{2}-\d{2})$};
        my $dt = $1;
        next unless $dt lt $when;

        my ($res, $key, $reason) = $self->do_delete( $api, $title, 0 );
        if ( $res ) {
            return $res if $res > 0;
            next;
        }

        my $title2 = "Template:POTD/$dt";
        my $tok=$api->edittoken( $title2, EditRedir => 1, links => { titles => $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 $title2: " . $tok->{'error'} . "\n" );
            return 60;
        }
        if ( exists( $tok->{'missing'} ) ) {
            $api->warn( "WTF? $title2 is missing!\n" );
            return 60;
        }
        next unless @{$tok->{'links'} // []};

        my $intxt = $tok->{'revisions'}[0]{'slots'}{'main'}{'*'};
        my $outtxt = $intxt;
        $outtxt =~ s!\n\*\s*\[\[Template:POTD(?:[ _]protected|\{\{#ifeq:\{\{BASEPAGENAME\}\}\|POTD protected\|\|(?:_|&#32;)protected\}\})/(?:\{\{SUBPAGENAME\}\}|$dt)(?:[/|][^]]*)?\]\][ \t]*!!g;
        $outtxt =~ s!\n==\s*See also\s*==\s*(?=(?:\[\[Category:[^]]+\]\]\s*|<\!--(?>.*?-->)\s*)*(?:</noinclude>|$))!\n!;
        $outtxt =~ s!<noinclude>\s*</noinclude>\s*$!!;
        if ( $intxt ne $outtxt ) {
            $api->log( "Removing reference to [[Template:POTD protected/$dt]] from $title2" );
            $res = $api->edit( $tok, $outtxt, "Removing reference to deleted [[Template:POTD protected/$dt]]. $screwup", 0, 1 );
            if($res->{'code'} ne 'success'){
                $api->warn( "Write failed on $title2: " . $res->{'error'} . "\n" );
                return 60;
            }
        } else {
            $api->warn( "Failed to find link to remove in $title2\n" );
        }
    }

    # Any leftover subpages.
    $iter = $api->iterator(
        list => 'allpages',
        apnamespace => 10,
        apprefix => 'POTD protected/',
        aplimit => 'max',
    );
    while(my $p=$iter->next){
        return 0 if $api->halting;
        return 0 if time() >= $endtime; # Give other tasks a chance to run

        if(!$p->{'_ok_'}){
            $api->warn("Failed to retrieve POTD pages for deletion: ".$p->{'error'}."\n");
            return 60;
        }

        my $title = $p->{'title'};
        next unless $title =~ m{^Template:POTD protected/(\d{4}-\d{2}-\d{2})/.*$};
        next unless $1 lt $when;

        my $tt = $title;
        my @tt = ();
        while ( 1 ) {
            $tt =~ s!/[^/]*$!!;
            last if $tt =~  m{^Template:POTD protected/\d{4}-\d{2}-\d{2}$};
            push @tt, $tt;
        }
        my $r = $api->query( titles => join( '|', @tt ), formatversion => 2 );
        next if grep { !exists( $_->{'missing'} ) } @{$r->{'query'}{'pages'}};

        my ($res, $key, $reason) = $self->do_delete( $api, $title, 1 );
        if ( $res ) {
            return $res if $res > 0;
            next;
        }
    }

    # Any leftover talk pages.
    $iter = $api->iterator(
        generator => 'allpages',
        gapnamespace => 11,
        gapprefix => 'POTD protected/',
        gaplimit => 'max',
        prop => 'info',
        inprop => 'subjectid'
    );
    while(my $p=$iter->next){
        return 0 if $api->halting;
        return 0 if time() >= $endtime; # Give other tasks a chance to run

        if(!$p->{'_ok_'}){
            $api->warn("Failed to retrieve POTD talk pages for deletion: ".$p->{'error'}."\n");
            return 60;
        }

        next if exists( $p->{'subjectid'} );

        my $title = $p->{'title'};
        next unless $title =~ m{^Template talk:POTD protected/(\d{4}-\d{2}-\d{2})(?:/.*)?$};
        next unless $1 lt $when;

        my ($res, $key, $reason) = $self->do_delete( $api, $title, 2 );
        if ( $res ) {
            return $res if $res > 0;
            next;
        }
    }

    return $self->nexttime();
}

sub do_delete {
    my ($self, $api, $title, $what) = @_;

    my $tok = $api->gettoken( 'csrf', Title => $title, EditRedir => 1, templates => { templates => 'Template:G8-exempt' } );
    if ( $tok->{'code'} eq 'shutoff' ) {
        $api->warn( "Task disabled: " . $tok->{'content'} . "\n" );
        return (300, undef);
    }
    if ( $tok->{'code'} eq 'botexcluded' ) {
        $api->warn( "Bot excluded from $title: " . $tok->{'error'} . "\n" );
        return (-1, 'skip', 'bot excluded');
    }
    if ( $tok->{'code'} ne 'success' ) {
        $api->warn( "Failed to get delete token for $title: " . $tok->{'error'} . "\n" );
        return (-1, 'fail', "couldn't fetch delete token");
    }
    if ( exists( $tok->{'missing'} ) ) {
        #$api->log("$title no longer exists, skipping");
        return (0, 'ok', "no longer exists");
    }
    if ( $what > 0 && @{$tok->{'templates'} // []} ) {
        #$api->log("$title is G8-exempt");
        return (-1, 'skip', "marked G8-exempt");
    }

    my $reason = $reasons[$what];
    $api->log( "Deleting $title: $reason" );
    my $res = $api->action( $tok,
        action => 'delete',
        title => $title,
        reason => "$reason $screwup",
    );
    if ( $res->{'code'} ne 'success' ) {
        $api->warn( "Failed to delete $title: " . $res->{'error'} . "\n" );
        return (-1, 'fail', 'delete failed');
    }

    if ( $what < 2 ) {
        my $talk = $title;
        $talk =~ s/^Template:/Template talk:/;
        my ($res, $key, $reason) = $self->do_delete( $api, $talk, 2 );
        return ($res, $key, $reason) if $res;
    }

    if ( $what == 0 ) {
        # Delete subpages of deleted page
        my $t = $title;
        $t =~ s/^Template://;
        my $iter = $api->iterator(
            list => 'allpages',
            apnamespace => 10,
            apprefix => "$t/",
            aplimit => 'max',
        );
        my %skip = ();
        ITER: while( my $p = $iter->next ) {
            if ( !$p->{'_ok_'} ) {
                $api->warn( "Failed to retrieve subpages of $title for deletion: " . $p->{'error'} . "\n" );
                last;
            }
            my @parts = split( m!/!, $p->{'title'} );
            for ( my $i = 0; $i < @parts; $i++ ) {
                my $t = join( '/', @parts[0..$i] );
                next ITER if exists( $skip{$t} );
            }
            if ( exists( $p->{'subjectid'} ) || @{$p->{'templates'} // []} ) {
                $skip{$p->{'title'}} = 1;
                next ITER;
            }
            my ($res, $key, $reason) = $self->do_delete( $api, $p->{'title'}, 1 );
            $skip{$p->{'title'}} = 1 if $res;
        }
    }

    return ( 0, 'ok', 'ok' );
}

sub nexttime {
    my $t = 86400 - ( time % 86400 );
    return $t < 60 ? 60 : $t;
}

1;