Jump to content

User:AnomieBOT/source/tasks/WikiProjectTagger.pm

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

=pod

=begin metadata

Bot:      AnomieBOT
Task:     WikiProjectTagger
BRFA:     Wikipedia:Bots/Requests for approval/AnomieBOT 14
Status:   Inactive 2024-01-29
Created:  2008-11-25
OnDemand: true

Tag articles in specified categories with a WikiProject template. Redirects and
disambiguation pages will be automatically assessed with
class=redirect/disambig and importance=NA, stubs will be automatically assessed
with class=stub, and non-article pages will be automatically assessed with the
appropriate class and importance=NA.

=end metadata

=cut

use utf8;
use strict;

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

# Request link, for edit summary.
my $req="[[User:AnomieBOT/req/WikiProject Brands|request]]";
my $screwup="Errors? [[User:AnomieBOT/shutoff/WikiProjectTagger]]";

# 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=14;

# If any of the 'verify' or 'params' functions need the page contents, set this
# flag.
my $need_page_contents=0;

# Extra props for the queries
my $pageprops="";
my %talkprops=();

# If every newly-tagged page should have a particular parameter, put it here
my $autotagged=undef;

# To add empty class/importance even if that is the only change, set this
my $addemptyparams=0;

# If you include a "mark" key in the cfg_cats hash, it will fill in this.
my %mark_cats=();

# Configuration for each template that is going to be applied. Options are:
#   ns => Hash mapping namespace numbers (or 'stub', 'redirect', 'disambig',
#       or '' for ns 0) to an array [ $class, $classre, $imp, $impre].
#   stubauto => If defined, add this "=yes" when tagging a stub.
#   blp => If defined and the template contains a paramater matching this re,
#       "blp=yes" will be added to the bannershell.
#   activepol => If defined and the template contains a paramater matching this
#       re, "activepol=yes" will be added to the bannershell.
#   importance => If specified, this is the name of the "importance" parameter
#       instead of "importance".
#   canonicalize => If defined, any instance of the template will be renamed to
#       this when edits are done to the page.
#   verify => If defined, must be a subroutine that will be called with the
#       page and talkpage results. The subroutine must return a boolean value,
#       true if the page should be tagged and false otherwise.
#   params => Subroutines that will be called with the page and talkpage
#       results, the template name, and the existing template parameters. It
#       should adjust the parameter array as necessary.
my %cfg_templates;
%cfg_templates=(
    'WikiProject Brands' => {
        ns => {
            stub => [ 'stub', qr/\S.*/s, '', qr/\S.*/s ],
            redirect => [ 'redirect', qr/redirect|red|redir/i, 'NA', qr/na/i ],
            disambig => [ 'disambig', qr/disambig|dab/i, 'NA', qr/na/i ],
            0 => [ '', qr/\S.*/s, '', qr/\S.*/s ],
            #2 => [ 'NA', qr/na/i, 'NA', qr/na/i ], # User
            #4 => [ 'project', qr/project/i, 'NA', qr/na/i ], # Wikipedia
            #6 => [ 'image', qr/image/i, 'NA', qr/na/i ], # Image
            #8 => [ 'NA', qr/na/i, 'NA', qr/na/i ], # MediaWiki
            #10 => [ 'template', qr/template|templ|temp/, 'NA', qr/na/i ], # Template
            #12 => [ 'NA', qr/na/i, 'NA', qr/na/i ], # Help
            #14 => [ 'category', qr/category|categ|cat/i, 'NA', qr/na/i ], # Category
            #100 => [ 'portal', qr/portal/i, 'NA', qr/na/i ], # Portal
        },
        #stubauto => 'auto',
        blp => undef,
        activepol => undef,
        canonicalize => undef,
        verify => undef,
        params => undef,
    },
);

