User:AnomieBOT/source/tasks/FlagIconRemover.pm

package tasks::FlagIconRemover;

=pod

=begin metadata

Bot:     AnomieBOT
Task:    FlagIconRemover
BRFA:    Wikipedia:Bots/Requests for approval/AnomieBOT 52
Status:  Approved 2011-09-12
Created: 2011-08-26

Remove flag icons from certain infoboxes and layout templates per community consensus.

=end metadata

=cut

use utf8;
use strict;

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

use Data::Dumper;

my $version=3;
my $screwup="Errors? [[User:AnomieBOT/shutoff/FlagIconRemover]]";

my %templates=(
    'Template:Infobox company' => qr/.*/s,
    'Template:Infobox dam' => qr/.*/s,
    'Template:Infobox power station' => qr/.*/s,
    'Template:Infobox World Heritage Site' => qr/.*/s,
    'Template:Infobox language' => qr/(?!nation$|minority$).*/s,
);

my @flagcats=(
    'Category:Flag templates',
    'Category:Flag template system',
    'Category:Flag template shorthands',
    'Category:Canada flag template shorthands',
);

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

=pod

=for info
Approved 2011-09-12<br />[[Wikipedia:Bots/Requests for approval/AnomieBOT 52]]

=cut

sub approved {
    return 3;
}

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

    $api->task('FlagIconRemover', 0, 10, qw(d::Templates d::Redirects d::Talk));

    if(($api->store->{'version'}//0) != $version){
        foreach my $k (keys %{$api->store}) {
            delete $api->store->{$k} if $k=~/^revid /;
        }
        $api->store->{'version'}=$version;
    }

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

    # Get the list of flag templates
    my $flags=$api->cache->get('FlagIconRemover:flags');
    if(!defined($flags)){
        my %f=();
        my $iter=$api->iterator(
            list        => 'categorymembers',
            cmtitle     => [ @flagcats ],
            cmprop      => 'title',
            cmnamespace => 10,
            cmlimit     => 'max',
        );
        while(my $t=$iter->next){
            return 0 if $api->halting;
            if(!$t->{'_ok_'}){
                $api->warn("Failed to retrieve members of ".$iter->iterval.": ".$t->{'error'}."\n");
                return 300;
            }
            $f{$t->{'title'}}=1;
        }
        $flags=\%f;
        $api->cache->set('FlagIconRemover:flags', $flags, 86400);
    }
    my %flags=%{$flags};

    # Query list of pages transcluding our target templates
    if(!defined($self->{'iter'})){
        $self->{'iter'}=$api->iterator(
            generator    => 'embeddedin',
            geititle     => [ keys %templates ],
            geinamespace => 0,
            geilimit     => '100',
            prop         => 'templates',
            tllimit      => 'max',
            onlylist('tltemplates', 500, keys %flags),
        );
    }
    my $iter=$self->{'iter'};
    while(my $p=$iter->next){
        return 0 if $api->halting;

        if(!$p->{'_ok_'}){
            $api->warn("Failed to retrieve embeddedin list for ".$iter->iterval.": ".$p->{'error'}."\n");
            return 300;
        }

        my $any=0;
        for my $t (@{$p->{'templates'}}) {
            $any=1 if exists($flags{$t->{'title'}});
        }
        next unless $any;

        my $title=$p->{'title'};
        my $tok=$api->edittoken($title);
        if($tok->{'code'} eq 'shutoff'){
            $api->warn("Task disabled: ".$tok->{'content'}."\n");
            return 300;
        }
        if($tok->{'code'} eq 'pageprotected' || $tok->{'code'} eq 'botexcluded'){
            $api->warn("Cannot edit $title: ".$tok->{'error'});
            next;
        }
        if($tok->{'code'} ne 'success'){
            $api->warn("Failed to get edit token for $title: ".$tok->{'error'});
            return 60;
        }
        next if $tok->{'lastrevid'} eq ($api->store->{"revid $title"} // 0);
        $api->log("Need to check $title");

        my $res=$api->query(
            titles    => $title,
            generator => 'templates',
            gtllimit  => 'max',
            redirects => 1,
        );
        if($res->{'code'} ne 'success'){
            $api->warn("Failed to get templates and redirects for $title: ".$res->{'error'});
            return 60;
        }
        my %map=();
        for my $r (@{$res->{'query'}{'redirects'} // []}) {
            $r->{'from'}=~s/^Template://;
            $r->{'to'}=~s/^Template://;
            $map{$r->{'from'}}=$r->{'to'};
        }

        my $fail=0;
        my %log=();
        my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'};
        my $outtxt=$api->process_templates($intxt, sub {
            return undef if $fail;

            my $tname=shift;
            my $params=shift;
            shift; # $wikitext
            shift; # $data
            my $oname = shift;

            my $name=$map{$tname} // $tname;
            return undef unless exists($templates{"Template:$name"});
            my $ret="{{$oname";
            my $paramre = $templates{"Template:$name"};
            for my $p ($api->process_paramlist(@$params)) {
                if ($p->{'name'} =~ /^$paramre$/){
                    my $o=$p->{'value'};
                    my $v=$api->process_templates($o, sub {
                        return undef if $fail;

                        my $name=shift;
                        my $params=shift;
                        my $wikitext=shift;

                        return undef if grep /^\s*consensus[\s_]*to[\s_]*keep\s*=/i, @$params;

                        $name=$map{$name} // $name;
                        return undef unless exists($flags{"Template:$name"});
                        my $res=$api->query(
                            action => 'expandtemplates',
                            title  => $title,
                            text   => $wikitext,
                            prop   => 'wikitext',
                        );
                        if($res->{'code'} ne 'success'){
                            $api->warn("Failed to expand $wikitext: ".$res->{'error'}."\n");
                            $fail=1;
                            return undef;
                        }
                        my $intxt=$res->{'expandtemplates'}{'wikitext'};
                        my $outtxt=$intxt;
                        $outtxt=~s!<span (?:[^>]* )?class="flagicon"(?: [^>]*)?>.*?</span>!!g;
                        if($outtxt eq $intxt){
                            $api->warn("Found nothing to replace in $wikitext in $title!\n") if $outtxt eq $intxt;
                            return undef;
                        }
                        $outtxt=~s/\[\[\s*([^]|])([^]|]*?)\s*\|\s*((?i:\1)\2)\s*\]\]/[[$3]]/g;
                        return $outtxt;
                    });
                    return undef if $fail;
                    if($o ne $v){
                        $log{"{{$tname}}"}=1;
                        $p->{'text'}=~s/\Q$o\E(\s*)$/$v$1/;
                    }
                }
                $ret.="|".$p->{'text'};
            }
            $ret.="}}";
            return $ret;
        });
        next if $fail;

        if(%log){
            my @log=keys %log;
            $log[-1]='and '.$log[-1] if @log>1;
            my $summary="Removing flag icons from ".join(@log>2?', ':' ', @log)." per consensus, see [[User:AnomieBOT/docs/FlagIconRemover]] for details. $screwup";
            $api->log("$summary in $title");
            my $r=$api->edit($tok, $outtxt, $summary, 0, 1);
            if($r->{'code'} ne 'success'){
                $api->warn("Write failed on $title: ".$r->{'error'}."\n");
            } else {
                $api->store->{"revid $title"}=$r->{'edit'}{'newrevid'};
            }
        } else {
            $api->log("Nothing to do in $title");
            $api->store->{"revid $title"}=$tok->{'lastrevid'};
        }

        # If we've been at it long enough, let another task have a go.
        return 0 if time()>=$endtime;
    }
    $self->{'iter'}=undef;

    return 14400;
}

1;