User:AnomieBOT/source/tasks/TalkTemplateMover.pm

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

=pod

=begin metadata

Bot:     AnomieBOT
Task:    TalkTemplateMover
BRFA:    Wikipedia:Bots/Requests for approval/AnomieBOT 31
Status:  Approved 2009-07-09
+BRFA:   Wikipedia:Bots/Requests for approval/AnomieBOT 82
+Status: Approved 2024-02-04
Created: 2009-06-18

Find transclusions of {{tl|translated page}} and other templates documented for talk pages
mistakenly placed on article pages, and move them to the corresponding talk page.

=end metadata

=cut

use utf8;
use strict;

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

my $did_redir=0;
my %templates=(
    'Template:Translated page' => 1,
    'Template:Interwiki copy' => 1,
);

my @skip_templates=(
    'Template:Archive box',
    'Template:Archives',
);

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

=pod

=for info
Approved 2009-07-09<br />[[Wikipedia:Bots/Requests for approval/AnomieBOT 31]]

=for info
Supplemental BRFA approved 2024-02-04<br />[[Wikipedia:Bots/Requests for approval/AnomieBOT 82]]

=cut

sub approved {
    return 3;
}

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

    $api->task('TalkTemplateMover', 0, 10, qw/d::Talk d::Templates d::Redirects d::Trial/);
    my $screwup=' If this bot is making errors, please report it at [[User:'.$api->user.'/shutoff/TalkTemplateMover]]';

    if(!$did_redir){
        my %r=$api->redirects_to_resolved(keys %templates);
        if(exists($r{''})){
            $api->warn("Failed to get template redirects: ".$r{''}{'error'}."\n");
            return 60;
        }
        %templates=%r;

        my %skip=$api->redirects_to_resolved(@skip_templates);
        if(exists($skip{''})){
            $api->warn("Could not load list of redirects for skip templates: ".$skip{''}{'error'}."\n");
            return undef;
        }
        @skip_templates=keys %skip;

        $did_redir=1;
    }

    my $iter=$api->iterator(
        generator    => 'embeddedin',
        geititle     => [ keys %templates ],
        geinamespace => 0,
        geilimit     => '1000',
        prop         => 'revisions',
        rvprop       => 'content',
        rvslots      => 'main',
    );
    while($_=$iter->next){
        return 0 if $api->halting;

        if(!$_->{'_ok_'}){
            $api->warn("Failed to retrieve transclusion list: ".$_->{'error'}."\n");
            return 60;
        }

        my $title=$_->{'title'};
        my $intxt=$_->{'revisions'}[0]{'slots'}{'main'}{'*'};
        my %tmpl=();
        my @moved=();
        my $outtxt=$api->process_templates($intxt, sub {
            my $name=shift;
            my $params=shift;
            my $wikitext=shift;
            shift; # $data
            shift; # $oname

            return undef unless exists($templates{"Template:$name"});
            $tmpl{key($api,$name,$params)}=$wikitext;
            push @moved, "{{$name}}";
            return '';
        });
        next if $intxt eq $outtxt;

        $moved[-1]='and '.$moved[-1] if @moved>1;
        my $moved=join((@moved>2)?', ':' ', @moved);
        my $tl_moved=$moved; $tl_moved=~s/\{\{/{{tl|/g;

        $api->log("Moving $moved from $title to Talk:$title");
        my $tok1=$api->edittoken($title, EditRedirect => 1);
        if($tok1->{'code'} eq 'shutoff'){
            $api->warn("Task disabled: ".$tok1->{'content'}."\n");
            return 300;
        }
        if($tok1->{'code'} eq 'pageprotected'){
            $tok1=undef;
        } elsif($tok1->{'code'} ne 'success'){
            $api->warn("Failed to get edit token for $title: ".$tok1->{'error'}."\n");
            return 60;
        } elsif($tok1->{'revisions'}[0]{'slots'}{'main'}{'*'} ne $intxt){
            $api->log("Page changed while processing! Skip for now.");
            next;
        }
        my $tok2=$api->edittoken("Talk:$title", EditRedirect => 1);
        if($tok2->{'code'} eq 'shutoff'){
            $api->warn("Task disabled: ".$tok2->{'content'}."\n");
            return 300;
        }
        if($tok2->{'code'} ne 'success'){
            $api->warn("Failed to get edit token for Talk:$title: ".$tok2->{'error'}."\n");
            return 60;
        }
        if(exists($tok2->{'redirect'})){
            $api->warn("Talk:$title is a redirect, cannot move $moved\n");
            $api->whine("[[:$title]] cannot be processed", "The talk page [[Talk:$title]] is a redirect, so I am unwilling to move $tl_moved. Please handle it manually.");
            next;
        }

        my $intalk=$tok2->{'revisions'}[0]{'slots'}{'main'}{'*'} // '';
        my $outtalk=$api->process_templates($intalk, sub {
            my $name=shift;
            my $params=shift;
            my $wikitext=shift;
            shift; # $data
            shift; # $oname

            return undef unless exists($templates{"Template:$name"});
            my $k=key($api,$name,$params);
            delete $tmpl{$k} if exists($tmpl{$k});
            return undef;
        });
        $outtalk=$self->tag($api, $outtalk, join("\n", values %tmpl)) if %tmpl;
        my $summary="Moving $moved from [[:$title]] per that template's documentation";
        if(!defined($tok1)){
            $outtalk=~s/\s+$//;
            my $rq;
            if(@moved==1){
                $rq="== Editprotected request ==\n{{editprotected}} The template $tl_moved belongs on this talk page rather than on the article itself, but I cannot remove it as this page is fully protected. Please remove it for me. Thanks.";
            } else {
                $rq="== Editprotected request ==\n{{editprotected}} The templates $tl_moved belong on this talk page rather than on the article itself, but I cannot remove them as this page is fully protected. Please remove them for me. Thanks.";
            }
            if(index($outtalk,$rq)<0){
                $outtalk.="\n\n$rq ~~~~\n";
                $summary.=" (and adding an {{editprotected}})";
            }
        }
        if($intalk ne $outtalk){
            my $res=$api->edit($tok2, $outtalk, "$summary. $screwup", 0, defined($tok1)?1:0);
            if($res->{'code'} ne 'success'){
                $api->warn("Write failed on Talk:$title: $res->{error}\n");
                return 60;
            }
        } else {
            $api->log("No edit to Talk:$title necessary");
        }
        if(defined($tok1)){
            my $res=$api->edit($tok1, $outtxt, "Moving $moved to [[Talk:$title]] per that template's documentation. $screwup", 0, 1);
            if($res->{'code'} ne 'success'){
                $api->warn("Write failed on $title: $res->{error}\n");
                return 60;
            }
        }
    }

    return 3600;
}

sub key {
    my $api=shift;
    my $name=shift;
    my $params=shift;

    my $k=$templates{"Template:$name"};
    foreach ($api->process_paramlist(@$params)){
        $_->{'name'}=~s/^\s+|\s+$//g;
        $_->{'value'}=~s/^\s+|\s+$//g;
        $k.='|'.$_->{'name'}.'='.$_->{'value'};
    }
    return $k;
}

sub tag {
    my $self=shift;
    my $api=shift;
    my $txt=shift;
    my $tmpl=shift;

    my $nowiki;
    ($txt,$nowiki)=$api->strip_nowiki($txt);
    my $outtmpl={};
    $txt=$api->process_templates($txt, \&_strip_templates, $outtmpl);
    $txt="$tmpl\n$txt" unless $txt=~s/^((?:\s*\x02[a-zA-Z0-9_-]+\x03)*[ \t]*)(?:$|(?=\n))/$1\n$tmpl/;
    $txt=_unstrip_templates($txt, $outtmpl);
    return $api->replace_nowiki($txt, $nowiki);
}

sub _strip_templates {
    my ($name, $params, $wikitext, $data) = @_;
    return undef if $name=~/^#/;
    return undef if grep(/^\s*small\s*=\s*(?!no|n|0)\S/, @$params);
    return undef if grep("Template:$name" eq $_, @skip_templates);

    $wikitext=_unstrip_templates($wikitext,$data);
    my $tmp = $wikitext;
    utf8::encode( $tmp ) if utf8::is_utf8( $tmp );
    my $tag="\x02".sha256_base64($tmp)."\x03";
    $tag=~tr!+/=!-_!d;
    $data->{$tag}=$wikitext;
    return $tag;
}

sub _unstrip_templates {
    my $wikitext=shift;
    my $templ=shift;

    $wikitext=~s!(\x02[a-zA-Z0-9_-]+\x03)! $templ->{$1} // $1 !gioe;
    return $wikitext;
}

1;