User:AnomieBOT/source/tasks/WikiProjectWorker.pm

This is an old revision of this page, as edited by AnomieBOT (talk | contribs) at 13:18, 5 October 2019 (Updating published sources: WikiProjectWorker: * Run complete.). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
package tasks::WikiProjectWorker;

=pod

=begin metadata

Bot:      AnomieBOT
Task:     WikiProjectWorker
BRFA:     Wikipedia:Bots/Requests for approval/AnomieBOT 28
Status:   Approved 2009-04-08
Created:  2009-03-27
OnDemand: true

Perform various tasks at the request of the affected WikiProjects:
* Add or remove banners on a specific set of pages (e.g. pages in a category, pages transcluding a template).
* Adjust banner parameters, particularly assessments and task forces.
* Fix banner shells on pages edited for the above reasons.

=end metadata

=cut

use utf8;
use strict;

use Data::Dumper;
use AnomieBOT::Task;
use vars qw/@ISA/;
@ISA=qw/AnomieBOT::Task/;

### Request link, for edit summary.
my $req="[[User:AnomieBOT/req/WikiProject Diptera|request]]";

### Increment this number every time a new run is started, so we don't have to
### mess around with deleting previous runs' database entries.
my $seq=25;

### How to find the pages?
my @cats=(
    'Category:Acalyptratae',
    'Category:Acalyptratae stubs',
    'Category:Acartophthalmidae',
    'Category:Achalcinae',
    'Category:Acroceridae',
    'Category:Acrosticta',
    'Category:Actia (genus)',
    'Category:Actia stubs',
    'Category:Adejeania',
    'Category:Aedeomyia',
    'Category:Aedes',
    'Category:Aedini',
    'Category:Agromyzidae',
    'Category:Amethysa',
    'Category:Andrenosoma',
    'Category:Anisopodidae',
    'Category:Anopheles',
    'Category:Anophelinae',
    'Category:Anthomyiidae',
    'Category:Anthomyzidae',
    'Category:Antyxinae',
    'Category:Armigeres',
    'Category:Aschiza',
    'Category:Aschiza genera',
    'Category:Aschiza stubs',
    'Category:Asilidae',
    'Category:Asilidae genera',
    'Category:Asilidae stubs',
    'Category:Asilinae',
    'Category:Asiloidea',
    'Category:Asiloidea genera',
    'Category:Asiloidea stubs',
    'Category:Asilomorpha',
    'Category:Asilomorpha genera',
    'Category:Asilomorpha stubs',
    'Category:Asilomorpha subfamilies',
    'Category:Asilomorpha taxa',
    'Category:Asilomorph flies of Europe',
    'Category:Asteiidae',
    'Category:Athericidae',
    'Category:Atomosia',
    'Category:Australimyzidae',
    'Category:Austroleptidae',
    'Category:Axymyiomorpha',
    'Category:Babindellinae',
    'Category:Bactrocera',
    'Category:Belgica',
    'Category:Bibionidae',
    'Category:Bibionomorpha',
    'Category:Bibionomorpha genera',
    'Category:Bibionomorpha stubs',
    'Category:Blephariceridae',
    'Category:Blephariceromorpha',
    'Category:Blephariceromorpha stubs',
    'Category:Blepharoneurinae',
    'Category:Bolitophilidae',
    'Category:Bombyliidae',
    'Category:Bombyliidae genera',
    'Category:Bombyliidae stubs',
    'Category:Brachycera',
    'Category:Brachycera families',
    'Category:Brachycera genera',
    'Category:Brachyceran flies of Europe',
    'Category:Brachycera stubs',
    'Category:Brachycera subfamilies',
    'Category:Brachycera taxa',
    'Category:Brachycera tribes',
    'Category:Brachystomatidae',
    'Category:Braulidae',
    'Category:Calliphoridae',
    'Category:Calyptratae',
    'Category:Calyptratae stubs',
    'Category:Camillidae',
    'Category:Campichoetidae',
    'Category:Canacidae',
    'Category:Carnidae',
    'Category:Carnoidea',
    'Category:Carnoidea genera',
    'Category:Carnoidea stubs',
    'Category:Cecidomyiidae',
    'Category:Cecidomyiidae genera',
    'Category:Cecidomyiinae',
    'Category:Celyphidae',
    'Category:Cephalia',
    'Category:Ceratitella',
    'Category:Ceratopogonidae',
    'Category:Cerotainia',
    'Category:Ceroxys',
    'Category:Chaetopsis',
    'Category:Chamaemyiidae',
    'Category:Chaoboridae',
    'Category:Chironomidae',
    'Category:Chironomidae stubs',
    'Category:Chironomoidea',
    'Category:Chironomoidea genera',
    'Category:Chironomoidea stubs',
    'Category:Chloropidae',
    'Category:Chloropidae genera',
    'Category:Chloropinae',
    'Category:Chrysomyza',
    'Category:Chyromyidae',
    'Category:Clusiidae',
    'Category:Conopidae',
    'Category:Conopoidea genera',
    'Category:Conopoidea stubs',
    'Category:Coquillettidia',
    'Category:Cremifaniidae',
    'Category:Culex',
    'Category:Culicidae',
    'Category:Culicinae',
    'Category:Culicoidea',
    'Category:Culicoidea genera',
    'Category:Culicoidea stubs',
    'Category:Culicoides',
    'Category:Culicomorpha',
    'Category:Culicomorpha genera',
    'Category:Culicomorpha stubs',
    'Category:Cyamops',
    'Category:Cylindrotomidae',
    'Category:Dacinae',
    'Category:Dacinae stubs',
    'Category:Dasymetopa',
    'Category:Dexiinae',
    'Category:Dexiinae stubs',
    'Category:Diadocidiidae',
    'Category:Diaphorinae',
    'Category:Diastatidae',
    'Category:Diopsidae',
    'Category:Diopsoidea',
    'Category:Diopsoidea genera',
    'Category:Diopsoidea stubs',
    'Category:Diptera by classification',
    'Category:Diptera by continent',
    'Category:Diptera by ___location',
    'Category:Diptera by region',
    'Category:Diptera families',
    'Category:Diptera genera',
    'Category:Diptera of Africa',
    'Category:Diptera of Asia',
    'Category:Diptera of Australasia',
    'Category:Diptera of Europe',
    'Category:Diptera of New Zealand',
    'Category:Diptera of North America',
    'Category:Diptera of Scandinavia',
    'Category:Diptera of South America',
    'Category:Diptera pests and diseases',
    'Category:Diptera stubs',
    'Category:Diptera subfamilies',
    'Category:Diptera superfamilies',
    'Category:Diptera taxa',
    'Category:Diptera taxa by rank',
    'Category:Diptera taxonomy',
    'Category:Diptera tribes',
    'Category:Diptera used as pest control agents',
    'Category:Ditomyiidae',
    'Category:Dixidae',
    'Category:Dolichopodidae',
    'Category:Dolichopodidae genera',
    'Category:Dolichopodidae stubs',
    'Category:Dolichopodidae subfamilies',
    'Category:Dolichopodinae',
    'Category:Dolichopus',
    'Category:Dorycera',
    'Category:Drosophila',
    'Category:Drosophila melanogaster',
    'Category:Drosophila melanogaster genes',
    'Category:Drosophila melanogaster genetics',
    'Category:Drosophilidae',
    'Category:Drosophilidae genera',
    'Category:Drosophilidae stubs',
    'Category:Dryomyzidae',
    'Category:Empididae',
    'Category:Empidoidea',
    'Category:Empidoidea genera',
    'Category:Empidoidea stubs',
    'Category:Empyelocera',
    'Category:Enliniinae',
    'Category:Ephydridae',
    'Category:Ephydroidea',
    'Category:Ephydroidea genera',
    'Category:Ephydroidea stubs',
    'Category:Eristalinae',
    'Category:Euleia',
    'Category:Eumerini',
    'Category:Euxesta',
    'Category:Euxesta stubs',
    'Category:Exoristinae',
    'Category:Exoristinae stubs',
    'Category:Fanniidae',
    'Category:Ficalbia',
    'Category:Flies and humans',
    'Category:Fly biology',
    'Category:Fly ecology',
    'Category:Goniaea',
    'Category:Heizmannia',
    'Category:Helcomyzidae',
    'Category:Heleomyzidae',
    'Category:Heleomyzidae stubs',
    'Category:Hexachaeta',
    'Category:Hippoboscidae',
    'Category:Hippoboscoidea',
    'Category:Hippoboscoidea genera',
    'Category:Hippoboscoidea stubs',
    'Category:Hodgesia',
    'Category:Homalocephala',
    'Category:Hoverflies',
    'Category:Hoverfly genera',
    'Category:Hybotidae',
    'Category:Hydrophorinae',
    'Category:Hydrophorus',
    'Category:Keroplatidae',
    'Category:Laphria',
    'Category:Laphriinae',
    'Category:Laphystia',
    'Category:Lauxaniidae',
    'Category:Lauxanioidea',
    'Category:Lauxanioidea genera',
    'Category:Lauxanioidea stubs',
    'Category:Leptoconops',
    'Category:Limoniidae',
    'Category:Lists of Diptera',
    'Category:Lists of Diptera by ___location',
    'Category:Lists of Diptera genera',
    'Category:Lonchaeidae',
    'Category:Lonchaeoidea',
    'Category:Lonchopteridae',
    'Category:Mansonia',
    'Category:Medeterinae',
    'Category:Melieria',
    'Category:Micropezidae',
    'Category:Microphorinae',
    'Category:Mimomyia',
    'Category:Monogeneric Diptera families',
    'Category:Monotypic Brachycera genera',
    'Category:Monotypic Diptera genera',
    'Category:Monotypic Diptera taxa',
    'Category:Mosquito genera',
    'Category:Muscidae',
    'Category:Muscidae genera',
    'Category:Muscidae stubs',
    'Category:Muscoidea',
    'Category:Muscoidea genera',
    'Category:Muscoidea stubs',
    'Category:Muscomorpha',
    'Category:Muscomorpha genera',
    'Category:Muscomorpha stubs',
    'Category:Muscomorph flies of Europe',
    'Category:Mycetophilidae',
    'Category:Mydidae',
    'Category:Myennis',
    'Category:Myoleja',
    'Category:Nematocera',
    'Category:Nematocera families',
    'Category:Nematocera genera',
    'Category:Nematoceran flies of Europe',
    'Category:Nematocera stubs',
    'Category:Nematocera subfamilies',
    'Category:Nematocera taxa',
    'Category:Nematocera tribes',
    'Category:Nemestrinoidea',
    'Category:Nemestrinoidea genera',
    'Category:Nemestrinoidea stubs',
    'Category:Nerioidea',
    'Category:Nerioidea genera',
    'Category:Nerioidea stubs',
    'Category:Neurigoninae',
    'Category:Nycteribiidae',
    'Category:Ochlerotatus',
    'Category:Odiniidae',
    'Category:Oestridae',
    'Category:Oestroidea',
    'Category:Oestroidea genera',
    'Category:Oestroidea stubs',
    'Category:Opomyzidae',
    'Category:Opomyzoidea',
    'Category:Opomyzoidea genera',
    'Category:Opomyzoidea stubs',
    'Category:Ortalis (fly)',
    'Category:Orthopodomyia',
    'Category:Oscinellinae',
    'Category:Otites',
    'Category:Otitinae',
    'Category:Pallopteridae',
    'Category:Paragorgopis',
    'Category:Parasitic flies',
    'Category:Parathalassiinae',
    'Category:Paravilla',
    'Category:Pediciidae',
    'Category:Peloropeodinae',
    'Category:Perilampsis',
    'Category:Periscelididae',
    'Category:Phasiinae',
    'Category:Phasiinae stubs',
    'Category:Phoridae',
    'Category:Physiphora',
    'Category:Phytalmiinae',
    'Category:Phytalmiinae stubs',
    'Category:Phytomyza',
    'Category:Piophilidae',
    'Category:Pipunculidae',
    'Category:Plagioneurinae',
    'Category:Platypezidae',
    'Category:Platypezoidea',
    'Category:Platypezoidea genera',
    'Category:Platypezoidea stubs',
    'Category:Platystomatidae',
    'Category:Pollinator flies',
    'Category:Prehistoric Diptera',
    'Category:Prehistoric Diptera genera',
    'Category:Procecidochares',
    'Category:Psilidae',
    'Category:Psychodidae',
    'Category:Psychodomorpha',
    'Category:Psychodomorpha genera',
    'Category:Psychodomorpha stubs',
    'Category:Pterocalla',
    'Category:Pterocerina',
    'Category:Ptychopteridae',
    'Category:Ptychopteromorpha',
    'Category:Pyrgotidae',
    'Category:Rhagionidae',
    'Category:Rhagionoidea',
    'Category:Rhagionoidea stubs',
    'Category:Rhagoletis',
    'Category:Rhagoletis stubs',
    'Category:Rhaphiinae',
    'Category:Rhinophoridae',
    'Category:Sarcophagidae',
    'Category:Scathophagidae',
    'Category:Scatopsidae',
    'Category:Schizophora',
    'Category:Schizophora genera',
    'Category:Schizophora stubs',
    'Category:Sciapodinae',
    'Category:Sciaridae',
    'Category:Sciaroidea',
    'Category:Sciaroidea genera',
    'Category:Sciaroidea stubs',
    'Category:Sciomyzidae',
    'Category:Sciomyzoidea',
    'Category:Sciomyzoidea genera',
    'Category:Sciomyzoidea stubs',
    'Category:Scutelliseta',
    'Category:Seioptera',
    'Category:Sepsidae',
    'Category:Sericomyiini',
    'Category:Simuliidae',
    'Category:Simulium',
    'Category:Sphaeroceridae',
    'Category:Sphaeroceroidea',
    'Category:Sphaeroceroidea genera',
    'Category:Sphaeroceroidea stubs',
    'Category:Spheginobacchini',
    'Category:Stratiomyidae',
    'Category:Stratiomyidae stubs',
    'Category:Stratiomyoidea',
    'Category:Stratiomyomorpha',
    'Category:Stratiomyomorpha genera',
    'Category:Stratiomyomorpha stubs',
    'Category:Strauzia',
    'Category:Sympycninae',
    'Category:Syrphidae stubs',
    'Category:Syrphinae',
    'Category:Syrphini',
    'Category:Syrphoidea',
    'Category:Syrphoidea genera',
    'Category:Syrphoidea stubs',
    'Category:Tabanidae',
    'Category:Tabanoidea',
    'Category:Tabanoidea genera',
    'Category:Tabanoidea stubs',
    'Category:Tabanomorpha',
    'Category:Tabanomorpha genera',
    'Category:Tabanomorpha stubs',
    'Category:Tachina',
    'Category:Tachina stubs',
    'Category:Tachinidae',
    'Category:Tachinidae genera',
    'Category:Tachinidae stubs',
    'Category:Tachininae',
    'Category:Tachininae stubs',
    'Category:Tachiniscinae',
    'Category:Tanypodinae',
    'Category:Tephritidae',
    'Category:Tephritidae genera',
    'Category:Tephritidae stubs',
    'Category:Tephritinae',
    'Category:Tephritinae stubs',
    'Category:Tephritoidea',
    'Category:Tephritoidea genera',
    'Category:Tephritoidea stubs',
    'Category:Tetanops',
    'Category:Thaumaleidae',
    'Category:Therevidae',
    'Category:Timia (fly)',
    'Category:Tipulidae',
    'Category:Tipulidae stubs',
    'Category:Tipuloidea',
    'Category:Tipuloidea genera',
    'Category:Tipuloidea stubs',
    'Category:Tipulomorpha',
    'Category:Tipulomorpha genera',
    'Category:Tipulomorpha stubs',
    'Category:Toxorhynchites',
    'Category:Tripteroides',
    'Category:Trypeta',
    'Category:Trypeta stubs',
    'Category:Trypetinae',
    'Category:Trypetinae stubs',
    'Category:Ulidia',
    'Category:Ulidiidae',
    'Category:Ulidiidae stubs',
    'Category:Ulidiinae',
    'Category:Uranotaenia',
    'Category:Urophora',
    'Category:Urophora stubs',
    'Category:Vermileonomorpha',
    'Category:Xanthochlorinae',
    'Category:Xylomyidae',
    'Category:Xylophagidae',
    'Category:Xylophagomorpha',
    'Category:Xylophagomorpha stubs',
);
my @iterators=(
    {
        generator  => 'categorymembers',
        gcmtitle   => [@cats],
        gcmlimit   => 100,
    },
);

