User:AnomieBOT/source/tasks/IMONumberRedirectCreator.pm

package tasks::IMONumberRedirectCreator;

=pod

=begin metadata

Bot:      AnomieBOT
Task:     IMONumberRedirectCreator
BRFA:     Wikipedia:Bots/Requests for approval/AnomieBOT 73
Status:   Approved 2015-04-07
Created:  2015-04-02

Create redirects for [[IMO numbers]], based on categorization in [[:Category:IMO numbers]].

=end metadata

=cut

use utf8;
use strict;

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

sub new {
    my $class = shift;
    my $self = $class->SUPER::new();
    $self->{'iter'} = undef;
    bless $self, $class;
    return $self;
}

=pod

=for info
Approved 2015-04-07<br />[[Wikipedia:Bots/Requests for approval/AnomieBOT 73]]

=cut

sub approved {
    return 3;
}

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

    $api->task('IMONumberRedirectCreator', 0, 10, qw/d::Redirects/);
    my $screwup=' Errors? [[User:'.$api->user.'/shutoff/IMONumberRedirectCreator]]';

    my $starttime = time();
    my $lastrun = $api->store->{'lastrun'} // 0;
    my $t = $lastrun + 86400 - $starttime;
    return $t if $t > 0;

    my $redir = $api->redirect_regex();

    # Spend a max of 5 minutes on this task before restarting
    my $endtime = time() + 300;

    my $iter = $self->{'iter'};
    if(!defined($iter)) {
        $iter = $api->iterator(
            list => 'categorymembers',
            cmtitle => 'Category:IMO numbers',
            cmprop => 'title|sortkeyprefix',
            cmlimit => '100',
        );
        $self->{'iter'} = $iter;
        $self->{'conflicts'} = {};
        $self->{'doconflicts'} = 1;
    }

    while(my $n = $iter->next) {
        return 0 if $api->halting;
        if(!$n->{'_ok_'}) {
            $api->warn("Failed to retrieve category members: " . $n->{'error'} . "\n");
            $self->{'doconflicts'} = 0;
            return 60;
        }

        next if $n->{'ns'} != 0;

        my $target = $n->{'title'};
        my $number = $n->{'sortkeyprefix'};
        my $title = "IMO $number";
        next unless $number =~ /^\d{7,8}$/; # Seems sufficient for the forseeable future

        my $tok = $api->edittoken($title, EditRedir => 1);
        if($tok->{'code'} eq 'shutoff') {
            $api->warn("Task disabled: " . $tok->{'content'} . "\n");
            $self->{'doconflicts'} = 0;
            return 300;
        }
        if($tok->{'code'} ne 'success') {
            $api->warn("Failed to get edit token for $title: " . $tok->{'error'} . "\n");
            $self->{'doconflicts'} = 0;
            next;
        }

        my $summary;
        if(exists($tok->{'missing'})) {
            # Doesn't exist, we can create it
            $summary = "Creating redirect from [[IMO number]] to ship article [[$target]]";
        } else {
            # Check if it's a redirect
            my $txt = $tok->{'revisions'}[0]{'slots'}{'main'}{'*'};
            unless($txt=~/$redir\[\[\s*(.+?)\s*\]\]/) {
                $api->log("Skipping $title: Not a redirect");
                $self->{'conflicts'}{$title}{$target} = 1;
                $self->{'conflicts'}{$title}{$title} = 1;
                next;
            }

            # Is a redirect, check if it points to $target (maybe via another redir)
            my $target2 = $1;
            next if $target2 eq $target;

            my $res = $api->query(
                titles => $target2,
                redirects => 1,
                prop => 'categories',
                clcategories => 'Category:IMO numbers',
                clprop => 'sortkey',
                cllimit => 'max',
            );
            if($res->{'code'} ne 'success') {
                $api->warn("Could not resolve target page $target2: " . $res->{'error'} . "\n");
                $self->{'doconflicts'} = 0;
                return 60;
            }

            my $p = (values %{$res->{'query'}{'pages'}})[0];
            my $c;
            if(exists($p->{'missing'})) {
                # Target page doesn't exist, so we can safely overwrite it
                $api->log("Overwriting $title => $target2: target doesn't exist");
            } elsif(!(($c)=grep { $_->{'title'} eq 'Category:IMO numbers' } @{$p->{'categories'} // []})) {
                # Target page doesn't have the IMO Number template, so we can safely overwrite it
                $api->log("Overwriting $title => $target2: target doesn't have [[Category:IMO numbers]]");
            } elsif($c->{'sortkeyprefix'} ne $number) {
                # Target page doesn't have the IMO Number template, so we can safely overwrite it
                $api->log("Overwriting $title => $target2: target has [[Category:IMO numbers]] pointing to IMO " . $c->{'sortkeyprefix'});
            } elsif(exists($res->{'query'}{'redirects'})) {
                # Target page is a redirect. Overwrite if it's a double redir to our target.
                if($res->{'query'}{'redirects'}[0]{'to'} ne $target) {
                    $api->log("Skipping $title => $target: Already a double redirect to " . $res->{'query'}{'redirects'}[0]{'to'});
                    $self->{'conflicts'}{$title}{$target} = 1;
                    $self->{'conflicts'}{$title}{$target2} = 1;
                    next;
                }
            } else {
                # Target page exists and isn't a redirect. Don't edit, maybe log a message.
                if(!exists($res->{'query'}{'normalized'}) || $res->{'query'}{'normalized'}[0]{'to'} ne $target) {
                    $api->log("Skipping $title => $target: Already a redirect to $target2");
                    $self->{'conflicts'}{$title}{$target} = 1;
                    $self->{'conflicts'}{$title}{$target2} = 1;
                }
                next;
            }

            $summary = "Updating redirect from [[IMO number]] to ship article [[$target]]";
        }

        my $txt = "#REDIRECT [[$target]]\n{{R from IMO number}}\n";
        $api->log( "$summary in $title" );
        my $res=$api->edit($tok, $txt, $summary.$screwup, 0, 1);
        if($res->{'code'} ne 'success'){
            $api->warn("Write for $title failed: " . $res->{'error'} . "\n");
            return 60;
        }

        return 0 if time()>=$endtime;
    }

    if ( $self->{'doconflicts'} ) {
        my %conflicts = %{$self->{'conflicts'}};
        my $txt = "List of IMO numbers being claimed by multiple articles (via {{tl|IMO Number}} / [[:Category:IMO numbers]])\n\n";
        $txt .= "This page is updated by a bot. Any changes will be overwritten periodically.\n\n";
        if ( %conflicts ) {
            for my $n (sort keys %conflicts) {
                $txt .= "* [[$n]]: " . join( ', ', map { "[[$_]]" } sort keys %{$conflicts{$n}} ) . "\n";
            }
        } else {
            $txt .= "There are no conflicts at this time.\n";
        }
        my $tok = $api->edittoken("User:AnomieBOT/IMO number conflicts", 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 User:AnomieBOT/IMO number conflicts: " . $tok->{'error'} . "\n");
            next;
        }
        if ( $txt ne ($tok->{'revisions'}[0]{'*'}//'') ) {
            my $res=$api->edit($tok, $txt, "Updating list of IMO number conflicts. $screwup", 0, 1);
            if($res->{'code'} ne 'success'){
                $api->warn("Write for User:AnomieBOT/IMO number conflicts failed: " . $res->{'error'} . "\n");
                return 60;
            }
        }
    }

    $self->{'iter'} = undef;
    $self->{'conflicts'} = {};
    return 3600 if !$self->{'doconflicts'};
    $self->{'doconflicts'} = 0;
    $api->store->{'lastrun'} = $starttime;
    return $starttime + 86400 - time();
}

1;