# Configuration for each category to process.
my @cfg_cats=();
foreach my $cat (
	'Category:3M brands',
	'Category:Abercrombie & Fitch brands',
	'Category:ACCO Brands brands',
	'Category:Accor brands',
	'Category:Acer mobile phones',
	'Category:Adidas brands',
	'Category:Affinity Gaming',
	'Category:Ajegroup brands',
	'Category:Alcatel mobile phones',
	'Category:Alliance Boots brands',
	'Category:Altadis brands',
	'Category:Alticor brands',
	'Category:Altria Group brands',
	'Category:Alza brands',
	'Category:American beer brands',
	'Category:American brands',
	'Category:American Licorice Company brands',
	'Category:Ameristar casinos',
	'Category:Amoi mobile phones',
	'Category:Amstrad',
	'Category:Amstrad CPC',
	'Category:Amway brands',
	'Category:Anheuser-Busch beer brands',
	'Category:Annabelle Candy Company brands',
	'Category:Apex Tool Group brands',
	'Category:Appellations',
	'Category:Apple Inc. mobile phones',
	'Category:Argentine brands',
	'Category:Art materials brands',
	'Category:Associated British Foods brands',
	'Category:Asus mobile phones',
	'Category:Athletic shoe brands',
	'Category:Audio branding',
	'Category:August Storck brands',
	'Category:Australian alcoholic beverages',
	'Category:Australian beer brands',
	'Category:Australian beverages',
	'Category:Australian brands',
	'Category:Australian vodkas',
	'Category:Austrian brands',
	'Category:Automatic transmission tradenames',
	'Category:Automotive fuel brands',
	'Category:Automotive technology tradenames',
	'Category:Backus and Johnston brands',
	'Category:Bada (operating system)',
	'Category:Bakelite',
	'Category:Bandai brands',
	'Category:Barbadian brands',
	'Category:Bata Shoes',
	'Category:Bayer brands',
	'Category:Beer brands',
	'Category:Beer brands of Germany',
	'Category:Beer brands of Italy',
	'Category:Beer brands of Italy',
	'Category:Beer brands of the United Kingdom',
	'Category:Beiersdorf brands',
	'Category:Belgian brands',
	'Category:Benelli',
	'Category:Benelli semi-automatic pistols',
	'Category:BenQ mobile phones',
	'Category:Beretta firearms',
	'Category:Beretta pistols',
	'Category:Beretta submachine guns',
	'Category:BlackBerry mobile phones',
	'Category:Bottled water brands',
	'Category:Brand management',
	'Category:Brand name alcohol products',
	'Category:Brand name beverage products',
	'Category:Brand name biscuits (British style)',
	'Category:Brand name breads',
	'Category:Brand name chocolate',
	'Category:Brand name condiments',
	'Category:Brand name confectionery',
	'Category:Brand name cookies',
	'Category:Brand name crackers',
	'Category:Brand name dairy products',
	'Category:Brand name desserts',
	'Category:Brand name diet products',
	'Category:Brand name food products',
	'Category:Brand name food products stubs',
	'Category:Brand name frozen desserts',
	'Category:Brand name hot dogs',
	'Category:Brand name materials',
	'Category:Brand name meats',
	'Category:Brand name pies',
	'Category:Brand name potato chips and crisps',
	'Category:Brand name poultry',
	'Category:Brand name products stubs',
	'Category:Brand name snack foods',
	'Category:Brand name soups',
	'Category:Brand name yogurts',
	'Category:Brand valuation',
	'Category:Branding companies',
	'Category:Branding consultants',
	'Category:Branding organizations',
	'Category:Branding terminology',
	'Category:Branding terminology',
	'Category:Brands',
	'Category:Brands by company',
	'Category:Brands by country',
	'Category:Brands by product type',
	'Category:Brands of toothpaste',
	'Category:Brazilian brands',
	'Category:Breakfast cereal companies',
	'Category:Breakfast cereals',
	'Category:Breakfast cereals by company',
	'Category:Breath mints',
	'Category:British American Tobacco brands',
	'Category:British beverage brands',
	'Category:British brands',
	'Category:British pie brands',
	'Category:British Rail brands',
	'Category:Brown-Forman brands',
	'Category:Bus transport brands',
	'Category:Cadbury Adams brands',
	'Category:Cadbury brands',
	'Category:Cadbury UK products',
	'Category:Cadbury-Schweppes brands',
	'Category:Cameras by brand',
	'Category:Campbell Soup Company brands',
	'Category:Canadian beer brands',
	'Category:Canadian brands',
	'Category:Candy bars',
	'Category:Canon cameras',
	'Category:Canon ELPH cameras',
	'Category:Canon EOS 35 mm cameras',
	'Category:Canon EOS accessories',
	'Category:Canon EOS APS cameras',
	'Category:Canon EOS cameras',
	'Category:Canon EOS DSLR cameras',
	'Category:Canon EOS film cameras',
	'Category:Canon FD cameras',
	'Category:Canon FL cameras',
	'Category:Canon flashes',
	'Category:Canon PowerShot cameras',
	'Category:Canon R cameras',
	'Category:Carter-Wallace',
	'Category:Casio brands',
	'Category:Casio cameras',
	'Category:Casio mobile phones',
	'Category:Casio watches',
	'Category:Cat food brands',
	'Category:Cendant brands',
	'Category:Cereal advertising characters',
	'Category:Cereal bars',
	'Category:Cereal box covers',
	'Category:Certification marks',
	'Category:Certification marks in India',
	'Category:Chevron Corporation brands',
	'Category:Chewing gum',
	'Category:Chewing tobacco brands',
	'Category:ChexKellogg Company brands',
	'Category:Chilean brands',
	'Category:Chinese brands',
	'Category:Chinese cigarette brands',
	'Category:Choice Hotels brands',
	'Category:Church & Dwight brands',
	'Category:Cigar brands',
	'Category:Cigarette brands',
	'Category:Cigarette lighter brands',
	'Category:Cigarette rolling papers',
	'Category:Clock brands',
	'Category:Clorox brands',
	'Category:Clothing brands',
	'Category:Clothing brands by country',
	'Category:Clothing brands by type',
	'Category:Clothing brands of Australia',
	'Category:Clothing brands of China',
	'Category:Clothing brands of France',
	'Category:Clothing brands of Germany',
	'Category:Clothing brands of Hong Kong',
	'Category:Clothing brands of India',
	'Category:Clothing brands of Italy',
	'Category:Clothing brands of Japan',
	'Category:Clothing brands of Pakistan',
	'Category:Clothing brands of Portugal',
	'Category:Clothing brands of Romania',
	'Category:Clothing brands of Serbia',
	'Category:Clothing brands of South Korea',
	'Category:Clothing brands of Spain',
	'Category:Clothing brands of the Netherlands',
	'Category:Clothing brands of the Philippines',
	'Category:Clothing brands of the United Kingdom',
	'Category:Clothing brands of the United States',
	'Category:Coca-Cola brands',
	'Category:Coffee brands',
	'Category:Cola brands',
	'Category:Colgate-Palmolive brands',
	'Category:Combe Incorporated brands',
	'Category:ConAgra Foods brands',
	'Category:Condoms',
	'Category:Consumer electronics brands',
	'Category:Consumer symbols',
	'Category:Contax cameras',
	'Category:Cooking appliance brands',
	'Category:Cooper Tools brands',
	'Category:Cooper Tools brands',
	'Category:Cosmetics brands',
	'Category:Crayola',
	'Category:Custom Coaches',
	'Category:Cyber-shot cameras',
	'Category:Czech brands',
	'Category:Dad\'s Root Beer brands',
	'Category:Dairy products companies',
	'Category:Dairy-free frozen dessert brands',
	'Category:Danaher Corporation brands',
	'Category:Danish brands',
	'Category:Darden Restaurants brands',
	'Category:Dean Foods brands',
	'Category:Defunct brands',
	'Category:Defunct motor vehicle brands of Japan',
	'Category:Defunct regional airline brands',
	'Category:Del Monte Foods brands',
	'Category:Dell mobile phones',
	'Category:Diageo beer brands',
	'Category:Diageo brands',
	'Category:Dial Corporation brands',
	'Category:Diaper brands',
	'Category:Die-cast toys',
	'Category:DiMAGE cameras',
	'Category:Dog food brands',
	'Category:Doll brands',
	'Category:Don Jorge brands',
	'Category:Dr Pepper Snapple Group brands',
	'Category:Drug brand names',
	'Category:Dutch brands',
	'Category:Dyson products',
	'Category:Ecolabelling',
	'Category:Electrolux brands',
	'Category:English beer brands',
	'Category:English brands',
	'Category:English ciders',
	'Category:Environmental certification marks',
	'Category:Estonian brands',
	'Category:ExxonMobil brands',
	'Category:Eyewear brands',
	'Category:Falstaff Brewing Corporation brands',
	'Category:Farley\'s & Sathers Candy Company brands',
	'Category:Fashion accessory brands',
	'Category:Fashion company stubs',
	'Category:FAW Group brands',
	'Category:Feminine hygiene brands',
	'Category:Ferrara Pan Candy Company brands',
	'Category:Ferrero brands',
	'Category:Fictional brands',
	'Category:Financial services brands',
	'Category:Finnish brands',
	'Category:Firearm brands',
	'Category:First International Computer mobile phones',
	'Category:Fish food brands',
	'Category:Fonterra brands',
	'Category:Food brands of the United Kingdom',
	'Category:Former Procter & Gamble brands',
	'Category:Fortune Brands brands',
	'Category:Fosters Group',
	'Category:Four-wheel-drive system tradenames',
	'Category:Franchised formats',
	'Category:Franchised radio formats',
	'Category:Franchised television formats',
	'Category:Franchises',
	'Category:French brands',
	'Category:Frito-Lay brands',
	'Category:Frozen food brands',
	'Category:Frozen pizza brands',
	'Category:Fujifilm cameras',
	'Category:Fujifilm DSLR cameras',
	'Category:Fujifilm FinePix cameras',
	'Category:Gallaher Group brands',
	'Category:Gap brands',
	'Category:Garmin mobile phones',
	'Category:General Mills brands',
	'Category:General Mills cereals',
	'Category:Geopolitical production certification marks',
	'Category:George Weston Limited brands',
	'Category:German brands',
	'Category:Glass trademarks and brands',
	'Category:GlaxoSmithKline brands',
	'Category:Goodman Fielder brands',
	'Category:Greek brands',
	'Category:Group Sense PDA mobile phones',
	'Category:Groupe Danone brands',
	'Category:Gucci brands',
	'Category:H. J. Heinz Company brands',
	'Category:Habanos S.A. brands',
	'Category:Haier mobile phones',
	'Category:Handspring mobile phones',
	'Category:Hasbro brands',
	'Category:Hasbro products',
	'Category:Health care brands',
	'Category:Heineken brands',
	'Category:Henkel brands',
	'Category:Herbs and spices logos',
	'Category:High fashion brands',
	'Category:Hitachi mobile phones',
	'Category:Home appliance brands',
	'Category:Hormel brands',
	'Category:Hosiery brands',
	'Category:Hospitality industry brands',
	'Category:Hostess Brands brands',
	'Category:Hotel chains',
	'Category:Household brands',
	'Category:HTC Corporation mobile phones',
	'Category:Hyundai mobile phones',
	'Category:IBM mobile phones',
	'Category:Ice cream brands',
	'Category:Ice hockey brands',
	'Category:Iced tea brands',
	'Category:IDEN mobile phones',
	'Category:Imperial Tobacco brands',
	'Category:InBev brands',
	'Category:Indian beverage brands',
	'Category:Indian brands',
	'Category:Inditex brands',
	'Category:Inditex brands',
	'Category:Indonesian brands',
	'Category:INQ mobile phones',
	'Category:Insecticide brands',
	'Category:Instant noodle brands',
	'Category:Intel Corporation mobile phones',
	'Category:Intercontinental Hotels Group brands',
	'Category:IPhone',
	'Category:Iranian brands',
	'Category:Isle of Capri casinos',
	'Category:Israeli brands',
	'Category:Italian brands',
	'Category:J & J Snack Foods Corporation brands',
	'Category:Janssen Pharmaceutica',
	'Category:Japan Tobacco brands',
	'Category:Japanese brand foods',
	'Category:Japanese brands',
	'Category:Japanese cigarette brands',
	'Category:Jel Sert brands',
	'Category:Jingles',
	'Category:Johnson & Johnson brands',
	'Category:Juice drink brands',
	'Category:Just Born brands',
	'Category:JVC products',
	'Category:JW Marriott Hotels',
	'Category:Kazakhstani brands',
	'Category:Kellogg Company brands',
	'Category:Kellogg Company cereals',
	'Category:Kimberly-Clark brands',
	'Category:Kitchen knife brands',
	'Category:Kitchenware brands',
	'Category:Kodak cameras',
	'Category:Kodak DC cameras',
	'Category:Kodak DSLR cameras',
	'Category:Kodak EasyShare cameras',
	'Category:Konica cameras',
	'Category:Konica Minolta cameras',
	'Category:Kraft Foods brands',
	'Category:Kyocera mobile phones',
	'Category:L\'Oréal brands',
	'Category:Lance Inc. brands',
	'Category:Latvian brands',
	'Category:Leica cameras',
	'Category:LG Electronics mobile phones',
	'Category:Liggett Group brands',
	'Category:Lighting brands',
	'Category:Limited Brands brands',
	'Category:Lingerie brands',
	'Category:Lists of brands',
	'Category:Lists of trademarks',
	'Category:Lorillard brands',
	'Category:Luggage brands',
	'Category:Luxottica',
	'Category:Luxury brands',
	'Category:LVMH brands',
	'Category:Malaysian brands',
	'Category:Mamiya cameras',
	'Category:Margarine brands',
	'Category:Marriott International brands',
	'Category:Mars brands',
	'Category:Mars confectionery brands',
	'Category:Marvel Comics action figure lines',
	'Category:Marvel Comics games',
	'Category:Massimo Zanetti brands',
	'Category:Maytag brands',
	'Category:McDonald\'s foods',
	'Category:McKee Foods brands',
	'Category:Merchandise',
	'Category:MGA Entertainment brands',
	'Category:Michelin brands',
	'Category:Minolta cameras',
	'Category:Minolta lenses',
	'Category:Mizkan brands',
	'Category:Mobile phones by company',
	'Category:Molson Coors brands',
	'Category:MOM Brands brands',
	'Category:Motorola mobile phones',
	'Category:N-Gage (service) compatible devices',
	'Category:Nabisco brands',
	'Category:NEC mobile phones',
	'Category:Necco brands',
	'Category:Nestlé brands',
	'Category:Nestlé cereals',
	'Category:New Zealand brands',
	'Category:Newell Rubbermaid brands',
	'Category:Nigerian brands',
	'Category:Nike brands',
	'Category:Nikon cameras',
	'Category:Nikon Coolpix cameras',
	'Category:Nikon DSLR cameras',
	'Category:Nikon SLR cameras',
	'Category:Nokia internet tablets',
	'Category:Nokia mobile phones',
	'Category:Nokia phones 1000 series',
	'Category:Nokia phones by series',
	'Category:Nokia software products',
	'Category:Northwest Airlink',
	'Category:Norway Bussekspress',
	'Category:Norwegian brands',
	'Category:Novartis brands',
	'Category:Olympus cameras',
	'Category:Olympus digital cameras',
	'Category:Olympus DSLR cameras',
	'Category:Olympus mirrorless interchangeable lens cameras',
	'Category:Omega watches',
	'Category:Orange mobile phones',
	'Category:Orbitz brands',
	'Category:Outdoor clothing brands',
	'Category:Pakistani brands',
	'Category:Palm mobile phones',
	'Category:Palm OS devices',
	'Category:Panasonic cameras',
	'Category:Panasonic Corporation brands',
	'Category:Panasonic lenses',
	'Category:Panasonic Lumix cameras',
	'Category:Panasonic products',
	'Category:Parmalat units',
	'Category:Pascall (company) brands',
	'Category:Peanut butter brands',
	'Category:Pearson\'s Candy Company brands',
	'Category:Pentax cameras',
	'Category:Pentax DSLR cameras',
	'Category:PepsiCo brands',
	'Category:PepsiCo soft drinks',
	'Category:Perfetti Van Melle brands',
	'Category:Pernod Ricard brands',
	'Category:Perry Ellis International brands',
	'Category:Personal care brands',
	'Category:Personal lubricants',
	'Category:Peruvian brands',
	'Category:Pet food brands',
	'Category:Pfizer brands',
	'Category:Philip Morris brands',
	'Category:Philips people',
	'Category:Philips products',
	'Category:Pinnacle Entertainment',
	'Category:Pinnacle Foods brands',
	'Category:Pipe tobacco brands',
	'Category:Plastic brands',
	'Category:PlayStation (console)',
	'Category:PlayStation 2',
	'Category:PlayStation 3',
	'Category:PlayStation Portable',
	'Category:PlayStation Vita',
	'Category:Polaroid cameras',
	'Category:Popcorn brands',
	'Category:Portuguese brands',
	'Category:Post Foods brands',
	'Category:PPR (company) brands',
	'Category:Premier Foods brands',
	'Category:Premixed alcoholic beverages',
	'Category:President\'s Choice',
	'Category:Prestige Brands brands',
	'Category:Procter & Gamble brands',
	'Category:Product management',
	'Category:Products by brand',
	'Category:Products with protected designation of origin',
	'Category:Puma SE',
	'Category:PureView',
	'Category:Quaker Oats Company brands',
	'Category:Quaker Oats Company cereals',
	'Category:Quixtar brands',
	'Category:R. J. Reynolds Tobacco Company brands',
	'Category:Rail transport brands',
	'Category:Ralston cereals',
	'Category:RCA brands',
	'Category:Realogy brands',
	'Category:Reckitt Benckiser brands',
	'Category:Reebok brands',
	'Category:Regional airline brands',
	'Category:Religious consumer symbols',
	'Category:Research In Motion mobile phones',
	'Category:Revlon brands',
	'Category:Richemont brands',
	'Category:Ricoh cameras',
	'Category:Ricoh digital cameras',
	'Category:Robotics at Sony',
	'Category:Rolex watches',
	'Category:Rollei cameras',
	'Category:Romanian brands',
	'Category:Rowntree\'s brands',
	'Category:Russian brands',
	'Category:S. C. Johnson & Son brands',
	'Category:Safran mobile phones',
	'Category:SAIC Motor brands',
	'Category:Samsung cameras',
	'Category:Samsung computers',
	'Category:Samsung DSLR cameras',
	'Category:Samsung Electronics',
	'Category:Samsung Electronics products',
	'Category:Samsung mobile phones',
	'Category:Samsung phone stubs',
	'Category:Sanyo mobile phones',
	'Category:Sanyo products',
	'Category:Sara Lee Corporation brands',
	'Category:Schering-Plough brands',
	'Category:Scottish beer brands',
	'Category:Scottish blended whisky',
	'Category:Scottish brands',
	'Category:Sears Holdings Corporation brands',
	'Category:Sendo mobile phones',
	'Category:Serbian brands',
	'Category:Sewing machine brands',
	'Category:Shampoos',
	'Category:Sharp Corporation mobile phones',
	'Category:Shaving cream brands',
	'Category:Shoe brands',
	'Category:Siemens mobile phones',
	'Category:Siemens products',
	'Category:Siemens software products',
	'Category:Sierra Wireless mobile phones',
	'Category:Sigma cameras',
	'Category:Sigma DSLR cameras',
	'Category:Singaporean brands',
	'Category:Slot car brands',
	'Category:Slovenian brands',
	'Category:Smithfield Foods brands',
	'Category:Snack Brands Australia brands',
	'Category:SNCF brands',
	'Category:Soap brands',
	'Category:Somerset ciders',
	'Category:Sony Alpha DSLR cameras',
	'Category:Sony cameras',
	'Category:Sony CLIÉ',
	'Category:Sony consoles',
	'Category:Sony Ericsson mobile phones',
	'Category:Sony hardware',
	'Category:Sony lenses',
	'Category:Sony products',
	'Category:Sony software',
	'Category:Sound trademarks',
	'Category:South Korean brands',
	'Category:Soviet brands',
	'Category:Soviet cameras',
	'Category:Soviet photographic lenses',
	'Category:Spanish brands',
	'Category:Sporting goods brands',
	'Category:Sportswear brands',
	'Category:Stanley Black & Decker brands',
	'Category:Starwood Hotels & Resorts brands',
	'Category:Station Casinos',
	'Category:Store brands',
	'Category:Surfwear brands',
	'Category:Swedish brands',
	'Category:Swimwear brands',
	'Category:Swiss brands',
	'Category:Swizzels Matlow brands',
	'Category:Taiwan brands',
	'Category:Tea brands',
	'Category:Television news music packages',
	'Category:Television presentation in the United Kingdom The Hershey Company brands',
	'Category:The J.M. Smucker Co. brands',
	'Category:The Smith\'s Snackfood Company brands',
	'Category:The Willy Wonka Candy Company brands',
	'Category:Timex watches',
	'Category:Tobacco brands',
	'Category:Tonka brands',
	'Category:Tool brands',
	'Category:Tootsie Roll Industries brands',
	'Category:Topps confectionery products',
	'Category:Toshiba brands',
	'Category:Toshiba mobile phones',
	'Category:Tourism campaigns',
	'Category:Toy brands',
	'Category:Trademarks',
	'Category:Turkish brands',
	'Category:Types of branding',
	'Category:Ukrainian brands',
	'Category:Underwear brands',
	'Category:Unilever brands',
	'Category:United Biscuits brands',
	'Category:Vaio desktops',
	'Category:Vaio laptops',
	'Category:Valve timing tradenames',
	'Category:Vegan pet food brands',
	'Category:Vicks brands',
	'Category:Victorinox',
	'Category:Vietnamese brands',
	'Category:Walkers (snack foods) brands',
	'Category:Walkman',
	'Category:Walmart brands',
	'Category:Watch brands',
	'Category:Weetabix cereals',
	'Category:Wham-O brands',
	'Category:Whirlpool Corporation brands',
	'Category:Wrigley Company brands',
	'Category:Wyeth brands',
	'Category:Wyndham brands',
	'Category:Yves Saint Laurent (brand)',
	'Category:ZTE mobile phones',
	'Category:Zuiko Digital lenses',
	'Category:Automobile models',
	'Category:Vehicles by brand',
	'Category:Abarth vehicles',
	'Category:AC vehicles',
	'Category:Acura vehicles',
	'Category:Adler vehicles',
	'Category:AEC buses',
	'Category:AEC vehicles',
	'Category:AJS motorcycles',
	'Category:Alfa Romeo vehicles',
	'Category:Allard Motor Company vehicles',
	'Category:Alvis vehicles',
	'Category:AMC motorcycles',
	'Category:AMC vehicles',
	'Category:Amilcar vehicles',
	'Category:Ansaldo Armored vehicles',
	'Category:Ansaldo vehicles',
	'Category:Aprilia motorcycles',
	'Category:Ariel motorcycles',
	'Category:Armstrong Siddeley vehicles',
	'Category:ARO vehicles',
	'Category:Artega vehicles',
	'Category:Ascari vehicles',
	'Category:Aston Martin vehicles',
	'Category:Asüna vehicles',
	'Category:Audi vehicles',
	'Category:Austin vehicles',
	'Category:Austin-Healey vehicles',
	'Category:Autobianchi vehicles',
	'Category:Autozam vehicles',
	'Category:Bajaj motorcycles',
	'Category:Bandini vehicles',
	'Category:Bedford buses',
	'Category:Bedford vehicles',
	'Category:Benelli motorcycles',
	'Category:Bentley vehicles',
	'Category:Benz vehicles',
	'Category:Bertone vehicles',
	'Category:Bimota motorcycles',
	'Category:Bizzarrini vehicles',
	'Category:BMC vehicles',
	'Category:BMW model codes',
	'Category:BMW motorcycles',
	'Category:BMW Sauber Formula One cars',
	'Category:BMW vehicle series',
	'Category:BMW vehicles',
	'Category:Bombardier motorcycles',
	'Category:Bond vehicles',
	'Category:Brennabor',
	'Category:Brennabor vehicles',
	'Category:Bristol vehicles',
	'Category:British Leyland vehicles',
	'Category:Brough Superior motorcycles',
	'Category:BSA motorcars',
	'Category:BSA motorcycles',
	'Category:Buell motorcycles',
	'Category:Bugatti vehicles',
	'Category:Buick vehicles',
	'Category:BYD vehicles',
	'Category:Cadillac vehicles',
	'Category:Cagiva motorcycles',
	'Category:Campagna motorcycles',
	'Category:Cars of Australia',
	'Category:Cars of England',
	'Category:Cars of India',
	'Category:Cars of Italy',
	'Category:Cars of the United States',
	'Category:Caterham vehicles',
	'Category:Caterpillar Inc. vehicles',
	'Category:Chang\'an Vehicles',
	'Category:Checker vehicles',
	'Category:Chery vehicles',
	'Category:Chevrolet Camaro',
	'Category:Chevrolet Corvette',
	'Category:Chevrolet vehiclesv Chrysler vehicles',
	'Category:Citroën 2CV',
	'Category:Citroën vehicles',
	'Category:Commer vehicles',
	'Category:Condor motorcycles',
	'Category:Dacia vehicles',
	'Category:Daewoo vehicles',
	'Category:DAF Trucks vehicles',
	'Category:DAF vehicles',
	'Category:Daihatsu vehicles',
	'Category:Daimler vehicles',
	'Category:Datsun vehicles',
	'Category:De Tomaso vehicles',
	'Category:Defunct motor vehicle brands of Japan',
	'Category:Delahaye vehicles',
	'Category:Dennis buses',
	'Category:Dennis vehicles',
	'Category:Derbi motorcycles',
	'Category:DeSoto vehicles',
	'Category:DKW motorcycles',
	'Category:DKW vehicles',
	'Category:Dnepr motorcycles',
	'Category:Dodge vehicles',
	'Category:Dongfeng vehicles',
	'Category:Douglas motorcycles',
	'Category:Ducati motorcycles',
	'Category:Duesenberg vehicles',
	'Category:Eagle vehicles',
	'Category:Edsel vehicles',
	'Category:Efini vehicles',
	'Category:Elfin vehicles',
	'Category:Escorts motorcycles',
	'Category:Eunos vehicles',
	'Category:Excelsior motorcycles',
	'Category:Facel Vega vehicles',
	'Category:FAW vehicles',
	'Category:Ferrari vehicles',
	'Category:Ferrero brands',
	'Category:Fiat Armored vehicles',
	'Category:Fiat vehicles',
	'Category:Flyscooters',
	'Category:Ford aircraft',
	'Category:Ford Falcon',
	'Category:Ford Focus',
	'Category:Ford of Europe vehicles',
	'Category:Ford Rally Sport vehicles',
	'Category:Ford vehicles',
	'Category:FPV vehicles',
	'Category:FSC vehicles',
	'Category:FSO vehicles',
	'Category:Galloper vehicles',
	'Category:GAZ',
	'Category:GAZ 24 Volga',
	'Category:Geely vehicles',
	'Category:General Motors vehicles',
	'Category:Gilera motorcycles',
	'Category:GM Korea vehicles',
	'Category:GME vehicles',
	'Category:Great Wall Motors vehicles',
	'Category:Grinnall vehicles',
	'Category:GTM vehicles',
	'Category:Gurgel vehicles',
	'Category:Harley-Davidson motorcycles',
	'Category:Hero Honda motorcycles',
	'Category:Hi-Bird motorcycles',
	'Category:HICOM vehicles',
	'Category:Hillman vehicles',
	'Category:Hino vehicles',
	'Category:Holden Caprice',
	'Category:Holden Commodore',
	'Category:Holden vehicles',
	'Category:Holden vehicles by series',
	'Category:Holden VL Commodore',
	'Category:Honda aircraft',
	'Category:Honda ATVs',
	'Category:Honda Civic',
	'Category:Honda mopeds',
	'Category:Honda motorcycles',
	'Category:Honda vehicles',
	'Category:Hotchkiss vehicles',
	'Category:HSV vehicles',
	'Category:Humber vehicles',
	'Category:Hummer vehicles',
	'Category:Hyosung motorcycles',
	'Category:Hyundai vehicles',
	'Category:Hyundai vehicles in India',
	'Category:Ideal Jawa motorcycles',
	'Category:IFA vehicles',
	'Category:Indian motor scooters',
	'Category:Indian motorcycles',
	'Category:Indian Motorcycles',
	'Category:Infiniti vehicles',
	'Category:Innocenti vehicles',
	'Category:International Harvester vehicles',
	'Category:Iranian brands',
	'Category:Irisbus vehicles',
	'Category:Iso vehicles',
	'Category:Isotta Fraschini vehicles',
	'Category:Issigonis vehicles',
	'Category:Isuzu vehicles',
	'Category:Italian motorcycles',
	'Category:Iveco vehicles',
	'Category:Jaguar vehicles',
	'Category:James motorcycles',
	'Category:Jawa motorcycles',
	'Category:Jeep vehicles',
	'Category:Jensen vehicles',
	'Category:John Deere vehicles',
	'Category:Jowett vehicles',
	'Category:Kamaz',
	'Category:Karsan vehicles',
	'Category:Kawasaki motorcycles',
	'Category:Kia vehicles',
	'Category:Koenigsegg vehicles',
	'Category:Komatsu bulldozers',
	'Category:Komatsu vehicles',
	'Category:KTM motorcycles',
	'Category:KTM vehicles',
	'Category:Kymco motorcycles',
	'Category:Lada vehicles',
	'Category:Lagonda vehicles',
	'Category:Lamborghini vehicles',
	'Category:Lanchester Motor Company vehicles',
	'Category:Lancia vehicles',
	'Category:Land Rover vehicles',
	'Category:Laverda motorcycles',
	'Category:LDV vehicles',
	'Category:Lexus LS',
	'Category:Lexus vehicles',
	'Category:Leyland buses',
	'Category:Leyland Trucks vehicles',
	'Category:Leyland vehicles',
	'Category:Lincoln Mark series',
	'Category:Lincoln vehicles',
	'Category:Lion-Peugeot vehicles',
	'Category:Lister vehicles',
	'Category:Lotus vehicles',
	'Category:Luxury vehicles',
	'Category:Mahindra vehicles',
	'Category:Maico motorcycles',
	'Category:MAN vehicles',
	'Category:Maple vehicles',
	'Category:Marcos vehicles',
	'Category:Maruti vehicles',
	'Category:Maserati vehicles',
	'Category:Massey Ferguson vehicles',
	'Category:Massey-Harris vehicles',
	'Category:Matchless motorcycles',
	'Category:Matra vehicles',
	'Category:Maybach vehicles',
	'Category:Mazda vehicles',
	'Category:Mazdaspeed vehicles',
	'Category:McLaren vehicles',
	'Category:MCX motorcycles',
	'Category:Mercedes-Benz buses',
	'Category:Mercedes-Benz racing cars',
	'Category:Mercedes-Benz vehicles',
	'Category:Mercury vehicles',
	'Category:Meteor vehicles',
	'Category:MG racing models',
	'Category:MG vehicles',
	'Category:Military vehicles by brand',
	'Category:Mini (BMW) vehicles',
	'Category:Mini (marque)',
	'Category:Mini vehicles',
	'Category:Mitsubishi Fuso vehicles',
	'Category:Mitsubishi Motors motorcycles',
	'Category:Mitsubishi Motors vehicles',
	'Category:Mitsuoka vehicles',
	'Category:Modenas motorcycles',
	'Category:Mondial (motorcycle)',
	'Category:Mondial motorcycles',
	'Category:Monteverdi vehicles',
	'Category:Morgan vehicles',
	'Category:Morris Commercial vehicles',
	'Category:Morris vehicles',
	'Category:Moskvitch',
	'Category:Mosler vehicles',
	'Category:Moto Guzzi motorcycles',
	'Category:Motorcycles by brand',
	'Category:MTT motorcycles',
	'Category:MV Agusta motorcycles',
	'Category:MZ motorcycles',
	'Category:Naza vehicles',
	'Category:Nissan Altima',
	'Category:Nissan vehicles',
	'Category:Noble vehicles',
	'Category:Norton motorcycles',
	'Category:NSU motorcycles',
	'Category:NSU vehicles',
	'Category:Oldsmobile vehicles',
	'Category:Opel vehicles',
	'Category:Oshkosh vehicles',
	'Category:Packard vehicles',
	'Category:Pagani vehicles',
	'Category:Panhard vehicles',
	'Category:Panoz vehicles',
	'Category:Panther motorcycles',
	'Category:Panther vehicles',
	'Category:Perodua vehicles',
	'Category:Peugeot vehicles',
	'Category:Piaggio motorcycles',
	'Category:Piaggio Vespa',
	'Category:Plymouth vehicles',
	'Category:Pontiac vehicles',
	'Category:Porsche vehicles',
	'Category:Prince vehicles',
	'Category:Proton vehicles',
	'Category:Puch motorcycles',
	'Category:RAF vehicles',
	'Category:Rambler vehicles',
	'Category:Reliant vehicles',
	'Category:Renault Samsung vehicles',
	'Category:Renault vehicles',
	'Category:Riich vehicles',
	'Category:Riley vehicles',
	'Category:Rolls-Royce Motor Cars vehicles',
	'Category:Rolls-Royce vehicles',
	'Category:Rootes vehicles',
	'Category:Rover vehicles',
	'Category:Royal Enfield motorcycles',
	'Category:Ruf vehicles',
	'Category:Saab vehicles',
	'Category:Sachs motorcycles',
	'Category:SAIC Motor vehicles',
	'Category:Saleen vehicles',
	'Category:Sany Group',
	'Category:Saturn vehicles',
	'Category:Scammell vehicles',
	'Category:Scania buses',
	'Category:Scania trucks',
	'Category:Scania vehicles',
	'Category:Scania-Vabis Trucks',
	'Category:Scion vehicles',
	'Category:Scott motorcycles',
	'Category:Scottish Aviation aircraft',
	'Category:Scottish Aviation cars',
	'Category:Scottish Aviation vehicles',
	'Category:SEAT vehicles',
	'Category:Shelby vehicles',
	'Category:Simca vehicles',
	'Category:Singer vehicles',
	'Category:Škoda vehicles',
	'Category:Smart vehicles',
	'Category:Sokół motorcycles',
	'Category:Soviet automobiles',
	'Category:Spyker vehicles',
	'Category:SsangYong vehicles',
	'Category:Standard Motor Company vehicles',
	'Category:Steyr-Puch vehicles',
	'Category:Studebaker vehicles',
	'Category:Stutz vehicles',
	'Category:Subaru vehicles',
	'Category:Sunbeam motorcycles',
	'Category:Sunbeam vehicles',
	'Category:Sunbeam-Talbot vehicles',
	'Category:Suzuki motorcycles',
	'Category:Suzuki vehicles',
	'Category:Talbot vehicles',
	'Category:Tata vehicles',
	'Category:Tatra vehicles',
	'Category:Tesla Motors vehicles',
	'Category:Thornycroft military vehicles',
	'Category:Toyota Camry',
	'Category:Toyota Corolla',
	'Category:Toyota Land Cruiser',
	'Category:Toyota Prius',
	'Category:Toyota vehicles',
	'Category:TRD vehicles',
	'Category:Triumph motorcycles',
	'Category:Triumph vehicles',
	'Category:Tunturi motorcycles',
	'Category:TVR vehicles',
	'Category:TVS motorcycles',
	'Category:Valiant vehicles',
	'Category:Vauxhall vehicles',
	'Category:Velocette motorcycles',
	'Category:Vincent motorcycles',
	'Category:Volvo vehicles',
	'Category:Wolseley vehicles',
	'Category:Yamaha mopeds',
	'Category:Yamaha motorcycles',
	'Category:Zastava Automobiles',
	'Category:Zero Motorcycles',
	'Category:Zongshen motorcycles',
) {
    push @cfg_cats, {
        cat => $cat,
        mark => 0,
        subcats => 0,
        exclude => [],
        templates => [ 'WikiProject Brands' ],
    };
}