### Filter function: manipulate the found data as necessary, returning the talk
### page to tag (or undef to skip).
sub filter {
    $_[0]->{'title'}='Talk:'.$_[0]->{'title'} if $_[0]->{'ns'}==0;
    $_[0]->{'title'}=~s/^([^:]*):/$1 talk:/ if($_[0]->{'ns'}!=0 && ($_[0]->{'ns'}&1)==0);
    return $_[0]->{'title'};
}

### How to copy other projects' assessments
sub copy_class {
    return ($_[0]->WPBmax($_[2]))[0];
    #return '';
}

my $always_copy_importance = 0;
my $set_empty_importance = 0;
sub copy_importance {
    return ($_[0]->WPBmax($_[2]))[1];
    #return '';
}

# Banner configurations.
my $main_banner='WikiProject Diptera';
my @preprocess_templates=();
my %banner_cfgs=(
    'WikiProject Diptera' => {
        meta => 0,
        canonicalize => 'WikiProject Diptera',
    },
);

my @bio_wg = (
);

my @main_banners=(
    $main_banner,
);

sub new {
    my $class=shift;
    my $self=$class->SUPER::new();
    $self->{'config loaded'}=0;
    $self->{'iter'}=undef;
    $self->{'iterators'}=[@iterators];
    bless $self, $class;
    return $self;
}

