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:
- 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.
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;