my %aftercats=(
    'Category:Talk header templates'=>1,
    'Category:Article talk header templates'=>1,
    'Category:Portal talk header templates'=>1,
    'Category:Script talk header templates'=>1,
    'Category:Template talk header templates'=>1,
    'Category:User talk header templates'=>1,
    'Category:Wikipedia talk header templates'=>1,
    'Category:Wikipedia GA templates'=>1,
    'Category:Wikipedia featured content templates'=>1,
    'Category:Wikipedia featured topics templates'=>1,
    'Category:Wikipedia release version templates'=>1,
    'Category:WikiProject banners'=>3,
    'Category:WikiProject banners with quality assessment'=>3,
    'Category:WikiProject banners without quality assessment'=>3,
);

sub new {
    my $class=shift;
    my $self=$class->SUPER::new();
    $self->{'cats'}=undef;
    $self->{'did_templates'}=0;
    $self->{'WPB'}=undef;
    bless $self, $class;
    return $self;
}

=pod

=for info
Blanket approval to run this code for any WikiProject over any set of categories has been granted, as long as the category list is approved by the WikiProject in question.<br />[[Wikipedia:Bots/Requests for approval/AnomieBOT 14]]

=for info
Retired 2024-01-29. Haven't had call to run this in a while, and the related templates have changed so I'd probably have to update this code to run it again.

