package tasks::MedcabBot;
=pod
=for warning
Due to breaking changes in AnomieBOT::API, this task will probably not run
anymore. If you really must run it, try getting a version from before
2018-08-12.
=begin metadata
Bot: MedcabBot
Task: MedcabBot
BRFA: Wikipedia:Bots/Requests for approval/MedcabBot 2
Status: Inactive 2012-07-27
Created: 2011-10-04
Perform basic clerking tasks for [[Wikipedia:Mediation Cabal]]:
* Update [[Wikipedia:Mediation Cabal/Cases]]
* Mark cases active, inactive, closing, or closed based on activity.
* Notify users about case status.
=end metadata
=cut
use utf8;
use strict;
use AnomieBOT::Task qw/:time/;
use Data::Dumper;
use vars qw/@ISA/;
@ISA=qw/AnomieBOT::Task/;
my $version=3;
my %cat2status=(
'Category:Wikipedia Medcab new cases' => 'New',
'Category:Wikipedia Medcab active cases' => 'Active',
'Category:Wikipedia Medcab cases on hold' => 'On hold',
'Category:Wikipedia Medcab inactive cases' => 'Inactive',
'Category:Wikipedia Medcab cases pending closure' => 'Closing',
);
my %statusmap=(
'NEW' => 'New',
'New' => 'New',
'new' => 'New',
'ACTIVE' => 'Active',
'Open' => 'Active',
'Active' => 'Active',
'active' => 'Active',
'Opened' => 'Active',
'opened' => 'Active',
'open' => 'Active',
'HOLD' => 'On hold',
'Onhold' => 'On hold',
'On hold' => 'On hold',
'hold' => 'On hold',
'Hold' => 'On hold',
'INACTIVE' => 'Inactive',
'Inactive' => 'Inactive',
'Stale' => 'Inactive',
'inactive' => 'Inactive',
'PENDINGCLOSE' => 'Closing',
'Closing' => 'Closing',
'CLOSED' => 'Closed',
'Closed' => 'Closed',
'close' => 'Closed',
'closed' => 'Closed',
);
sub new {
my $class=shift;
my $self=$class->SUPER::new();
bless $self, $class;
return $self;
}
=pod
=for info
Approved 2011-10-29.<br />[[Wikipedia:Bots/Requests for approval/MedcabBot 2]]
=for info
Bot is currently inactive, as MedCab is closed.
=cut
sub approved {
return -211;
}
sub run {
my ($self, $api)=@_;
my $res;
$api->task('MedcabBot', 0, 10, qw/d::Sections d::Timestamp d::Talk/);
my $screwup=' Errors? [[User:'.$api->user.'/shutoff/MedcabBot]]';
my $b0rken=0;
my $botname=$api->user;
my $starttime=time();
my $slow=0;
my $vv=$api->store->{'version'}//0;
if($vv < $version){
$slow=1;
for my $k (keys %{$api->store}){
next unless $k=~/^case /;
my $scase=$api->store->{$k};
delete $scase->{'opened'} if $vv<2;
delete $scase->{'lastrevid'};
$api->store->{$k}=$scase;
}
}
# Database cleanup
while(my ($k,$v)=each %{$api->store}){
next unless $k=~/^case /;
delete $api->store->{$k} if(($v->{'lastedit'}//0) < time-120*86400);
}
# Load the templates processed by the bot
my %templates=$api->redirects_to_resolved('Template:Medcab participant', 'Template:Inactivecase', 'Template:Medcab case update', 'Template:MedcabStatus');
if(exists($templates{''})){
$api->warn("Failed to get medcab template redirects: ".$templates{''}{'error'}."\n");
return 60;
}
# Load the list of cases to be processed
my %cases=();
my $iter=$api->iterator(
generator => 'categorymembers',
gcmtitle => ['Category:Wikipedia Medcab new cases', 'Category:Wikipedia Medcab active cases', 'Category:Wikipedia Medcab cases on hold', 'Category:Wikipedia Medcab inactive cases', 'Category:Wikipedia Medcab cases pending closure'],
gcmnamespace => 4,
gcmtype => 'page',
gcmlimit => 'max',
prop => 'info|categories',
cllimit => 'max',
clcategories => 'Category:Wikipedia Medcab closed cases',
);
while(my $p=$iter->next){
return 0 if $api->halting;
if(!$p->{'_ok_'}){
$api->warn("Failed to retrieve members for ".$iter->iterval.": ".$p->{'error'}."\n");
return 60;
}
next unless $p->{'title'}=~m{^Wikipedia:Mediation Cabal/Cases/(\d+ \S+ \d+/.+)$};
next if grep $_ eq 'Category:Wikipedia Medcab closed cases', @{$p->{'categories'}//[]};
my $case=$1;
$cases{$case}={
case => $case,
status => $cat2status{$iter->iterval},
lastrevid => $p->{'lastrevid'},
};
}
# Update data for edited cases
while(my ($k,$case)=each %cases){
return 0 if $api->halting;
my $scase=($api->store->{"case $k"}//{});
my $edited=$case->{'lastrevid'} != ($scase->{'lastrevid'}//0);
$scase->{'case'}=$case->{'case'};
$scase->{'status'}//=$case->{'status'};
$scase->{'newstatus'}=$case->{'status'};
$scase->{'needcheck'}=1 if $edited;
$scase->{'lastrevid'}=$case->{'lastrevid'};
$scase->{'externaldiscussion'}//='';
if(!exists($scase->{'created'})){
my $res=$api->query(
titles => "Wikipedia:Mediation Cabal/Cases/$k",
prop => 'revisions',
rvprop => 'timestamp',
rvlimit => 1,
rvdir => 'newer',
);
if($res->{'code'} ne 'success'){
$api->warn("Failed to fetch first revision for $k: ".$res->{'error'}."\n");
return 60;
}
$res=(values %{$res->{'query'}{'pages'}})[0];
$scase->{'created'}=ISO2timestamp($res->{'revisions'}[0]{'timestamp'}) if @{$res->{'revisions'}};
$scase->{'created'}//=time;
}
if($edited){
my $res=$api->query(
titles => "Wikipedia:Mediation Cabal/Cases/$k",
prop => 'revisions',
rvprop => 'timestamp|content',
rvlimit => 1,
rvexcludeuser => $api->user,
);
if($res->{'code'} ne 'success'){
$api->warn("Failed to fetch most recent non-bot revision for $k: ".$res->{'error'}."\n");
return 60;
}
$res=(values %{$res->{'query'}{'pages'}})[0];
if(@{$res->{'revisions'}}){
$scase->{'lastedit'}=ISO2timestamp($res->{'revisions'}[0]{'timestamp'});
# Get list of mediators
{
my $mediators='';
my $comment='';
my $externaldiscussion='';
$api->process_templates($res->{'revisions'}[0]{'*'}, sub {
my $name=shift;
my $params=shift;
return undef unless ($templates{"Template:$name"}//'') eq 'Template:MedcabStatus';
foreach ($api->process_paramlist(@$params)){
if($_->{'name'} eq 'mediators'){
$mediators=$_->{'value'}
}
if($_->{'name'} eq 'external discussion'){
$externaldiscussion=$_->{'value'}
}
if($_->{'name'} eq 'comment'){
$comment=$_->{'value'};
$comment=~s/^\s*|\s*$//g;
}
}
return undef;
});
my %seen=();
my @m=grep { !$seen{$_}++ } $mediators=~/\[\[\s*(?i:User)\s*:\s*(.*?)(?:\|.*?)?\s*\]\]/g;
$scase->{'mediators'}=\@m;
$scase->{'externaldiscussion'}=$externaldiscussion;
$scase->{'curcomment'}=$comment;
}
# Get list of parties
for my $s ($api->split_sections($res->{'revisions'}[0]{'*'})){
next unless $s->{'title'} eq 'Who is involved?';
my $x=join("\n", grep /^[*#][^*#:]/, split(/\n/, $s->{'body'}));
if(($scase->{'rawparties'}//'') ne $x) {
my $res2=$api->query(
action => 'parse',
title => "Wikipedia:Mediation Cabal/Cases/$k",
text => $x,
prop => 'links',
);
if($res2->{'code'} ne 'success'){
$api->warn("Failed to parse list of parties in $k: ".$res2->{'error'}."\n");
return 60;
}
$scase->{'rawparties'}=$x;
my %x=map { $_=$_->{'*'}; s#^[^:]*:([^/]+)(?:/.*)?#$1#; $_ => 1; } grep(($_->{'ns'}&~1)==2, @{$res2->{'parse'}{'links'}});
$scase->{'parties'}=[sort keys %x];
}
last;
}
}
}
if($scase->{'externaldiscussion'} ne ''){
my $ed=$scase->{'externaldiscussion'};
$ed=~s/#.*//;
my $res2=$api->query(
titles => $ed,
prop => 'revisions',
rvprop => 'timestamp',
rvlimit => 1,
rvexcludeuser => $api->user,
);
if($res2->{'code'} ne 'success'){
$api->warn("Failed to fetch most recent non-bot revision for $k external discussion at $ed: ".$res2->{'error'}."\n");
return 60;
}
$res2=(values %{$res2->{'query'}{'pages'}})[0];
if(@{$res2->{'revisions'}}){
my $le=ISO2timestamp($res2->{'revisions'}[0]{'timestamp'});
$scase->{'lastedit'}=$le if $le>$scase->{'lastedit'};
}
}
$scase->{'newstatus'}='Active' if($scase->{'newstatus'} ne 'New' && $scase->{'newstatus'} ne 'On hold' && $scase->{'lastedit'}>=time-7*86400);
if($scase->{'newstatus'} eq 'Active' && $scase->{'lastedit'}<time-7*86400){
$scase->{'newstatus'}='Inactive';
$scase->{'spaminactive'}=[@{$scase->{'parties'}}, @{$scase->{'mediators'}}];
}
($scase->{'spamclosing'},$scase->{'newstatus'})=(1,'Closing') if($scase->{'newstatus'} eq 'Inactive' && $scase->{'lastedit'}<time-21*86400);
$scase->{'newstatus'}='Closed' if($scase->{'newstatus'} eq 'Closing' && $scase->{'lastedit'}<time-28*86400);
my $c=$scase->{'comment'}//'';
$scase->{'comment'}='';
$scase->{'comment'}="Inactive since ".strftime('%e %B %Y', gmtime $scase->{'lastedit'})."<!-- MedcabBot -->" if($scase->{'newstatus'} eq 'Inactive' || $scase->{'newstatus'} eq 'Closing');
$scase->{'needcheck'}=1 if $scase->{'status'} ne $scase->{'newstatus'};
$scase->{'needcheck'}=1 if $c ne $scase->{'comment'};
if($scase->{'newstatus'} ne 'New' && !exists($scase->{'opened'})){
$scase->{'opened'}=time;
if($slow){
# Ugh.
my $res2=$api->query(
titles => "Wikipedia:Mediation Cabal/Cases/$k",
prop => 'revisions',
rvprop => 'ids|timestamp',
rvlimit => 'max',
);
if($res2->{'code'} ne 'success'){
$api->warn("Failed to fetch revision list for $k: ".$res2->{'error'}."\n");
return 60;
}
$res2=(values %{$res2->{'query'}{'pages'}})[0]{'revisions'};
for my $r (@$res2){
my $res3=$api->query(
action => 'parse',
oldid => $r->{'revid'},
prop => 'categories',
);
if($res3->{'code'} ne 'success'){
$api->warn("Failed to parse revision ".$r->{'revid'}." for $k: ".$res3->{'error'}."\n");
return 60;
}
if(grep $_->{'*'} eq 'Wikipedia_Medcab_new_cases', @{$res3->{'parse'}{'categories'}}){
last;
} else {
$scase->{'opened'}=ISO2timestamp($r->{'timestamp'});
}
}
}
}
delete $scase->{'held'} if $scase->{'newstatus'} ne 'On hold';
if($scase->{'newstatus'} eq 'On hold' && !exists($scase->{'held'})){
$scase->{'held'}=time;
if($slow){
# Ugh.
my $res2=$api->query(
titles => "Wikipedia:Mediation Cabal/Cases/$k",
prop => 'revisions',
rvprop => 'ids|timestamp',
rvlimit => 'max',
);
if($res2->{'code'} ne 'success'){
$api->warn("Failed to fetch revision list for $k: ".$res2->{'error'}."\n");
return 60;
}
$res2=(values %{$res2->{'query'}{'pages'}})[0]{'revisions'};
for my $r (@$res2){
my $res3=$api->query(
action => 'parse',
oldid => $r->{'revid'},
prop => 'categories',
);
if($res3->{'code'} ne 'success'){
$api->warn("Failed to parse revision ".$r->{'revid'}." for $k: ".$res3->{'error'}."\n");
return 60;
}
if(grep $_->{'*'} eq 'Wikipedia_Medcab_cases_on_hold', @{$res3->{'parse'}{'categories'}}){
last;
} else {
$scase->{'held'}=ISO2timestamp($r->{'timestamp'});
}
}
}
}
$api->store->{"case $k"}=$scase;
}
$api->store->{'version'} = $version;
# Now, update any pages that need updating
for my $k (keys %cases){
return 0 if $api->halting;
my $scase=$api->store->{"case $k"};
next unless $scase->{'needcheck'};
my $tok=$api->edittoken("Wikipedia:Mediation Cabal/Cases/$k", 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 $k: ".$tok->{'error'}."\n");
return 60;
}
my $intxt=$tok->{'revisions'}[0]{'*'};
my @summary=();
my $istaggedinactive=0;
my $curcomment=$scase->{'curcomment'}//'';
my $outtxt=$api->process_templates($intxt, sub {
my $name=shift;
my $params=shift;
my $wikitext=shift;
shift; # $data
my $oname=shift;
$istaggedinactive=1 if ($templates{"Template:$name"}//'') eq 'Template:Inactivecase';
return undef unless ($templates{"Template:$name"}//'') eq 'Template:MedcabStatus';
my $status='';
my $comment='';
foreach ($api->process_paramlist(@$params)){
if($_->{'name'} eq 'status'){
my $v=$_->{'value'};
$status=$statusmap{$v}//$v;
}
$comment=$_->{'value'} if $_->{'name'} eq 'comment';
}
if($status eq 'Closing' && $comment!~/<!-- MedcabBot -->/){
$scase->{'newstatus'}='Closing';
$scase->{'comment'}='';
}
my @ch=();
my $ret="{{$oname";
my $didstatus=0;
my $didcomment=0;
foreach ($api->process_paramlist(@$params)){
if($_->{'name'} eq 'status'){
my $v=$_->{'value'};
$v=$statusmap{$v}//$v;
my $nl=($_->{'text'}=~/[\r\n]\s*$/)?"\n":"";
if($v ne $scase->{'newstatus'}){
$_->{'text'}=$_->{'oname'}.'='.$scase->{'newstatus'}.$nl;
push @ch, "status=".$scase->{'newstatus'};
}
$didstatus=1;
}
if($_->{'name'} eq 'comment'){
$didcomment=1;
my $nl=($_->{'text'}=~/[\r\n]\s*$/)?"\n":"";
if($scase->{'comment'}){
if($scase->{'comment'} ne $_->{'value'}){
$_->{'text'}=$_->{'oname'}.'='.$scase->{'comment'}.$nl;
push @ch, "update comment";
$curcomment=$scase->{'comment'};
}
} elsif($_->{'text'}=~/<!-- MedcabBot -->/) {
$_->{'text'}=$_->{'oname'}.'='.$nl;
push @ch, "remove bot comment";
$curcomment='';
}
}
$ret.='|'.$_->{'text'};
}
if(!$didstatus){
my $nl=($ret=~/[\r\n]\s*$/)?"\n":"";
$ret.="|status=".$scase->{'newstatus'}.$nl;
push @ch, "status=".$scase->{'newstatus'};
}
if(!$didcomment && $scase->{'comment'}){
my $nl=($ret=~/[\r\n]\s*$/)?"\n":"";
$ret.="|comment=".$scase->{'comment'}.$nl;
push @ch, "add comment";
$curcomment=$scase->{'comment'};
}
$ret.="}}";
$wikitext=~s/\s+/ /g;
my $x=$ret; $x=~s/\s+/ /g;
return undef if $x eq $wikitext;
push @summary, "update {{MedcabStatus}} (".join(', ', @ch).")";
return $ret;
});
# Add or remove {{inactivecase}}
if($scase->{'lastedit'} < time-7*86400){
$outtxt="{{inactivecase}}\n".$outtxt if !$istaggedinactive;
push @summary, "tag {{inactivecase}}" if !$istaggedinactive;
} elsif($istaggedinactive) {
$outtxt=$api->process_templates($outtxt, sub {
my $name=shift;
return undef if ($templates{"Template:$name"}//'') ne 'Template:Inactivecase';
push @summary, "remove {{inactivecase}}";
return '';
});
}
if(@summary){
$summary[$#summary]='and '.$summary[$#summary] if @summary>1;
my $summary=join((@summary>2)?', ':' ', @summary);
$api->log("$summary in $k");
$res=$api->edit($tok, $outtxt, $summary, 0, 1);
if($res->{'code'} ne 'success'){
$api->warn("Failed to edit $k: ".$res->{'error'}."\n");
return 60;
}
}
$scase->{'status'}=$scase->{'newstatus'};
$scase->{'curcomment'}=$curcomment;
$scase->{'needcheck'}=0;
$api->store->{"case $k"}=$scase;
}
# Spam anyone that needs spamming
for my $k (keys %cases){
return 0 if $api->halting;
my $scase=$api->store->{"case $k"};
my $mediator=$scase->{'mediators'}[0]//'';
if($scase->{'status'} ne 'New' && $scase->{'status'} ne 'On hold' && $scase->{'status'} ne 'Closed' && $mediator ne ''){
$scase->{'spammednew'}//=[];
for my $user (@{$scase->{'parties'}}) {
if($api->halting){
$api->store->{"case $k"}=$scase;
return 0;
}
next if grep $_ eq $user, @{$scase->{'spammednew'}};
my $tok=$api->edittoken("User talk:$user", links=>{ namespace=>4 });
if($tok->{'code'} eq 'shutoff'){
$api->warn("Task disabled: ".$tok->{'content'}."\n");
$api->store->{"case $k"}=$scase;
return 300;
}
if($tok->{'code'} eq 'pageprotected' || $tok->{'code'} eq 'botexcluded'){
# Cannot notify, don't worry about it
push @{$scase->{'spammednew'}}, $user;
next;
}
if($tok->{'code'} ne 'success'){
$api->warn("Failed to get edit token for User talk:$user: ".$tok->{'error'}."\n");
next;
}
if(grep $_->{'title'} eq "Wikipedia:Mediation Cabal/Cases/$k", @{$tok->{'links'}}){
$api->log("It seems $user is already notified of $k, skipping");
push @{$scase->{'spammednew'}}, $user;
} else {
my $txt=$tok->{'revisions'}[0]{'*'};
$txt=~s/\s*$/\n\n{{subst:Medcab participant|2=$k|3=$mediator}} ~~~~/;
$api->log("Notifying $user of $k");
$res=$api->edit($tok, $txt, "/* Mediation Cabal: Request for participation */ You have been mentioned in [[Wikipedia:Mediation Cabal/Cases/$k]]", 0, 0);
if($res->{'code'} ne 'success'){
$api->warn("Failed to edit User talk:$user: ".$res->{'error'}."\n");
next;
}
push @{$scase->{'spammednew'}}, $user;
}
}
$api->store->{"case $k"}=$scase;
}
my @users=@{$scase->{'spaminactive'}//[]};
if(@users && $mediator ne ''){
$scase->{'spaminactive'}=[];
while(@users){
if($api->halting){
push @{$scase->{'spaminactive'}}, @users;
$api->store->{"case $k"}=$scase;
return 0;
}
my $user=shift @users;
my $tok=$api->edittoken("User talk:$user");
if($tok->{'code'} eq 'shutoff'){
$api->warn("Task disabled: ".$tok->{'content'}."\n");
push @{$scase->{'spaminactive'}}, $user, @users;
$api->store->{"case $k"}=$scase;
return 300;
}
if($tok->{'code'} eq 'pageprotected' || $tok->{'code'} eq 'botexcluded'){
# Cannot notify, don't worry about it
next;
}
if($tok->{'code'} ne 'success'){
$api->warn("Failed to get edit token for User talk:$user: ".$tok->{'error'}."\n");
push @{$scase->{'spaminactive'}}, $user;
next;
}
my $ed='';
$ed="|external discussion=".$scase->{'externaldiscussion'} if $scase->{'externaldiscussion'} ne '';
my $txt=$tok->{'revisions'}[0]{'*'};
$txt=~s/\s*$/\n\n{{subst:Medcab case update|2=$k|3=$mediator$ed}}/;
$api->log("Notifying $user of $k inactivity");
$res=$api->edit($tok, $txt, "/* Mediation Cabal: Case update */ The case [[Wikipedia:Mediation Cabal/Cases/$k]] you are involved with is inactive", 0, 0);
if($res->{'code'} ne 'success'){
$api->warn("Failed to edit User talk:$user: ".$res->{'error'}."\n");
push @{$scase->{'spaminactive'}}, $user;
next;
}
}
$api->store->{"case $k"}=$scase;
}
if($scase->{'spamclosing'}){{
return 0 if $api->halting;
my $tok=$api->edittoken("Wikipedia talk:Mediation Cabal");
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 Wikipedia talk:Mediation Cabal: ".$tok->{'error'}."\n");
last;
}
my $txt=$tok->{'revisions'}[0]{'*'};
$txt=~s/\s*$//;
$txt.="\n\n== MedcabBot: Case [[Wikipedia:Mediation Cabal/Cases/$k|$k]] pending closure due to inactivity ==\nThe case [[Wikipedia:Mediation Cabal/Cases/$k]]".($scase->{'externaldiscussion'} ne ''?" (with outside discussion at [[:$scase->{externaldiscussion}]])":"")." has been inactive since ".strftime("%e %B %Y", gmtime $scase->{'lastedit'}).", and will be automatically closed at about ".strftime("%H:%M, %e %B %Y", gmtime $scase->{'lastedit'}+28*86400)." (UTC). Note that any non-bot edit to the case page will reset the timer. ~~~~";
$api->log("Notifying Medcab of $k inactivity");
$res=$api->edit($tok, $txt, "/* MedcabBot: Case $k pending closure due to inactivity */ new section", 0, 0);
if($res->{'code'} ne 'success'){
$api->warn("Failed to edit Wikipedia talk:Mediation Cabal: ".$res->{'error'}."\n");
} else {
$scase->{'spamclosing'}=0;
}
}}
$api->store->{"case $k"}=$scase;
}
# Update the case listing page
for my $page ('Wikipedia:Mediation Cabal/Cases', 'Wikipedia:Mediation Cabal/Coordination Desk') {
return 0 if $api->halting;
my $tok=$api->edittoken($page);
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 $page: ".$tok->{'error'}."\n");
return 60;
}
my $intxt=$tok->{'revisions'}[0]{'*'};
my %scases=(
'New' => [],
'Active' => [],
'On hold' => [],
'Inactive' => [],
'Closing' => [],
);
for my $k (keys %cases){
my $scase=$api->store->{"case $k"};
push @{$scases{$scase->{'status'}}}, $scase;
}
my $outtxt=$intxt;
my @tags=();
for my $x (['New','created',0],
['Active','opened',0],
['On hold','held',0],
['Inactive','lastedit',1],
['Closing','lastedit',1]){
my ($tag,$sort,$le)=@$x;
my @cases=@{$scases{$tag}};
@cases=sort { $a->{$sort} <=> $b->{$sort} } @cases;
@cases=map {
my $ret="* [[Wikipedia:Mediation Cabal/Cases/".$_->{'case'}."|".$_->{'case'}."]] — ";
my @mediators=@{$_->{'mediators'}};
if(@mediators){
$ret.=(@mediators==1?'Mediator: ':'Mediators: ');
$ret.=join(', ', map "[[User:$_|$_]]", @mediators)."; ";
}
if($le){
$ret.="opened ".strftime("%e %B %Y", gmtime $_->{'opened'});
$ret.=", inactive since ".strftime("%e %B %Y", gmtime $_->{'lastedit'});
} elsif($tag eq 'On hold'){
$ret.="opened ".strftime("%e %B %Y", gmtime $_->{'opened'});
$ret.=", on hold since ".strftime("%e %B %Y", gmtime $_->{'held'});
} else {
$ret.="$sort ".strftime("%e %B %Y", gmtime $_->{$sort});
}
my $cc=$_->{'curcomment'}//'';
$cc=~s/<!--.*?-->//g;
$cc=~s/^\s*|\s*$//g;
$ret.='.';
$ret.=" '''Comment:''' $cc" if $cc ne '';
$ret.=" ([[:".$_->{'externaldiscussion'}."|external discussion]])" if $_->{'externaldiscussion'} ne "";
$ret.=" ($tag)";
} @cases;
my $cases=join("\n", @cases);
$cases="\n$cases\n" if $cases ne '';
$outtxt=~s/(<!-- BEGIN ${tag}Cases -->).*?(<!-- END ${tag}Cases -->)/$1$cases$2/s;
push @tags, $tag if $outtxt=~/<!-- BEGIN ${tag}Cases -->/;
}
if($outtxt ne $intxt){
my @s=();
for my $tag (@tags){
my $ct=@{$scases{$tag}};
push @s, lc("$ct $tag") if $ct;
}
push @s, 'no cases' unless @s;
my $summary='Updating cases list: '.join(', ', @s);
$api->log($summary);
$res=$api->edit($tok, $outtxt, $summary, 0, 1);
if($res->{'code'} ne 'success'){
$api->warn("Failed to edit $page: ".$res->{'error'}."\n");
return 60;
}
}
}
return 1800;
}
1;