User:AnomieBOT/source/tasks/TFAProtector.pm

From Wikipedia, the free encyclopedia
package tasks::TFAProtector;

=pod

=begin metadata

Bot:       AnomieBOT III
Task:      TFAProtector
BRFA:      Wikipedia:Bots/Requests for approval/AnomieBOT III 7
Status:    Inactive 2021-05-31
Created:   2021-04-29
Exclusion: false

Apply pending changes protection to Today's Featured Article while it's on the main page.

Currently won't do anything pending an RFC to determine whether the community wants this to
continue past the trial that ended 2021-05-31.

=end metadata

=cut

use utf8;
use strict;

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

use Data::Dumper;

# Timestamp of the end of the trial.
my $trialUntil = ISO2timestamp( '2021-05-31T22:00:00Z' );

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

=pod

=for info
Approved 2021-06-01<br />[[Wikipedia:Bots/Requests for approval/AnomieBOT III 7]]

=for info
Inactive. [[Wikipedia:Village pump (proposals)/Archive 183#RFC: Pending-changes protection of Today's featured article|The RFC]] was not approved.

=cut

sub approved {
    return -500;
}

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

    $api->task( 'TFAProtector', 0, 0.1 );

    my $reason = "Applying pending changes protection to TFA per [[Special:PermaLink/1017460384#Pending-changes protection of Today's featured article|RFC]], trial running through " . timestamp2ISO( $trialUntil );

    my $starttime = time();
    my $broken = 0;

    if ( $starttime >= $trialUntil ) {
        $api->warn( "Trial has ended\n" );
        return undef;
    }

    # Only check once per hour
    if ( $self->{'lasttime'} == 0 ) {
        if ( exists( $api->store->{'lasttime'} ) ) {
            my $t = $api->store->{'lasttime'};
            $self->{'lasttime'} = $t if ( $t =~ /^\d+$/ && $t <= time() );
        }
        $self->{'broken'} = $api->store->{'broken'} if ( exists( $api->store->{'broken'} ) );
    }
    my $t = $self->{'lasttime'} + ( $self->{'broken'} ? 300 : 3600 ) - $starttime;
    return $t if $t > 0;
    # If it's close enough to 23:00, just wait for 23:00.
    $t = 82800 - ( $starttime % 86400 );
    return $t if ( $t > 0 && $t < ( $self->{'broken'} ? 300 : 3600 ) );

    # Check today's TFA, and tomorrow's if it's after 23:00.
    my %titles = ();
    my @t = gmtime();
    @t = @t[3..5];
    $titles{"Template:TFA title/" . strftime( "%B %-d, %Y", gmtime( timegm( 0, 0, 0, $t[0], $t[1], $t[2] ) ) )} = timegm( 0, 0, 1, $t[0] + 1, $t[1], $t[2] );
    if ( $starttime % 86400 >= 82800 ) {
        $titles{"Template:TFA title/" . strftime( "%B %-d, %Y", gmtime( timegm( 0, 0, 0, $t[0] + 1, $t[1], $t[2] ) ) )} = timegm( 0, 0, 1, $t[0] + 2, $t[1], $t[2] );
    }

    # Determine the TFAs using Template:TFA title data.
    my $res = $api->query(
        titles => join( '|', keys %titles ),
        prop => 'revisions',
        rvprop => 'content',
        rvslots => 'main',
        formatversion => 2
    );
    if ( $res->{'code'} ne 'success' ) {
        $api->warn( "Failed to fetch PC status for TFAs: $res->{error}\n" );
        return 60;
    }
    my %tfas = ();
    foreach my $p ( @{$res->{'query'}{'pages'}} ) {
        my $title = $p->{'title'};
        if ( ! exists( $titles{$title} ) ) {
            $api->warn( "WTF? Got $title when we didn't ask for it.\n" );
            next;
        }
        my $tfa = $p->{'revisions'}[0]{'slots'}{'main'}{'content'} // '';
        if ( $tfa eq '' ) {
            $api->warn( "$title does not point to a TFA.\n" );
            $broken = 1;
            next;
        }
        $tfas{$tfa} = $titles{$title};
    }

    # Exit early if all TFAs didn't exist.
    if ( ! %tfas ) {
        $self->{'lasttime'} = $starttime;
        $self->{'broken'} = $broken;
        $api->store->{'lasttime'} = $starttime;
        $api->store->{'broken'} = $broken;
        return $starttime + ( $self->{'broken'} ? 300 : 3600 ) - time();
    }

    # Get token.
    my $tok = $api->gettoken('csrf' );
    if ( $tok->{'code'} eq 'shutoff' ) {
        $api->warn( "Task disabled: " . $tok->{'content'} . "\n" );
        return 300;
    }
    if ( $tok->{'code'} ne 'success' ) {
        $api->warn( "Failed to get protect token: " . $tok->{'error'} . "\n" );
        return 300;
    }

    # Check the PC status of each TFA.
    $res = $api->query(
        titles => join( '|', keys %tfas ),
        prop => 'flagged',
        formatversion => 2
    );
    if ( $res->{'code'} ne 'success' ) {
        $api->warn( "Failed to fetch PC status for TFAs: $res->{error}\n" );
        return 60;
    }
    foreach my $p ( @{$res->{'query'}{'pages'}} ) {
        my $title = $p->{'title'};
        if ( ! exists( $tfas{$title} ) ) {
            $api->warn( "WTF? Got $title when we didn't ask for it.\n" );
            next;
        }
        if ( exists( $p->{'flagged'}{'protection_expiry'} ) && ( $p->{'flagged'}{'protection_expiry'} eq 'infinity' || ISO2timestamp( $p->{'flagged'}{'protection_expiry'} ) >= $tfas{$title} ) ) {
            $api->log( "TFA $title is already PC-protected." );
            next;
        }

        $api->log( "PC-protecting TFA $title" );
        my $res2 = $api->action( $tok,
            action => 'stabilize',
            title => $title,
            protectlevel => 'autoconfirmed',
            expiry => timestamp2ISO( $tfas{$title} ),
            reason => $reason,
        );
        if ( $res2->{'code'} ne 'success' ) {
            $api->warn( "Failed to PC-protect $title: $res2->{error}\n" );
            $broken = 1;
        }
    }

    # Done!
    $self->{'lasttime'} = $starttime;
    $self->{'broken'} = $broken;
    $api->store->{'lasttime'} = $starttime;
    $api->store->{'broken'} = $broken;
    return $starttime + ( $self->{'broken'} ? 300 : 3600 ) - time();
}

1;