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 d::IWNS/);
my $screwup=' Errors? [[User:'.$api->user.'/shutoff/PERTableUpdater]]';
# First, load MediaWiki:Titleblacklist to catch pages protected by
# that mechanism
my %tb=();
my %sources=(
'Title blacklist' => 'MediaWiki:Titleblacklist',
'Global title blacklist' => 'meta:Title blacklist',
);
while(my ($k,$page)=each %sources){
my $tb=$api->rawpage($page);
if($tb->{'code'} ne 'success'){
$api->warn("Failed to load $page: ".$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);
$re=~s!({{\s*ns\s*:\s*(.+?)\s*}})! replace_ns($api,$2) // $1 !ge;
# Let's just hope no one ever uses {{int:}} here...
$tb{$re}={ source=>"[[$page|$k]]", 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'.$sper}//{}};
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'.$sper}=\%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'}=$data->{'source'};
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 unless 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'}."]]: ";
$iter=$api->iterator(
list => 'logevents',
letype => 'protect',
letitle => $le->{'0'},
lestart => $le->{'timestamp'},
);
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]]·<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/{/{/g;
return $c;
}
sub encodetitle {
my $t=shift;
$t=~s/ /_/g;
$t=uri_escape_utf8($t, '^A-Za-z0-9_\-.:/~');
return $t;
}
sub replace_ns {
my ($api,$ns)=@_;
if($ns=~/^([+-]?[0-9]+)/){
$ns=int($1);
} else {
$ns=~s/_/ /g;
my %x=$api->namespace_map();
$ns=$x{$ns} // undef;
return undef unless defined($ns);
}
my %x=$api->namespace_reverse_map();
return $x{$ns} // undef;
}
1;