=pod

=for info
Approved 2009-04-08<br />[[Wikipedia:Bots/Requests for approval/AnomieBOT 28]]

=cut

sub approved {
    return -1;
}

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

    $api->task('WikiProjectWorker', 0, 10, qw/d::WikiProjectTagging/);
    my $errto = 'Errors? [[User:'.$api->user.'/shutoff/WikiProjectWorker]]';

    # Load configs, if necessary
    if(!$self->{'config loaded'}){
        my %cfg=();
        while(my ($banner,$cfg)=each %banner_cfgs){
            $cfg=$api->WPBMetaConfig($cfg->{'meta'}, %$cfg) if exists($cfg->{'meta'});
            $cfg{$banner}=$cfg;
            $banner_cfgs{$banner}{'stubauto'}=$cfg->{'stubauto'};
        }
        $api->WPBconfig(%cfg);

        $self->{'config loaded'}=1;
    }
    if(($api->store->{'configured'} // 0) < $seq){
        ### Initialize configuration here
    }

    # Spend a max of 5 minutes on this task before restarting
    my $endtime=time()+300;

    while(1){
        my $iter=$self->{'iter'};
        if(!defined($iter)){
            my $i=shift @{$self->{'iterators'}};
            last unless $i;
            $iter=$api->iterator(%$i);
            $self->{'iter'}=$iter;
        }
        while(my $page=$iter->next()){
            if(!$page->{'_ok_'}){
                $api->warn("Could not retrieve page from iterator: ".$page->{'error'}."\n");
                return 60;
            }

            my $pageid=$page->{'pageid'};
            next if ($api->store->{$pageid} // 0) >= $seq;

            my $title=filter($page);
            if(!defined($title)){
                $api->log("Skipping ".$page->{'title'}.", filter returned undef");
                $api->store->{$pageid}=$seq;
                next;
            }

            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");
                next;
            }
            if(($tok->{'ns'}&1)==0){
                $api->log("Cannot edit $title: namespace ".$tok->{'ns'}." is non-talk");
                $api->store->{$pageid}=$seq;
                next;
            }
            if(exists($tok->{'redirect'})){
                $api->log("$title is a redirect, skipping.");
                $api->store->{$pageid}=$seq;
                next;
            }

            $api->log("Checking $title...");

            my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'} // '';
            my ($outtxt,$nowiki)=$api->strip_nowiki($intxt);

            my @params=();

            my $assess = undef;
            my $class = copy_class($api, $title, $intxt);
            if(ref($class) eq 'HASH'){
                $api->warn("Processing $title failed: ".$class->{'error'}."\n");
                next;
            }
            $class=~s/^\s+|\s+$//g;
            $assess=$api->WPBassess($title);
            if(ref($assess) eq 'HASH'){
                if($assess->{'code'} eq 'pagemissing'){
                    # No subject page, doesn't matter
                    $assess=undef;
                } else {
                    $api->warn("Processing $title failed: ".$assess->{'error'}."\n");
                    next;
                }
            }

            my $copy_importance = $always_copy_importance; # Always copy?
            if($class eq '' || lc($class) eq lc($assess)){
                $class='';
            } else {
                $assess=undef;
                push @params, "class=$class";
                $copy_importance = 1;
            }

            if($copy_importance){
                my $imp = copy_importance($api, $title, $intxt);
                if(ref($imp) eq 'HASH'){
                    $api->warn("Processing $title failed: ".$imp->{'error'}."\n");
                    next;
                }
                push @params, "importance=$imp" if ( $set_empty_importance || ($imp//'') ne '' );
            }

            my $need_main_banner = ($api->WPBcheck($outtxt, $main_banner) == 0);
            if(0){
                # grr, already redirected so the above always returns true
                $need_main_banner=1;
                $api->WPBcheck($outtxt, sub {
                        my $banner=shift;
                        my $name=shift;
                        $need_main_banner=0 if(grep $name eq $_, @main_banners);
                        return undef;
                    }, $main_banner);
            }

            ### PROCESSING ###

            my @merge=();
            my @summary=();
            my $no_summary = 0;

            if(@preprocess_templates){
                (undef,$outtxt)=$api->WPBcheck($outtxt, sub {
                    my $banner=shift;
                    my $name=shift;
                    my $oname=shift;
                    my $params=shift;
                    my $wikitext=shift;

                    return undef if(grep $name eq $_, @main_banners);

                    push @merge, $oname;

                    return '' unless $need_main_banner;
                    $need_main_banner=0;
                    return "{{$main_banner}}";
                }, @preprocess_templates);
                if(ref($outtxt) eq 'HASH'){
                    $api->warn("Preprocessing $title failed: ".$outtxt->{'error'}."\n");
                    next;
                }
                if(@merge){
                    $merge[-1]='and '.$merge[-1] if @merge>1;
                    push @summary, "merging ".join((@merge>2)?', ':' ', @merge)." into $main_banner" if @merge;
                    $no_summary = 1 if @merge;
                } else {
                #    $api->warn("$title contains ".$iter->iterval.", but not in section 0\n");
                    next;
                }
            }

            if ( @bio_wg ) {
                my %added_wg=();
                (undef,$outtxt)=$api->WPBcheck($outtxt, sub {
                    my $banner=shift;
                    my $name=shift;
                    my $oname=shift;
                    my $params=shift;
                    my $wikitext=shift;

                    my %have = ();
                    foreach ($api->process_paramlist(@$params)) {
                        $have{$_->{'name'}} = 1;
                    }
                    my $any = 0;
                    foreach my $p (@bio_wg){
                        next if exists($have{$p});
                        push @$params, "$p=yes";
                        $added_wg{"$p=yes"}=1;
                        $any = 1;
                    }
                    return undef unless $any;

                    my $out="{{$oname";
                    $out.="|".join("|", @$params) if @$params;
                    $out.="}}";
                    return $out;
                }, 'WikiProject Biography');
                if(ref($outtxt) eq 'HASH'){
                    $api->warn("WPBIO workgroups in $title failed: ".$outtxt->{'error'}."\n");
                    next;
                }
                if(%added_wg){
                    my @added_wg = keys %added_wg;
                    $added_wg[-1]='and '.$added_wg[-1] if @added_wg>1;
                    push @summary, "added " . join((@added_wg>2)?', ':' ', @added_wg) . " to {{WikiProject Biography}}";
                }
            }

            my $tag;
            my $pg=$title;
            $pg=~s/^Talk://;
            if($page->{'title'} ne $pg){
                $page=$api->query(
                    titles       => $pg,
                    prop         => 'info',
                    redirects    => 1,
                );
                if($page->{'code'} ne 'success'){
                    $api->warn("Could not load $pg info: ".$page->{'error'}."\n");
                    return 60;
                }

                $page=(values %{$page->{'query'}{'pages'}})[0];
            }

            $outtxt=$api->WPBadd($outtxt, $assess, sub {
                my $banner=shift; # banner
                my $name=shift; # name
                my $oname=shift;
                my $params=shift;
                my $wikitext=shift;
                my $new=shift;
                #return '' if $new;

                return undef unless(grep $banner eq $_, @main_banners);
                my %cfg=%{$banner_cfgs{$banner}};

                my $any=0;
                foreach my $p (@params){
                    next unless $p=~/^(.+?)=(.*)$/;
                    my ($k,$v)=($1,$2);
                    my $re=qr/\S.*?/;
                    unless(grep(/^\s*\Q$k\E\s*=\s*$re\s*$/, @$params)){
                        next if(grep(/^\s*\Q$k\E\s*=\s*(?i:\Q$v\E)\s*$/s, @$params));
                        unshift @$params, "$k=$v" unless(grep(s/^(\s*\Q$k\E\s*=\s*?)(?:\S.*?)?(\s*)$/$1$v$2/s, @$params));
                        $any=1 unless $v eq '';
                        if($k eq 'class' && exists($cfg{'meta'}) && ($cfg{'stubauto'}//'')){
                            my $aa=$cfg{'stubauto'};
                            $aa=~s/\s*=.*//;
                            push @$params, "$aa=inherit" unless(grep(s/^(\s*\Q$aa\E\s*=\s*?)(?:\S.*?)?(\s*)$/$1inherit$2/s, @$params));
                        }
                    }
                }
                if($new && $class && (grep /^class=/, @$params) && exists($cfg{'meta'}) && ($cfg{'stubauto'}//'')){
                    my $aa=$cfg{'stubauto'};
                    $aa=~s/\s*=.*//;
                    push @$params, "$aa=inherit" unless(grep(s/^(\s*\Q$aa\E\s*=\s*?)(?:\S.*?)?(\s*)$/$1inherit$2/s, @$params));
                    $any=1;
                }
                return $wikitext unless $any;

                if(defined($cfg{'canonicalize'})){
                    my $n=$cfg{'canonicalize'};
                    $oname=~s/_/ /g;
                    $oname=~s/^(\s*)\S(?:.*\S)?(\s*)$/$1$n$2/is;
                }
                my $out="{{$oname";
                $out.="|".join("|", @$params) if @$params;
                $out.="}}";
                return $out;
            }, $main_banner, @params);
            if(ref($outtxt) eq 'HASH'){
                $api->warn("Processing $title failed: ".$outtxt->{'error'}."\n");
                next;
            }
            push @summary, "Tagging with {{$main_banner}}" unless $no_summary;

            $outtxt=$api->replace_nowiki($outtxt, $nowiki);

            # Need to edit?
            if($outtxt ne $intxt){
                if(!@summary){
                    $api->warn("$title changed, but nothing in \@summary\n");
                    next;
                }
                my $cat=$iter->iterval;
                $summary[-1]='and '.$summary[-1] if @summary>1;
                my $summary = ucfirst(join((@summary>2)?', ':' ', @summary))." per $req";

                my @cleanup=();
                $outtxt=$api->WPBfixshell($outtxt, \@cleanup);
                if(ref($outtxt) eq 'HASH'){
                    $api->warn("Processing $title failed: ".$outtxt->{'error'}."\n");
                    next;
                }
                $summary.="; general banner cleanup (".join(', ', @cleanup).")" if @cleanup;
                $summary.=". $errto";

                $api->log("$summary in $title");
                my $r=$api->edit($tok, $outtxt, $summary, 1, 1);
                if($r->{'code'} ne 'success'){
                    $api->warn("Write failed on $title: ".$r->{'error'}."\n");
                    next;
                }
            } else {
                $api->log("Nothing to do in $title");
            }

            # Remember that we processed this page already
            $api->store->{$pageid}=$seq;

            # If we've been at it long enough, let another task have a go.
            return 0 if time()>=$endtime;
        }
        $self->{'iter'}=undef;
    }

    # No more pages to check, try again in 10 minutes or so in case of errors.
    $self->{'iter'}=undef;
    $self->{'iterators'}=[@iterators];
    $api->log("WikiProjectWorker may be DONE!");
    return 600;
}