User:AnomieBOT/source/tasks/PERTableUpdater.pm

This is an old revision of this page, as edited by AnomieBOT (talk | contribs) at 02:22, 5 December 2011 (Updating published sources: PERTableUpdater: * Don't break uploaded source display.). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
package tasks::PERTableUpdater;

=pod

=begin metadata

Bot:     AnomieBOT
Task:    PERTableUpdater
BRFA:    N/A
Status:  Begun 2011-12-04
Created: 2011-12-01

Update [[User:AnomieBOT/PERTable]] and [[User:AnomieBOT/SPERTable]]

=end metadata

=cut

use utf8;
use strict;

use AnomieBOT::Task qw/:time bunchlist/;
use URI::Escape;
use Data::Dumper;
use vars qw/@ISA/;
@ISA=qw/AnomieBOT::Task/;

my %colors=(
    'red'    => ' style="background-color:#ffbfdc"',
    'yellow' => ' style="background-color:#fff9bf"',
    'green'  => ' style="background-color:#e4ffcc"',
    'clear'  => '',
);
my %protact=(
    'modify'    => 'Modified',
    'protect'   => 'Protected',
    'unprotect' => 'Unprotected',
);

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

=pod

=for info
Per [[WP:BOT#Approval]], any bot or automated editing process that only
affects only the operators' user and talk pages (or subpages thereof),
and which are not otherwise disruptive, may be run without prior
approval.

=cut

sub approved {
    return 999;
}

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

    $api->task('PERTableUpdater', 0, 10, qw/d::Sections d::Timestamp d::Talk/);

    my $screwup=' Errors? [[User:'.$api->user.'/shutoff/PERTableUpdater]]';

    # First, load MediaWiki:Titleblacklist to catch pages protected by
    # that mechanism
    my %tb=();
    my $tb=$api->rawpage('MediaWiki:Titleblacklist');
    if($tb->{'code'} ne 'success'){
        $api->warn("Failed to load MediaWiki:Titleblacklist: ".$tb->{'error'}."\n");
        return 60;
    }
    for my $line (split /\r?\n/, $tb->{'content'}){
        my $re=$line;
        my %opts=();
        $re=~s/^\s*([^#]*?)\s*(?:#.*)?$/$1/;
        if($re=~s/\s*<([^<>]*)>$//){
            my $opts=$1;
            $opts=~s/^\s+|\s+$//g;
            for my $opt (split /\s*\|\s*/, $opts){
                if($opt=~/^([^=]*?)\s*=\s*(.+)$/){
                    $opts{lc($1)}=$2;
                } else {
                    $opts{lc($opt)}=1;
                }
            }
        }
        $re=~s/_/ /g;
        $re="(?-i:$re)" if($opts{'casesensitive'}//0);

        # Let's just hope no one ever uses {{ns:}} or {{int:}} here...

        $tb{$re}={ line=>$line, opts=>\%opts } if($opts{'noedit'}//0);
    }

    my $starttime=time;
    for my $sper (0,1){
        my $iter=$api->iterator(
            generator      => 'categorymembers',
            gcmtitle       => $sper?'Category:Wikipedia semi-protected edit requests':'Category:Wikipedia protected edit requests',
            gcmlimit       => 'max',
            prop           => 'info',
        );
        my %oldpages=%{$api->store->{'pages'}//{}};
        my %pages=();
        while(my $p=$iter->next){
            if(!$p->{'_ok_'}){
                $api->warn("Failed to retrieve members for ".($sper?'CAT:SEP':'CAT:EP').": ".$p->{'error'}."\n");
                return 60;
            }
            next unless $p->{'ns'}&1;
            my $t=$p->{'title'};
            if($p->{'ns'}==1){
                $t=~s/^Talk://;
            } else {
                $t=~s/^([^:]+) talk:/$1:/;
            }
            $pages{$t}=($oldpages{$t} // {
                    ns      => $p->{'ns'}&~1,
                    title   => $t,
                    talk    => $p->{'title'},
                    touched => ISO2timestamp($p->{'touched'}),
                });
            delete $pages{$t}{'color'};
            delete $pages{$t}{'prottype'};
            delete $pages{$t}{'reason'};
            delete $pages{$t}{'logtitle'};
        }
        $api->store->{'pages'}=\%pages;
        
        if(%pages){
            $iter=$api->iterator(
                titles => bunchlist(500, keys %pages),
                prop   => 'info',
                inprop => 'protection',
            );
            while(my $p=$iter->next){
                if(!$p->{'_ok_'}){
                    $api->warn("Failed to retrieve members for ".($sper?'CAT:SEP':'CAT:EP').": ".$p->{'error'}."\n");
                    return 60;
                }
                my $t=$p->{'title'};
                my $pd=$pages{$t};
                $pd->{'color'}='red';
                $pd->{'prottype'}='Not protected';
                $pd->{'reason'}='';
                if($p->{'ns'}==8){
                    $pd->{'color'}='yellow';
                    $pd->{'prottype'}='MediaWiki page';
                } elsif($p->{'ns'}==2 && $t=~m!/.*\.js$!){
                    $pd->{'color'}='yellow';
                    $pd->{'prottype'}='User JS page';
                } elsif($p->{'ns'}==2 && $t=~m!/.*\.css$!){
                    $pd->{'color'}='yellow';
                    $pd->{'prottype'}='User CSS page';
                }
                while(my ($re,$data)=each %tb){
                    next unless $t=~/^(?:$re)$/si;
                    $pd->{'color'}='yellow' unless(exists($data->{'opts'}{'autoconfirmed'}) && !$sper);
                    $pd->{'prottype'}='[[MediaWiki:Titleblacklist|Title blacklist]]';
                    my $line=$data->{'line'};
                    $pd->{'reason'}=qq(Matching line: <syntaxhighlight lang="text" enclose="none">$line</syntax).qq(highlight>);
                }

                my $protscore=-1;
                my $expiry=undef;
                my $pg=$t;
                for my $pp (@{$p->{'protection'}//[]}){
                    next unless $pp->{'type'} eq 'edit';
                    my $sc=0;
                    $sc|=2 if $pp->{'level'} eq 'sysop';
                    $sc|=1 if exists($pp->{'source'});
                    next if $sc<$protscore;
                    $pd->{'color'}=($p->{'ns'}==10?'green':'clear') if($pp->{'level'} eq ($sper?'autoconfirmed':'sysop'));
                    if(exists($pp->{'source'})){
                        $pg=$pp->{'source'};
                        $pd->{'prottype'}="Cascade-protected from [[:$pg]]";
                    } else {
                        $pg=$t;
                        $pd->{'prottype'}='Fully protected' if $pp->{'level'} eq 'sysop';
                        $pd->{'prottype'}='Semiprotected' if $pp->{'level'} eq 'autoconfirmed';
                    }
                    $pd->{'prottype'}.=strftime(', expires %F at %T UTC', gmtime ISO2timestamp($pp->{'expiry'})) if $pp->{'expiry'} ne 'infinity';
                    $protscore=$sc;
                    $pd->{'reason'}='';
                }

                if($pd->{'reason'} eq ''){
                    my $iter=$api->iterator(
                        list    => 'logevents',
                        letype  => 'protect',
                        letitle => $pg,
                    );
                    my $from='';
                    while(my $le=$iter->next){
                        if(!$le->{'_ok_'}){
                            $api->warn("Failed to retrieve protection log for $pg: ".$le->{'error'}."\n");
                            return 60;
                        }
                        if($le->{'action'} eq 'move_prot'){
                            $from="From [[:".$le->{'0'}."]]: ";
                            next;
                        }
                        next unless exists($protact{$le->{'action'}});
                        $le->{'timestamp'}=~s/T.*//;
                        my $comment=$le->{'comment'};
                        $comment=~s/\s*\[[^]]*\](?: \(expires [^)]*\))?$//;
                        $pd->{'reason'}=$from.$protact{$le->{'action'}}.' by [[User:'.$le->{'user'}.'|'.$le->{'user'}.']] on '.$le->{'timestamp'}.': "'.esccomment($comment).'"';
                        last;
                    }
                }
                $pd->{'logtitle'}=$pg;
            }
        }

        # The formatting here is a little strange, for backwards compat
        my $txt=qq(<div class="veblenbot-pertable">\n);
        $txt.=qq({| class="wikitable" style="padding:0em"\n);
        $txt.=qq(|-\n);
        my $ct=scalar keys %pages;
        my $s=($ct==1?'':'s');
        my $type=($sper?'semi-protected':'protected');
        my $pg='User:AnomieBOT/'.($sper?'SPERTable':'PERTable');
        $txt.=qq(! $ct [[:Category:Wikipedia $type edit requests|$type edit request$s]] <div style="float:right;white-space:nowrap">[[$pg|v]]&middot;<span class="plainlinks">[//en.wikipedia.org/w/index.php?title=$pg&action=history h]</span></div>\n);
        $txt.=qq(|-\n);
        $txt.=qq(|\n);
        $txt.=qq({| class="wikitable sortable" width=100% style="margin:0em"\n);
        $txt.=qq(! Page\n);
        $txt.=qq(! Tagged since\n);
        $txt.=qq(! Protection level\n);
        $txt.=qq(! class = "unsortable" | Last protection log entry\n);
        my $tgt=$sper?'editsemiprotected':'editprotected';
        for my $p (sort { $a->{'touched'} <=> $b->{'touched'} } values %pages){
            my $c=$colors{$p->{'color'}};
            my $t=$p->{'title'};
            my $et=encodetitle($p->{'logtitle'});
            $t=":$t" if($p->{'ns'}==6 || $p->{'ns'}==14);
            my $tt=$p->{'talk'};
            my $pt=$p->{'prottype'};
            my $r=$p->{'reason'};
            $txt.=qq(|-$c\n);
            $txt.=qq(| [[$t]] ([[$tt#$tgt|request]])\n);
            $txt.=strftime("| %F %H:%M\n", gmtime $p->{'touched'});
            $txt.=qq(| $pt <span class="plainlinks">([//en.wikipedia.org/w/index.php?title=Special:Log&type=protect&page=$et log])</span>\n);
            $txt.=qq(| $r\n);
        }
        $txt.=qq(|}\n);
        $txt.=qq(|-\n);
        $txt.=qq(|style="text-align:right;font-size:smaller"| Updated as needed. Last updated: <!--TS-->~~~~~<!--/TS-->\n);
        $txt.=qq(|}</div>);

        my $tok=$api->edittoken($pg, 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 $pg: ".$tok->{'error'}."\n");
            next;
        }
        my $intxt=$tok->{'revisions'}[0]{'*'}//'';
        $intxt=~s/^\s+|\s+$//;
        $intxt=~s#<!--TS-->.*<!--/TS-->#<!--TS-->~~~~~<!--/TS-->#g;
        if($intxt ne $txt){
            $api->log("Upadting $pg ($ct request$s)");
            my $r=$api->edit($tok, $txt, "Update table ($ct request$s)", 0, 0);
            if($r->{'code'} ne 'success'){
                $api->warn("Write failed on $pg: ".$r->{'error'}."\n");
            }
        }
    }

    my $t=$starttime-time+300;
    $t=0 if $t<0;
    return $t;
}

sub esccomment {
    my $c=shift;
    $c=~s/{/&#x7b;/g;
    return $c;
}

sub encodetitle {
    my $t=shift;
    $t=~s/ /_/g;
    $t=uri_escape($t, '^A-Za-z0-9_\-.:/~');
    return $t;
}

1;