=cut

sub approved {
    return -1;
}

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

    $api->task('WikiProjectTagger', 0, 10, qw/d::WikiProjectTagging d::Redirects/);

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

    # Load list of cats to process
    if(!defined($self->{'cats'})){
        my %q=(
            list        => 'categorymembers',
            cmnamespace => 14,
            cmtype      => 'subcat',
            cmlimit     => 'max',
        );
        my %cache=();
        my %cats=();
        for(my $i=0; $i<@cfg_cats; $i++){
            my %skip=();
            $skip{$_}=1 foreach @{$cfg_cats[$i]->{'exclude'}};
            my @cats=( [ $cfg_cats[$i]->{'cat'}, $cfg_cats[$i]->{'subcats'} ] );
            while(my $x=shift(@cats)){
                my ($c,$s)=@$x;
                next if exists($skip{$c});
                $skip{$c}=1;
                $cats{$c}=[] unless exists($cats{$c});
                foreach my $t (@{$cfg_cats[$i]->{'templates'}}){
                    push @{$cats{$c}}, $t unless grep($_ eq $t, @{$cats{$c}});
                }
                $mark_cats{$c}|=$cfg_cats[$i]{'mark'} if exists($cfg_cats[$i]{'mark'});
                next if $s==0;
                my $res;
                if(exists($cache{$c})){
                    $res=$cache{$c};
                } else {
                    $res=$api->query([], %q, cmtitle=>$c);
                    if($res->{'code'} ne 'success'){
                        $api->warn("Failed to retrieve category list for $c: ".$res->{'error'}."\n");
                        return 60;
                    }
                    $cache{$c}=$res;
                }
                push @cats, [ $_->{'title'}, $s-1 ] foreach (@{$res->{'query'}{'categorymembers'}});
            }
        }
        $self->{'cats'}=\%cats;
    }
    my %cats=%{$self->{'cats'}};

    # Load the list of target templates, including redirects
    if(!$self->{'did_templates'}){
        while(my ($t,$c)=each %cfg_templates){
            my %redirs=$api->redirects_to_resolved("Template:$t");
            if(exists($redirs{''})){
                $api->warn("Failed to get redirects to Template:$t: ".$redirs{''}{'error'}."\n");
                return 60;
            }
            $c->{'names'}=[ keys %redirs ];
        }
        $self->{'did_templates'}=1;
    }

    # Also, get redirects to WikiProjectBanners and WikiProjectBannerShell, for
    # adding the project banner if necessary
    if(!defined($self->{'WPB'})){
        my %WPB=();
        my %B=('Template:WikiProjectBanners'=>1,'Template:WikiProjectBannerShell'=>2);
        while(my ($t,$c)=each %B){
            $WPB{$t}=$c;

            my %redirs=$api->redirects_to_resolved($t);
            if(exists($redirs{''})){
                $api->warn("Failed to get redirects to $t: ".$redirs{''}{'error'}."\n");
                return 60;
            }
            $WPB{$_}=$c foreach (keys %redirs);
        }
        $self->{'WPB'}=\%WPB;
    }
    my %WPB=%{$self->{'WPB'}};

    # Ok, start scanning categories now
    my %q=(
        generator    => 'categorymembers',
        gcmlimit     => 'max',
        prop         => "info|categories$pageprops",
        inprop       => 'subjectid',
        cllimit      => 'max',
        tllimit      => 'max',
    );
    my %q2=(
        pageids  => '',
        prop     => "info|categories$pageprops",
        cllimit  => 'max',
        tllimit  => 'max',
    );
    if($need_page_contents){
        $q{'gcmlimit'}=500; # max 500 when getting revision contents
        $q{'prop'}.='|revisions';
        $q{'rvprop'}='content';
        $q{'rvslots'}='main';
    }
    my @cats=keys %cats;
    foreach my $cat (@cats){
        next if(exists($api->store->{$cat}) && $api->store->{$cat}>=$seq);

        my $catdone=1;
        my @pageids=();
        my @templates=@{$cats{$cat}};
        # Get the list of pages to check
        $q{'gcmtitle'}=$cat;
        do {
            if(@pageids){
                $q2{'pageids'}=join('|', splice(@pageids, 0, 500));
                $res=$api->query(%q2);
            } else {
                $res=$api->query(%q);
                if($res->{'code'} ne 'success'){
                    $api->warn("Failed to retrieve category list for $cat: ".$res->{'error'}."\n");
                    return 60;
                }
                if(exists($res->{'query-continue'})){
                    $q{'gcmcontinue'}=$res->{'query-continue'}{'categorymembers'}{'gcmcontinue'};
                } else {
                    delete $q{'gcmcontinue'};
                }
            }

            foreach my $page (values %{$res->{'query'}{'pages'}}){
                my $title=$page->{'title'};
                my $pageid=$page->{'pageid'};
                next if(exists($api->store->{$pageid}) && $api->store->{$pageid}>=$seq);

                my $ns=$page->{'ns'};
                my $auto=0;
                my $talk;
                if(($ns&1)==1){
                    # Talk page, add subjectid to pageid list if we haven't
                    # seen it yet.
                    next unless exists($page->{'subjectid'});
                    $pageid=$page->{'subjectid'};
                    next if(exists($api->store->{$pageid}) && $api->store->{$pageid}>=$seq);
                    push @pageids, $pageid;
                    next;
                }
                if($ns==0){
                    if(exists($page->{'redirect'})){
                        $ns='redirect';
                    } elsif(grep { $_->{'title'} eq 'Category:All disambiguation pages' } @{$page->{'categories'}}){
                        $ns='disambig';
                    } elsif(grep { $_->{'title'}=~/^Category:.* stubs?$/i } @{$page->{'categories'}}){
                        $ns='stub';
                        $auto=1;
                    }
                    $talk="Talk:$title";
                } else {
                    ($talk=$title)=~s/([^:]*):/$1 talk:/;
                }

                $api->log("Tagging $title for $cat");
                my $tok=$api->edittoken($talk, EditRedir => 1, %talkprops);
                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 $talk: ".$tok->{'error'}."\n");
                    $catdone=0;
                    next;
                }
                if(exists($tok->{'redirect'})){
                    $api->warn("$talk is a redirect, skipping.\n");
                    $api->store->{$pageid}=$seq;
                    next;
                }
                my $intxt=$tok->{'revisions'}[0]{'slots'}{'main'}{'*'} // '';
                my ($outtxt,$nowiki)=$api->strip_nowiki($intxt);
                my $any=0;
                my @found=();

                # Now, go through each template
                foreach my $template (@templates){
                    my %cfg=%{$cfg_templates{$template}};
                    next if(defined($cfg{'verify'}) && !$cfg{'verify'}($api,$page,$tok));
                    next unless exists($cfg{'ns'}{$ns});
                    my ($class,$classre,$imp,$impre)=@{$cfg{'ns'}{$ns}};
                    my $impname=exists($cfg{'importance'})?$cfg{'importance'}:'importance';

                    # If the target tempate is already on the page, it's easy
                    my $done=0;
                    {
                        $outtxt=$api->process_templates($outtxt, sub {
                            my $name=shift;
                            my @params=@{shift()};
                            shift; # $wikitext
                            shift; # $data
                            my $oname=shift;

                            return undef unless grep($_ eq "Template:$name", @{$cfg{'names'}});

                            my $orig_param=join('|', @params);

                            # Auto-assess
                            if(defined($classre) && !grep(/^\s*class\s*=\s*$classre\s*$/s, @params)){
                                push @params, "class=$class" unless(grep(s/^(\s*class\s*=(?:\s*(?=\S))?).*?(\s*)$/$1$class$2/s, @params));
                                if(defined($cfg{'stubauto'})){
                                    my $a=$cfg{'stubauto'};
                                    if($auto){
                                        push @params, "$a=yes" if(!grep(s/^(\s*\Q$a\E\s*=(?:\s*(?=\S))?).*?(\s*)$/$1yes$2/s, @params));
                                    } else {
                                        @params = grep(!/^\s*\Q$a\E\s*=/, @params);
                                    }
                                }
                            }

                            if(defined($impre) && !grep(/^\s*$impname\s*=\s*$impre\s*$/s, @params)){
                                push @params, "$impname=$imp" unless(grep(s/^(\s*$impname\s*=(?:\s*(?=\S))?).*?(\s*)$/$1$imp$2/s, @params));
                            }

                            # Do extra parameters, if necessary
                            $cfg{'params'}($api,$page,$tok,$name,\@params) if defined($cfg{'params'});

                            $done=1;
                            my $new_param=join('|', @params);
                            $any=1 if $orig_param ne $new_param;

                            # We don't want to be making edits that just add
                            # empty class/importance.
                            if($any && !$addemptyparams){
                                $orig_param=~s/(?:^|\|)\s*class\s*=\s*(?=\||$)//g;
                                $new_param=~s/(?:^|\|)\s*class\s*=\s*(?=\||$)//g;
                                $orig_param=~s/(?:^|\|)\s*importance\s*=\s*(?=\||$)//g;
                                $new_param=~s/(?:^|\|)\s*importance\s*=\s*(?=\||$)//g;
                                $any=0 if $orig_param eq $new_param;
                            }

                            push @found, "{{".($cfg{'canonicalize'} // $name)."}}" if $any;

                            if(defined($cfg{'canonicalize'})){
                                my $n=$cfg{'canonicalize'};
                                $oname=~s/_/ /g;
                                $oname=~s/^(\s*)\S(?:.*\S)?(\s*)$/$1$n$2/is;
                            }
                            return "{{$oname|".join("|",@params)."}}";
                        });
                        last if $done;

                        # No existing banner, that makes it more difficult. May
                        # as well construct the template now.
                        my $oname=$cfg{'canonicalize'} // $template;
                        my @params=();
                        push @params, $autotagged if defined($autotagged);
                        push @params, "class=".($class // '') if defined($classre);
                        push @params, "$impname=".($imp // '') if(defined($impname) && defined($impre));
                        push @params, $cfg{'stubauto'}."=yes" if($auto && defined($cfg{'stubauto'}));
                        $cfg{'params'}($api,$page,$tok,$oname,\@params) if defined($cfg{'params'});
                        my $tmpl="{{$oname|".join("|", @params)."}}";
                        $any=1;
                        push @found, "{{$oname}}";

                        # First, look for a banner shell to put it in.
                        $outtxt=$api->process_templates($outtxt, sub {
                            my $name=shift;
                            my @params=@{shift()};
                            shift; # $wikitext
                            shift; # $data
                            my $oname=shift;

                            return undef unless exists($WPB{"Template:$name"});
                            my @p_name=();
                            my @p_num=();
                            foreach ($api->process_paramlist(@params)){
                                if($_->{'name'}=~/^\d+$/){
                                    push @p_num, $_->{'value'} unless $_->{'value'}=~/^\s*$/;
                                } else {
                                    push @p_name, $_->{'text'};
                                }
                            }
                            if(defined($cfg{'blp'})){
                                my $re=$cfg{'blp'};
                                push @p_name, 'blp=yes' if($tmpl=~/\|\s*$re\s*/ && !grep(s/^(\s*blp\s*=(?:\s*(?=\S))?).*?(\s*)$/${1}yes$2/s, @p_name));
                            }
                            if(defined($cfg{'activepol'})){
                                my $re=$cfg{'activepol'};
                                push @p_name, 'activepol=yes' if($tmpl=~/\|\s*$re\s*/ && !grep(s/^(\s*activepol\s*=(?:\s*(?=\S))?).*?(\s*)$/${1}yes$2/s, @p_name));
                            }
                            if(@p_num==1){
                                $p_num[0]=~s/\s*$//;
                                $p_num[0].="\n$tmpl\n";
                            } elsif(@p_num<10){
                                push @p_num, "$tmpl\n";
                            } else {
                                $p_num[-1]=~s/\s*$//;
                                $p_num[-1].="\n$tmpl\n";
                            }
                            for(my $i=0; $i<@p_num; $i++){
                                push @p_name, ($i+1).'='.$p_num[$i];
                            }
                            $done=2;
                            return "{{$oname|".join("|", @p_name)."}}";
                        });
                        last if $done;

                        # No banner shell. Check the templates at the start of
                        # the page to see if there are any we should go after.
                        # First, we go backwards through section 0 looking for
                        # banners, and put it after the first one we find.
                        my $outtmpl={};
                        my $after=$api->process_templates($outtxt, \&_strip_templates, $outtmpl);
                        $after=~s/^(.*?)(?=\n==|$)//s;
                        $outtxt=$1;
                        while(1){
                            $after=$1.$after if $outtxt=~s/([^\x03]+)$//s;
                            last unless $outtxt=~s/(\x02[0-9A-Za-z_-]+\x03)$//;
                            my $tag=$1;
                            if(!exists($outtmpl->{$tag})){
                                # Not a template
                                $after=$tag.$after;
                                next;
                            }
                            my $chk=$self->_chk_template($api, $outtmpl->{$tag}{'name'});
                            return 60 if !defined($chk); # Fail

                            if(!($chk&2)){
                                # Not a banner
                                $after=$tag.$after;
                                next;
                            }

                            # Is a banner! Put the new one just after it
                            $outtxt.=$tag."\n".$tmpl;
                            $outtxt.="\n" unless $after=~/^\s*\n/;
                            $outtxt.=$after;
                            $outtxt=_unstrip_templates($outtxt, $outtmpl);
                            $done=3;
                            last;
                        }
                        last if $done;

                        # No banner shell, and no other banners either. Just
                        # pull stuff off the front until we hit content or a
                        # template we shouldn't go after.
                        while(1){
                            $outtxt.=$1 if $after=~s/^((?:\s*<!--.*?-->)*)//s;
                            last unless $after=~s/^(\s*)(\x02[0-9A-Za-z_-]+\x03)//;
                            my ($sp,$tag)=($1,$2);
                            if(!exists($outtmpl->{$tag})){
                                # Not a template, so put it back and stop
                                # looking.
                                $after=$sp.$tag.$after;
                                last;
                            }
                            my $chk=$self->_chk_template($api, $outtmpl->{$tag}{'name'});
                            return 60 if !defined($chk); # Fail
                            if($chk){
                                # It's a template we should be after
                                $outtxt.=$sp.$tag;
                                next;
                            }

                            # It's some other template. End!
                            $after=$sp.$tag.$after;
                            last;
                        }
                        $outtxt.="\n" if $outtxt ne '';
                        $outtxt.=$tmpl;
                        $outtxt.="\n" unless $after=~/^\s*\n/;
                        $outtxt.=$after;
                        $outtxt=_unstrip_templates($outtxt, $outtmpl);
                        $done=4;
                        last;
                    }
                }
                $outtxt=$api->replace_nowiki($outtxt, $nowiki);

                # Need to edit?
                if($any && $outtxt ne $intxt){
                    $found[-1]='and '.$found[-1] if @found>1;
                    my $summary="Tagging with ".join((@found>2)?', ':' ', @found)." based on membership in [[:$cat]] per $req $screwup";

                    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;

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

                # 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;
            }
        } while(@pageids || exists($q{'geicontinue'}));
        $api->store->{$cat}=$seq if $catdone;
    }
    foreach my $cat (@cats){
        delete $api->store->{$cat};
    }

    # No more pages to check, try again in 10 minutes or so in case of errors.
    $api->log("WikiProjectTagger may be DONE!");
    return 600;
}

# process_templates callback to strip templates and store them in the fourth
# parameter hash
sub _strip_templates {
    my ($name, $params, $wikitext, $data) = @_;
    return undef if $name=~/^#tag:\s*ref$/is;

    $wikitext=_unstrip_templates($wikitext,$data);
    my $tmp = $wikitext;
    utf8::encode( $tmp ) if utf8::is_utf8( $tmp );
    my $tag="\x02".sha256_base64($tmp)."\x03";
    $tag=~tr!+/=!-_!d;
    $data->{$tag}={ name=>$name, text=>$wikitext };
    return $tag;
}

# Undo what _strip_templates did
sub _unstrip_templates {
    my $wikitext=shift;
    my $templ=shift;

    $wikitext=~s!(\x02[a-zA-Z0-9_-]+\x03)! exists($templ->{$1})?$templ->{$1}{'text'}:$1 !gioe;
    return $wikitext;
}

sub _chk_template {
    my $self=shift;
    my $api=shift;
    my $name=shift;

    return $api->store->{$name} if exists($api->store->{$name});

    my $res=$api->query(
        titles    => "Template:$name",
        prop      => 'categories',
        cllimit   => 'max',
        redirects => 1,
    );
    if($res->{'code'} ne 'success'){
        $api->warn("Failed to retrieve category list for $name: ".$res->{'error'}."\n");
        return undef;
    }
    my $pg=(values %{$res->{'query'}{'pages'}})[0];
    my @c=exists($pg->{'categories'})?@{$pg->{'categories'}}:();
    my $chk=0;
    while(my ($k,$v)=each %aftercats){
        $chk|=$v if grep($k eq $_->{'title'}, @c);
    }
    $api->store->{$name}=$chk;
    $pg->{'title'}=~s/^Template://;
    $api->store->{$pg->{'title'}}=$chk;

    return $chk;
}

1;