Jump to content

User:AnomieBOT/source/tasks/MedComClerk.pm

From Wikipedia, the free encyclopedia
package tasks::MedComClerk;

=pod

=begin metadata

Bot:     MediationBot
Task:    MedComClerk
BRFA:    Wikipedia:Bots/Requests for approval/MediationBot
Status:  Inactive 2018-11-12
Created: 2011-01-12

Perform basic clerking tasks at [[Wikipedia:Requests for mediation]].

=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=0;

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

=pod

=for info
Approved 2011-02-03.<br />[[Wikipedia:Bots/Requests for approval/MediationBot]]

=for info
Bot is currently inactive, as MedCom is closed.

=cut

sub approved {
    return -210;
}

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

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

    my $screwup=' Errors? [[User:'.$api->user.'/shutoff/MedComClerk]]';
    my $b0rken=0;
    my $botname=$api->user;

    my $starttime=time();

    if($api->store->{'version'}//0 < $version){
        $api->store->{'Members revid'}=0;
        $api->store->{'version'} = $version;
    }

    # Get the last rev ID for the members list, to skip downloading the
    # entire page if not necessary.
    $res=$api->query(titles=>'Wikipedia:Mediation Committee/Members', prop=>'revisions', rvprop=>'ids');
    if($res->{'code'} ne 'success'){
        $api->warn("Failed to get revids: ".$res->{'error'}."\n");
        return 60;
    }
    my ($Members_revid)=(undef);
    foreach (values %{$res->{'query'}{'pages'}}){
        $Members_revid=$_->{'revisions'}[0]{'revid'} if $_->{'title'} eq 'Wikipedia:Mediation Committee/Members';
    }
    if(!defined($Members_revid)){
        $api->warn("Response was missing required revids: ".$res->{'error'}."\n");
        return 60;
    }

    # Load the list of committee members, if necessary
    my @Members;
    if($Members_revid==($api->store->{'Members revid'} // 0)){
        @Members=@{$api->store->{'Members'}};
    } else {
        $res=$api->query(
            titles => 'Wikipedia:Mediation Committee/Members',
            prop   => 'revisions',
            rvprop => 'content',
            rvslots => 'main',
        );
        if($res->{'code'} ne 'success'){
            $api->warn("Failed to get Wikipedia:Mediation Committee/Members: ".$res->{'error'}."\n");
            return 60;
        }
        @Members=();
        my $txt=(values %{$res->{'query'}{'pages'}})[0]{'revisions'}[0]{'slots'}{'main'}{'*'};
        $api->process_templates($txt, sub {
            my $name=shift;
            my $params=shift;

            return undef unless $name eq '/Table' || $name eq 'Wikipedia:Mediation Committee/Members/Table';
            foreach ($api->process_paramlist(@$params)){
                next unless $_->{'name'} eq 'user';
                $_->{'value'}=~s/^\s*|\s*$//g;
                push @Members, $_->{'value'};
            }
            return undef;
        });
        if(!@Members){
            $api->warn("Invalid medcom member table\n");
            $api->whine("[[Wikipedia:Mediation Committee/Members]] cannot be processed", "The MedCom Member List table didn't seem to contain any members.", Pagename => 'Wikipedia talk:Mediation Committee/Bot issues');
            return 60;
        }
        $api->store->{'Members revid'}=$Members_revid;
        $api->store->{'Members'}=\@Members;
    }

    # Load the templates used to attract the bot
    my %templates=$api->redirects_to_resolved('Template:Accepted case', 'Template:Rejected case');
    if(exists($templates{''})){
        $api->warn("Failed to get medcom template redirects: ".$templates{''}{'error'}."\n");
        return 60;
    }

    # Fix all pages using these templates
    my $iter=$api->iterator(
        generator      => 'embeddedin',
        geititle       => ['Template:Accepted case', 'Template:Rejected case'],
        geinamespace   => 4,
        geifilterredir => 'nonredirects',
        geilimit       => 'max',
    );
    while(my $p=$iter->next){
        return 0 if $api->halting;
        if(!$p->{'_ok_'}){
            $api->warn("Failed to retrieve transclusions for ".$iter->iterval.": ".$p->{'error'}."\n");
            return 60;
        }
        my $title=$p->{'title'};
        next if $title=~m{^Wikipedia:Requests for mediation/Rejected/\d+$};
        next unless $title=~m{^Wikipedia:Requests for mediation/(.+)$};
        my $subject=$1;
        next if $subject eq 'Pending' || $subject eq 'Rejected cases' || $subject eq 'Tasks';

        my $tok=$api->edittoken($title, 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 $title: ".$tok->{'error'}."\n");
            return 60;
        }
        my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'};

        # Remove template
        my $outtxt=$api->process_templates($intxt, sub {
            my $name=shift;
            return (($templates{"Template:$name"}//'') eq $iter->iterval)?'':undef;
        });

        # Sanity check
        if($outtxt eq $intxt){
            my $t=$iter->iterval; $t=~s/^Template://;
            $api->whine("Cannot find MedCom template in [[$title]]", "I cannot find {{tl|$t}} in [[$title]], which probably means its transcluded in some odd manner. Please correct the case or fix me.", Pagename => 'Wikipedia talk:Mediation Committee/Bot issues');
            next;
        }

        # Find the most recent MedCom editor to edit
        my ($revid,$user,$ts,$txt);
        if(grep $_ eq $tok->{'revisions'}[0]{'user'}, @Members){
            $revid=$tok->{'revisions'}[0]{'revid'};
            $user=$tok->{'revisions'}[0]{'user'};
            $ts=$tok->{'revisions'}[0]{'timestamp'};
            $txt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'};
        } else {
            $res=$api->query(
                titles  => $title,
                prop    => 'revisions',
                rvprop  => 'ids|user|timestamp',
                rvlimit => 'max',
            );
            foreach my $r (@{(values %{$res->{'query'}{'pages'}})[0]{'revisions'}}){
                last if $r->{'user'} eq $botname;
                next unless grep $_ eq $r->{'user'}, @Members;
                $revid=$r->{'revid'};
                $user=$r->{'user'};
                $ts=$r->{'timestamp'};
                last;
            }
            if(!defined($revid)){
                my $t=$iter->iterval; $t=~s/^Template://;
                $api->whine("Problem in [[$title]]", "While I found {{tl|$t}} in [[$title]], I cannot find any edits by a member of MedCom. Something screwy is going on, or else I'm just broken. Please correct the case or fix me.", Pagename => 'Wikipedia talk:Mediation Committee/Bot issues');
                next;
            }
            $res=$api->query(
                revids => $revid,
                prop   => 'revisions',
                rvprop => 'content',
                rvslots => 'main',
            );
            $txt=(values %{$res->{'query'}{'pages'}})[0]{'revisions'}[0]{'slots'}{'main'}{'*'};
            my $outtxt2=$api->process_templates($txt, sub {
                my $name=shift;
                return (($templates{"Template:$name"}//'') eq $iter->iterval)?'':undef;
            });
            # Sanity check
            if($outtxt2 eq $txt){
                my $t=$iter->iterval; $t=~s/^Template://;
                $api->whine("Problem in [[$title]]", "While I found {{tl|$t}} in [[$title]], the most recent revision by a MedCom member did not contain the template. Something screwy is going on, or else I'm just broken. Please correct the case or fix me.", Pagename => 'Wikipedia talk:Mediation Committee/Bot issues');
                next;
            }
        }

        # Extract/construct sig
        my $sig=undef;
        if(!$sig){
            my $t=strftime('%H:%M, %-d %B %Y \\(UTC\\)', gmtime ISO2timestamp($ts));
            $sig=$1 if $txt=~/[fF]or the Mediation Committee,?\s+(.*?\Q$user\E.*?\s+$t)/;
        }
        if(!$sig){
            my $t=strftime('%H:\\d{2}, %-d %B %Y \\(UTC\\)', gmtime ISO2timestamp($ts));
            $sig=$1 if $txt=~/[fF]or the Mediation Committee,?\s+(.*?\Q$user\E.*?\s+$t)/;
        }
        if(!$sig){
            my $t=strftime('\\d{2}:\\d{2}, %-d %B %Y \\(UTC\\)', gmtime ISO2timestamp($ts));
            $sig=$1 if $txt=~/[fF]or the Mediation Committee,?\s+(.*?\Q$user\E.*?\s+$t)/;
        }
        if(!$sig){
            my $t=strftime('%H:%M, %-d %B %Y (UTC)', gmtime ISO2timestamp($ts));
            $sig="[[User:$user]] ([[User talk:$user|talk]]) $t";
        }

        # Find list of involved users
        my @users=find_involved_users($api,$title,$intxt);
        next unless @users;

        # Build messages
        my ($cat,$act);
        if($iter->iterval eq 'Template:Accepted case'){
            $act='accepted';
            $cat='Category:Mediation Committee current cases';
            $outtxt=$api->process_templates($outtxt, sub {
                my $name=shift;
                my $params=shift;
                shift; # $wikitext
                shift; # $data
                my $oname=shift;

                return undef unless $name eq "Medcombox";
                my $ret="{{$oname";
                my $done=0;
                foreach ($api->process_paramlist(@$params)){
                    if($_->{"name"} eq "status"){
                        my $v=$_->{"value"};
                        $_->{"text"}=~s/\Q$v\E/mediator/;
                        $done=1;
                    }
                    $ret.="|".$_->{"text"};
                }
                $ret.="\n|status=mediator" unless $done;
                $ret.="}}";
                return $ret;
            });
        } elsif($iter->iterval eq 'Template:Rejected case'){
            $act='rejected';
            $cat='Category:Requests for mediation rejected requests';
        }
        my $msg="{{subst:User:MediationBot/\u$act message|case=$subject|signature=$sig}}";

        # Adjust categorization
        my $adj='removing template';
        $adj='adjusting category' if $outtxt=~s/\[\[\s*(?i:Category)\s*:\s*(?:Mediation Committee (?:pending|current|rejected) cases|Requests for mediation rejected requests)\s*((?:\|.*?)?)\]\]/[[$cat$1]]/g;

        $api->log("$title $act, $adj");
        $res=$api->edit($tok, $outtxt, "Case $act, $adj. $screwup", 0, 0);
        if($res->{'code'} ne 'success'){
            $api->warn("Failed to edit $title: ".$res->{'error'}."\n");
            return 60;
        }
        if($adj eq 'adjusting category'){
            my $notify=$api->store->{'notify'};
            foreach my $u (@users) {
                $notify->{"$u|$title"}=[$u,$subject,$title,$act,"A request for mediation which you are a party to has been $act",$msg];
            }
            $api->store->{'notify'}=$notify;
        }
    }

    # Update the pending listing page
    {
        return 0 if $api->halting;
        my $tok=$api->edittoken('Wikipedia:Requests for mediation/Pending');
        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:Requests for mediation/Pending: ".$tok->{'error'}."\n");
            return 60;
        }
        my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'};
        my $outtxt=$intxt; $outtxt=~s/(<!--MEDBOT-Do-Not-Remove-Or-Change-This-Line-->).*/$1\n/s;
        $iter=$api->iterator(
            generator    => 'categorymembers',
            gcmtitle     => 'Category:Mediation Committee pending cases',
            gcmnamespace => 4,
            gcmtype      => 'page',
            gcmlimit     => 'max',
            gcmsort      => 'timestamp',
            prop         => 'info',
        );
        my $ct=0;
        while(my $p=$iter->next){
            if(!$p->{'_ok_'}){
                $api->warn("Failed to retrieve members for Category:Mediation Committee pending cases: ".$p->{'error'}."\n");
                return 60;
            }
            my $title=$p->{'title'};
            next unless $title=~m{^Wikipedia:Requests for mediation/(.+)$};
            my $subject=$1;
            $outtxt.="{{$title}}\n";
            $ct++;

            # Notify users of newly-filed case
            if(($api->store->{"new $title"}//0) != $p->{'lastrevid'}){
                my $res2=$api->rawpage($title);
                if($res2->{'code'} ne 'success'){
                    $api->warn("Failed to get content for $title: ".$res2->{'error'}."\n");
                    return 60;
                }
                my $txt=$res2->{'content'};

                # Find list of involved users
                my @users=find_involved_users($api,$title,$txt);
                next unless @users;

                # Build messages
                my $msg="{{subst:User:MediationBot/Opened message|case=$subject}}";

                # Queue notifications
                my $notify=$api->store->{'notify'};
                my $didnotify=$api->store->{'didnotify'};
                foreach my $u (@users) {
                    next if grep $_ eq $u, @{$didnotify->{$title}};
                    $notify->{"opened|$u|$title"}=[$u,$subject,$title,'opened',"A request for formal mediation has been filed for a case in which you are involved",$msg];
                    push @{$didnotify->{$title}}, $u;
                }
                $api->store->{'notify'}=$notify;
                $api->store->{'didnotify'}=$didnotify;
                $api->store->{"new $title"}=$p->{'lastrevid'};
            }
        }
        if($outtxt ne $intxt){
            $api->log("Updating pending case list, $ct listed");
            $res=$api->edit($tok, $outtxt, "Updating pending case list, $ct listed. $screwup", 0, 0);
            if($res->{'code'} ne 'success'){
                $api->warn("Failed to edit Wikipedia:Requests for mediation/Pending: ".$res->{'error'}."\n");
                return 60;
            }
        }
    }

    my %q=(
        list        => 'categorymembers',
        cmnamespace => 4,
        cmtype      => 'page',
        cmlimit     => 'max',
        cmsort      => 'timestamp',
        cmprop      => 'title|timestamp',
    );

    # Update the rejected listing page
    {
        return 0 if $api->halting;
        my $tok=$api->edittoken('Wikipedia:Requests for mediation/Rejected cases', links=>{ namespace=>4 });
        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:Requests for mediation/Rejected cases: ".$tok->{'error'}."\n");
            return 60;
        }
        my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'};
        my @links=map $_->{'title'}, @{$tok->{'links'}};
        my $outtxt=$intxt;
        $iter=$api->iterator(%q, cmtitle => 'Category:Requests for mediation rejected requests');
        my $ct=0;
        my %skip=map { $_ => 1 } @{$api->store->{'processed rejected'} // []};
        my %newskip=();
        while(my $p=$iter->next){
            if(!$p->{'_ok_'}){
                $api->warn("Failed to retrieve members for Category:Requests for mediation rejected requests: ".$p->{'error'}."\n");
                return 60;
            }
            my $title=$p->{'title'};
            next unless $title=~m{^Wikipedia:Requests for mediation/(.+)$};
            my $subject=$1;
            $newskip{$subject}=1;
            next if grep $title eq $_, @links;
            next if exists($skip{$subject});
            my $dt=strftime('%-d %B %Y', gmtime ISO2timestamp($p->{'timestamp'}));
            $outtxt=~s/(<!--MEDBOT-Do-Not-Remove-Or-Change-This-Line-->)/===$subject===\n*[[$title]]\n:*Rejected $dt\n$1/s;
            $ct++;
        }
        if($outtxt ne $intxt){
            $api->log("Updating case list, +$ct rejected");
            $res=$api->edit($tok, $outtxt, "Updating case list, +$ct rejected. $screwup", 0, 0);
            if($res->{'code'} ne 'success'){
                $api->warn("Failed to edit Wikipedia:Requests for mediation/Rejected cases: ".$res->{'error'}."\n");
                return 60;
            }
        }
        $api->store->{'processed rejected'}=[keys %newskip];
    }
    
    # Update the accepted listing page
    {
        return 0 if $api->halting;
        my $tok=$api->edittoken('Wikipedia:Requests for mediation/Tasks', links=>{ namespace=>4 });
        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:Requests for mediation/Tasks: ".$tok->{'error'}."\n");
            return 60;
        }
        my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'};
        my @links=map $_->{'title'}, @{$tok->{'links'}};
        my $outtxt=$intxt;
        $iter=$api->iterator(%q, cmtitle => 'Category:Mediation Committee current cases');
        my $ct_new_cases=0;
        while(my $p=$iter->next){
            if(!$p->{'_ok_'}){
                $api->warn("Failed to retrieve members for Category:Mediation Committee current cases: ".$p->{'error'}."\n");
                return 60;
            }
            my $title=$p->{'title'};
            next unless $title=~m{^Wikipedia:Requests for mediation/(.+)$};
            my $subject=$1;
            next if grep $title eq $_, @links;
            my $dt=strftime('%-d %b %Y', gmtime ISO2timestamp($p->{'timestamp'}));
            $outtxt=~s/(?:\{\{Wikipedia:Requests for mediation\/Open Tasks\/None\}\}\n)?(<!--MEDBOT-Do-Not-Remove-Or-Change-This-Line-->)/{{Wikipedia:Requests for mediation\/Open Tasks\/U|$subject|$dt}}\n$1/s;
            $ct_new_cases++;
        }
        $iter=$api->iterator(%q, cmtitle => 'Category:Mediation Committee Nominations/Pending');
        my $ct_new_noms=0;
        my $ct_rm_noms=0;
        my %rmnoms=map { $_ => 1 } grep(m{^Wikipedia:Mediation Committee/Nominations/.+$}, @links);
        while(my $p=$iter->next){
            if(!$p->{'_ok_'}){
                $api->warn("Failed to retrieve members for Category:Mediation Committee Nominations/Pending: ".$p->{'error'}."\n");
                return 60;
            }
            my $title=$p->{'title'};
            next unless $title=~m{^Wikipedia:Mediation Committee/Nominations/(.+)$};
            next if $title eq 'Wikipedia:Mediation Committee/Nominations/Current';
            my $subject=$1;
            delete $rmnoms{$title};
            next if grep $title eq $_, @links;
            my $dt=strftime('%-d %b %Y', gmtime ISO2timestamp($p->{'timestamp'}));
            $outtxt=~s/(?:\{\{Wikipedia:Requests for mediation\/Open Tasks\/None\}\}\n)?(<!--MEDBOT-Do-Not-Remove-Or-Change-This-Line-Nom-->)/{{Wikipedia:Requests for mediation\/Tasks\/N|$subject|$dt}}\n$1/s;
            $ct_new_noms++;
        }
        if(%rmnoms){
            $outtxt=$api->process_templates($outtxt, sub {
                my $name=shift;
                my $params=shift;
                return undef if $name ne 'Wikipedia:Requests for mediation/Tasks/N';
                foreach ($api->process_paramlist(@$params)){
                    $name="Wikipedia:Mediation Committee/Nominations/".$_->{'value'} if $_->{'name'} eq '1';
                }
                return undef unless exists($rmnoms{$name});
                $ct_rm_noms++;
                return '';
            });
        }
        my $rmnone=0;
        for my $in ($outtxt=~/\|-((?:[^!]|(?><!--.*?-->))*<!--MEDBOT-Do-Not-Remove-Or-Change-This-Line(?:|-Nom)-->(?:[^!]|(?><!--.*?-->))*)(?=\n!)/g){
            my $any=0;
            my $out=$api->process_templates($in, sub {
                my $name=shift;
                return '' if $name eq "Wikipedia:Requests for mediation/Tasks/None";
                $any=1 if $name=~/Wikipedia:Requests for mediation\//;
                return undef;
            });
            if($any && $out ne $in){
                $outtxt=~s/\Q$in\E/$out/g;
                $rmnone=1;
            }
        }
        my $addnone=0;
        for my $in ($outtxt=~/\|-\n! colspan="7".*\n((?:[^!]|(?><!--.*?-->))*)(?=\n\|-\n[!|] colspan="7")/g){
            my $any=0;
            $api->process_templates($in, sub {
                my $name=shift;
                $any=1 if $name=~/Wikipedia:Requests for mediation\//;
                return undef;
            });
            if(!$any){
                $outtxt=~s/\Q$in\E/$in\n{{Wikipedia:Requests for mediation\/Tasks\/None}}/g;
                $addnone=1;
            }
        }
        if($outtxt ne $intxt){
            my @summary=();
            push @summary, "updating case list, +$ct_new_cases accepted" if $ct_new_cases;
            push @summary, "updating nominations list".($ct_new_noms?", +$ct_new_noms current":"").($ct_rm_noms?", -$ct_rm_noms completed":"") if($ct_new_noms || $ct_rm_noms);
            push @summary, "removing unneeded \"Currently none\"" if $rmnone;
            push @summary, "adding needed \"Currently none\"" if $addnone;
            unless(@summary){
                $api->warn("No summary for changes to Wikipedia:Requests for mediation/Tasks!");
                return 60;
            }
            my $summary=join('; ', @summary);
            $summary="\u$summary";
            $api->log($summary);
            $res=$api->edit($tok, $outtxt, "$summary. $screwup", 0, 0);
            if($res->{'code'} ne 'success'){
                $api->warn("Failed to edit Wikipedia:Requests for mediation/Tasks: ".$res->{'error'}."\n");
                return 60;
            }
        }
    }
    
    # Update the pending nominations page
    {
        return 0 if $api->halting;
        my $tok=$api->edittoken('Wikipedia:Mediation Committee/Nominations/Current');
        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:Mediation Committee/Nominations/Current: ".$tok->{'error'}."\n");
            return 60;
        }
        my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'};
        my $outtxt=$intxt; $outtxt=~s/(<!--MEDBOT-Do-Not-Remove-Or-Change-This-Line-->).*/$1\n/s;
        $iter=$api->iterator(%q, cmtitle => 'Category:Mediation Committee Nominations/Pending');
        my $ct=0;
        while(my $p=$iter->next){
            if(!$p->{'_ok_'}){
                $api->warn("Failed to retrieve members for Category:Mediation Committee Nominations/Pending: ".$p->{'error'}."\n");
                return 60;
            }
            my $title=$p->{'title'};
            next unless $title=~m{^Wikipedia:Mediation Committee/Nominations/(.+)$};
            next if $title eq 'Wikipedia:Mediation Committee/Nominations/Current';
            $outtxt.="{{$title}}\n";
            $ct++;
        }
        if($outtxt ne $intxt){
            $api->log("Updating nominations list, $ct listed");
            $res=$api->edit($tok, $outtxt, "Updating nominations list, $ct listed. $screwup", 0, 0);
            if($res->{'code'} ne 'success'){
                $api->warn("Failed to edit Wikipedia:Mediation Committee/Nominations/Current: ".$res->{'error'}."\n");
                return 60;
            }
        }
    }

    my $notify=$api->store->{'notify'};
    foreach my $k (keys %$notify) {
        return 0 if $api->halting;
        my ($user,$subject,$title,$act,$summary,$msg)=@{$notify->{$k}};
        my $tok=$api->edittoken("User talk:$user");
        if($tok->{'code'} eq 'shutoff'){
            $api->warn("Task disabled: ".$tok->{'content'}."\n");
            return 300;
        }
        if($tok->{'code'} eq 'pageprotected' || $tok->{'code'} eq 'botexcluded'){
            # Cannot notify, don't worry about it
            delete $notify->{$k};
            my $cannotnotify=$api->store->{'cannotnotify'}//{};
            push @{$cannotnotify->{$title}}, $user;
            $api->store->{'notify'}=$notify;
            $api->store->{'cannotnotify'}=$cannotnotify;
            next;
        }
        if($tok->{'code'} ne 'success'){
            $api->warn("Failed to get edit token for User talk:$user: ".$tok->{'error'}."\n");
            next;
        }
        my $txt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'}//'';
        if($act eq 'opened' && $txt=~/<!---MedComBot-Do-not-remove-this-line-Notified-\Q$subject\E--->/){
            $api->log("It seems $user is already notified of $act request, skipping");
        } else {
            $txt=~s/\s*$/\n\n$msg/;
            $api->log("Notifying $user of $act request");
            $res=$api->edit($tok, $txt, $summary, 0, 0);
            if($res->{'code'} ne 'success'){
                $api->warn("Failed to edit User talk:$user: ".$res->{'error'}."\n");
                next;
            }
        }
        delete $notify->{$k};
        $api->store->{'notify'}=$notify;
    }

    my $cannotnotify=$api->store->{'cannotnotify'}//{};
    foreach my $title (keys %$cannotnotify) {
        return 0 if $api->halting;
        my @users=@{$cannotnotify->{$title}};
        my $ttitle=$title; $ttitle=~s/^Wikipedia:/Wikipedia talk:/;
        my $tok=$api->edittoken($ttitle);
        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 $ttitle: ".$tok->{'error'}."\n");
            next;
        }
        my $txt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'}//'';
        $txt=~s/\s+$//;
        $txt.="\n\n" if $txt ne '';
        $txt.="== Involved users who cannot be notified ==\n{{ClerkNote}} The following users could not be notified due to page protection or bot exclusion:\n".join("\n", map "* {{user|$_}}", @users)."\nPlease notify them manually, or take other appropriate action. ~~~~\n";
        $api->log("Noting users who cannot be notified");
        $res=$api->edit($tok, $txt, "Noting users who cannot be notified. $screwup", 0, 0);
        if($res->{'code'} ne 'success'){
            $api->warn("Failed to edit $ttitle: ".$res->{'error'}."\n");
        } else {
            delete $cannotnotify->{$title};
            $api->store->{'cannotnotify'}=$cannotnotify;
        }
    }

    return 3600;
}

sub find_involved_users {
    my ($api,$title,$txt) = @_;

    my $userre = qr/\{\{[Uu]ser\|\s*([^\s\x7c\x7d](?:[^\x7c\x7d]*?[^\s\x7c\x7d]))\s*\}\}/;
    $txt=~s/\n::<span style="font-size: 95%;">\{\{Red\|'''Filing party'''\}\}:.*//;
    unless($txt=~/\n(?:; (?:Involved users|(?:Users|Editors) involved in (?:this )?dispute)|===\s*Involved parties\s*===)\s*\n((?:#\s*${userre}[^\n{}]*\n(?:#[:*][^\n{}]*\n)*)+)\s*(?:;|==|<!-- end user list -->)/){
        $api->warn("Cannot find involved users list in $title");
        $api->whine("Cannot find involved users list in [[$title]]", "I cannot find the list of involved users in [[$title]]; I look for the line \"; Editors involved in this dispute\", followed by an ordered list (lines beginning \"#\") with each list entry beginning with the {{tl|user}} template, followed by a new section (a line beginning \"==\"), subsection (a line beginning \";\"), or the special comment \"<code><nowiki><!-- end user list --></nowiki></code>\". Please correct the case or fix me.", Pagename => 'Wikipedia talk:Mediation Committee/Bot issues');
        return ();
    }
    return map ucfirst($_), ($1=~/$userre/g);
}

1;