User:AnomieBOT/source/tasks/MedcabBot.pm

{{ombox|type=content|style=border:1px solid #b22222|image=40px|text= 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.}}

{{ombox|type=notice|text= Approved 2011-10-29.
Wikipedia:Bots/Requests for approval/MedcabBot 2}}

{{ombox|type=notice|text= Bot is currently inactive, as MedCab is closed.}}

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:

=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.
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'}

$scase->{'newstatus'}='Inactive';

$scase->{'spaminactive'}=[@{$scase->{'parties'}}, @{$scase->{'mediators'}}];

}

($scase->{'spamclosing'},$scase->{'newstatus'})=(1,'Closing') if($scase->{'newstatus'} eq 'Inactive' && $scase->{'lastedit'}

$scase->{'newstatus'}='Closed' if($scase->{'newstatus'} eq 'Closing' && $scase->{'lastedit'}

my $c=$scase->{'comment'}//'';

$scase->{'comment'}='';

$scase->{'comment'}="Inactive since ".strftime('%e %B %Y', gmtime $scase->{'lastedit'})."" 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!~//){

$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'}=~//) {

$_->{'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 $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="* ".$_->{'case'}." — ";

my @mediators=@{$_->{'mediators'}};

if(@mediators){

$ret.=(@mediators==1?'Mediator: ':'Mediators: ');

$ret.=join(', ', map "$_", @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.=" (external discussion)" if $_->{'externaldiscussion'} ne "";

$ret.=" ($tag)";

} @cases;

my $cases=join("\n", @cases);

$cases="\n$cases\n" if $cases ne '';

$outtxt=~s/().*?()/$1$cases$2/s;

push @tags, $tag if $outtxt=~//;

}

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;