Jump to content

User:FACBot/fac.pl

From Wikipedia, the free encyclopedia
#!/usr/bin/perl -w
#
# fac.pl -- Pass or fail an Featured Article class review
#     This Bot runs every day, looking for featured class articles that have been promoted by a delegate
#    If it finds one, it follows the steps involved in promoting or failing it.
# Usage: fac.pl
#    21 Sep 14 Created
#     2 Sep 17 Correction for new announcements page format
#     9 Sep 17 Fix for GA templates missing oldids
#    15 Nov 17 Guard against not being able to find nomination page
#    23 Feb 18 allow bots moved to cred; some code for old articles with GA reviews on the talk page
#    26 Apr 18 Fix for <noinclude>..</noinclude> tags on archive page

use English;
use strict;
use utf8;
use warnings;

use Carp;
use Data::Dumper;
use Date::Calc qw(Delta_Days);
use Date::Parse;
use DateTime;
use File::Basename qw(dirname);
use File::Spec;
use MediaWiki::Bot;
use POSIX qw(strftime);
use XML::Simple;

binmode (STDERR, ':utf8');
binmode (STDOUT, ':utf8');

# Pages used
my $candidates_page = 'Wikipedia:featured article candidates';
my $encoded_nomination;
my $announcements = 'Template:Announcements/New featured content';
my $goings_on = 'Wikipedia:Goings-on';
my $showcase_a = 'Wikipedia:WikiProject Military history/Showcase/A';
my $showcase_fa = 'Wikipedia:WikiProject Military history/Showcase/FA';

my @months = qw(January February March April May June July August September October November December);

my $editor = MediaWiki::Bot->new ({
        assert        => 'bot',
        host        => 'en.wikipedia.org',
        protocol     => 'https',
        operator     => 'Hawkeye7',
}) or die "new MediaWiki::Bot failed";

my $dirname = dirname (__FILE__, '.pl');
push @INC, $dirname;
require Cred;
my $cred = new Cred ();
my $log = $cred->log ();

require showcase;

sub error_exit ($) {
    my @message = @ARG;
    if ($editor->{error}->{code}) {
        push @message, ' (', $editor->{error}->{code} , ') : ' , $editor->{error}->{details};
    }
    $cred->error (@message);
}

sub has_been_closed ($) {
    my ($nomination) = @ARG;
    $cred->showtime ("checking if $nomination has been closed...\n");
    my $text = $editor->get_text ($nomination) or do {
        $cred->showtime ("Unable to find nomination page '$nomination')\n");
        return ();
    };   
    if ($text =~ /{{FACClosed.+ (\d+:\d+, (\d+) (\w+) (\d+))/) {
        $cred->showtime ("$nomination has been closed\n");
        return ($1, $2, $3, $4);
    }
    return ();
}   

sub has_been_promoted ($$$) {
    my ($nomination, $month, $year) = @ARG;
    $cred->showtime ("\tchecking if $nomination has been promoted...\n");
    my $log_page = "Wikipedia:Featured article candidates/Featured log/$month $year";
    my $log_text = $editor->get_text ($log_page) or
        error_exit ("Unable to find '$log_page')");
    $log_text =~ s/_/ /g;
    if ($log_text =~ /\Q$nomination\E/) {
        $cred->showtime ("\tfound $nomination\n");
        return 1;   
    }
    $cred->showtime ("\t$nomination NOT promoted\n");
    return 0;
}

sub has_been_archived ($$$) {
    my ($nomination, $month, $year) = @ARG;
    $cred->showtime ("checking if $nomination has been archived...\n");
    my $log_page = "Wikipedia:Featured article candidates/Archived nominations/$month $year";
    my $log_text = $editor->get_text ($log_page) or
        error_exit ("Unable to find '$log_page')");
    $log_text =~ s/_/ /g;
    if ($log_text =~ /\Q$nomination\E/) {
        $cred->showtime ("\tfound $nomination\n");
        return 1;   
    }
    $cred->showtime ("\t$nomination NOT archived\n");
    return 0;
}

sub whodunnit ($$$) {
    my ($article, $nomination, $action) = @ARG;
    my $old;
    my @history = $editor->get_history ($nomination) or
        error_exit ("Unable to get history of '$nomination'");
    foreach my $revision (@history) {
#        print Dumper $revision, "\n";
        my $text = $editor->get_text ($nomination, $revision->{revid}) or
            error_exit ("Unable to find '$nomination:$revision->{revid}')");
        if ($text !~ /{{FACClosed/) {
            $cred->showtime ("\t$article was $action by $old->{user} at $old->{timestamp_date} $old->{timestamp_time}\n");
            my $diff = "https://en.wikipedia.org/w/index.php?title=$nomination\&diff=$old->{revid}\&oldid=$revision->{revid}";
            $diff =~ s/ /_/g;
#            print $diff, "\n";
            return ($old->{user}, $old->{timestamp_date}, $old->{timestamp_time}, $diff);
        } else {
            $old = $revision;
        }
    }
}

sub remove_from_candidates ($$) {
    my ($page, $nomination) = @ARG;
    $cred->showtime ("\tRemoving from the candidates page\n");
    my $candidates_text = $editor->get_text ($candidates_page) or
        error_exit ("Unable to find '$candidates_page')");
    $candidates_text =~ s/{{$nomination}}//;

    $editor->edit ({
        page => $candidates_page,
        text => $candidates_text,
        summary => "Removing $page from FAC candidates page",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$candidates_page'");
}

sub promote_update_nomination_page ($$$$$) {
    my ($page, $nomination, $user, $date, $diff) = @ARG;
    $cred->showtime ("\tUpdating the nomination page\n");
    my $text = $editor->get_text ($nomination) or
        error_exit ("Unable to find '$nomination')");

    # Remove transcluded article links and featured article tools
    $text =~ s/<noinclude>.+?<\/noinclude>//s;

    # Tag the top and bottom of the page
    my $result = "'''promoted''' by [[User:$user|$user]] via ~~~ $date [$diff]";
    my $top = "{{subst:Fa top|result=$result}}";
    my $bottom = "{{subst:Fa bottom}}\n";
    $text = join "\n", $top, $text, $bottom;

    $editor->edit ({
        page => $nomination,
        text => $text,
        summary => "Promoting '$page'",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$nomination'");
}

sub archive_update_nomination_page ($$$$$) {
    my ($page, $nomination, $user, $date, $diff) = @ARG;
    $cred->showtime ("\tUpdating the nomination page\n");
    my $text = $editor->get_text ($nomination) or
        error_exit ("Unable to find '$nomination')");

    # Remove transcluded article links and featured article tools
    $text =~ s/<noinclude>.+?<\/noinclude>//s;

    # Tag the top and bottom of the page
    my $result = "'''archived''' by [[User:$user|$user]] via ~~~ $date [$diff]";
    my $top = "{{subst:Fa top|result=$result}}";
    my $bottom = "{{subst:Fa bottom}}\n";
    $text = join "\n", $top, $text, $bottom;

    $editor->edit ({
        page => $nomination,
        text => $text,
        summary => "Archiving '$page'",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$nomination'");
}

sub good_article_lists  () {
    $cred->showtime ("\tFinding the good article lists\n");
    my $good_articles = 'Wikipedia:Good articles';
    my $good_articles_all = "$good_articles/all";
    my $text = $editor->get_text ($good_articles_all) or
        error_exit ("Unable to find '$good_articles_all'");
    $cred->error ("no bots allowed on '$good_articles_all'") unless $cred->allow_bots ($text);

    my @good_article_lists = $text =~ /{{($good_articles\/[A-Z].+)}}/g;

#    foreach (@good_article_lists) {
#        print $ARG, "\n";
#    }   
    return @good_article_lists;
}

sub update_good_article_list ($) {
    my ($article) = @ARG;

    $cred->showtime ("\tFinding $article in the good article list\n");
    my @good_article_lists = good_article_lists ();
   
    my $foundit = 0;
    my $totals_updated = 0;
   
    foreach my $list (@good_article_lists) {

        my $text = $editor->get_text ($list) or
            error_exit ("Unable to find '$list'");
        $cred->error ("no bots allowed on '$list'") unless $cred->allow_bots ($text);
       
        my @input = split /\n/, $text;
        my @output;
       
         foreach (@input) {
             if (! $foundit) {
                 if (/\[\[([^\]\|]+)/) {
                     my $good_article_name = $1;
#                     print $good_article_name, "\n";
                    
                     if ($good_article_name eq $article) {
                         $foundit = 1;
                         next;
                     }
                }
            }
           
            if ($foundit && ! $totals_updated) {
                if (/\((\d+) articles\)/) {
#                    print $ARG, "\n";
                    my $updated = $1 - 1;
                    s/$1/$updated/;
                    $totals_updated = 1;                                               
#                    print $ARG, "\n";
                }
            }
                       
            push @output, $ARG;
        }
       
        if ($foundit) {
            $text = join "\n", @output;
            last;
   
            $editor->edit ({
                page => $list,
                text => $text,
                summary => 'update good article list',
                minor => 0,
            }) or
                error_exit ("unable to edit '$list'");
               
            last;
           
        }
        last; 
    }
}

sub promote_update_article_page ($) {
    my ($page) = @ARG;
    $cred->showtime ("\tUpdating the article page\n");
    my $text = $editor->get_text ($page) or
        error_exit ("Unable to find '$page')");
   
    if ($text =~ s/{{good article}}//i) {
        update_good_article_list ($page);       
    }
   
    $text =~ s/^/{{featured article}}\n/;

    $editor->edit ({
        page => $page,
        text => $text,
        summary => "Promoting '$page' to Featured Article status",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$page'");
}

sub remove_from_ga ($) {
    my ($page) = @ARG;

    $cred->showtime ("\tRemoving from GA...\n");
    my $all = 'Wikipedia:Good articles/all';
    my $text = $editor->get_text ($all) or
        error_exit ("Unable to find '$all')");
       
    my @categories = $text =~/{{(Wikipedia:Good articles\/[\w\s]+?)}}/g;
    foreach my $category (@categories) {
#        print $category, "\n";
   
        $text = $editor->get_text ($category) or
            error_exit ("Unable to find '$category')");
        $cred->error ("no bots allowed on '$category'") unless $cred->allow_bots ($text);
           
        if ($text =~ /\Q$page\E/) {
            $cred->showtime ("\t\tfound $page in $category\n");
            my @a;
            my $decrement_count = 0;
            my @lines = split /\n/, $text;
            foreach (@lines) {
                if (/\Q$page\E/) {
                    $decrement_count = 1;
                    next;
                }
                if ($decrement_count && /(\d+) articles/) {
                    $decrement_count = 0;
                    my $count = $1 - 1;
                    s/\d+/$count/;   
                }
                push @a, $ARG;
            }
            $text = join "\n", @a, "\n";
                           
            $editor->edit ({
                page => $category,
                text => $text,
                summary => "$page has been promoted to Featured Article status",
                bot => 1,
                minor => 0,
            }) or
                error_exit ("unable to edit '$category'");
   
            return;
        }
    }
}

sub parse_template ($@) {
    my ($text, @args) = @ARG;
    my %p;
    while ($text =~ s/\|(\w+)\s*=\s*([^}|]+)//is) {
        $p{$1}=$2;   
    }
   
    my @p = split '\|', $text;
    param:foreach my $p (@p) {
        next param unless $p;
        foreach my $arg (@args) {
            if ($p =~ /^(\w+)\s*=/) {
                next;
            }
            if (!defined $p{$arg}) {
                $p{$arg} = $p;
                next param;
            }
        }
    }
#    foreach my $p (keys %p) {
#        print "$p => $p{$p}\n";
#    }
   
    return %p;
}

sub newaction ($$$$$$) {
    my ($action, $date, $link, $result, $revid, $id) = @ARG;
    my $newaction = join "\n",
        "|action${id}=$action",
        "|action${id}date=$date",
        "|action${id}link=$link",
        "|action${id}result=$result",
        "|action${id}oldid=$revid";
    return $newaction;
}

sub has_nested_text ($\$) {
    my ($tag, $text) = @ARG;
#    print "tag='$tag'\n";
    my $has_nested_text = 0;
    while ($$text =~ /{{$tag[^}]+({{[^}]+}})/) {
#        print "Nested text!!!!\n";
        my $nested_text = $1;
#        print "nested text='$nested_text'\n";   
        my $transformed_text = $nested_text;
        $transformed_text =~ s/{{(.+)}}/%%<$1>%%/;
#        print "transformed text=$transformed_text\n";   
        $$text =~ s/\Q$nested_text\E/$transformed_text/;
        $has_nested_text = 1;                                    
    }
    return $has_nested_text;
}

sub reset_nested_text (\$) {
    my ($text) = @ARG;
    $$text =~ s/%%</{{/g;
    $$text =~ s/>%%/}}/g;
}

sub update_article_history ($$$$$$) {
    my ($text, $action, $date, $link, $result, $revid) = @ARG;
    $text =~ s/{{Article\s*History/{{ArticleHistory/is;
    my ($articleHistory) = $text =~ /{{ArticleHistory(.+)}}/gis;
    if ($articleHistory) {
#        print "update article history\n";
        my $has_nested_text = has_nested_text ('ArticleHistory', $text);
#        print "articlehistory='$articleHistory'\n";
        for (my $id = 1;; ++$id) {
            if ($articleHistory =~ /action$id/) {
#                print "\t\tfound action$id\n";
            } else {
#                print "\t\tno $id - going with that\n";
                my $newaction = newaction ($action, $date, $link, $result,  $revid, $id);
                $text =~ s/{{ArticleHistory(.+?)}}/{{ArticleHistory$1\n$newaction\n}}/is;
                last;
            }
        }
       
        if ($has_nested_text) {
            reset_nested_text ($text);       
        }

    } else {
        my $newaction = newaction ($action, $date, $link, $result, $revid, 1);
        $text =~ s/^/{{ArticleHistory\n$newaction\n}}\n/is;
    }
    return $text;
}

sub get_revid ($$$) {
    my ($page, $date, $time) = @ARG;
    my @history = $editor->get_history ($page) or
        error_exit ("Unable to get history of '$page'");
    foreach my $history (@history) {
        if ("$history->{timestamp_date} $history->{timestamp_time}" le "$date $time") {   
            return $history->{revid};
        }
    }
    error_exit ("Unable to get revid of '$page')");
}

sub parse_display_date ($) {
    my ($display_date) = @ARG;
    my ($ss, $mm, $hh, $day, $month, $year, $zone) = strptime ($display_date) or
        error_exit ("Unable to parse date '$display_date'");
    my $dt = DateTime->new (
          year       => 1900 + $year,
          month      => 1 + $month,
          day        => $day,
          hour       => $hh // 0,
          minute     => $mm // 0,
          second     => $ss // 0,
    );
     
    my $date = $dt->strftime ("%Y-%m-%d");
    my $time = $dt->strftime ("%H:%M");
    return ($time, $date);
}

sub add_ga_to_history ($$$) {
    my ($article, $text, $talk) = @ARG;
    if ($text =~ s/{{GA[^m](.+?)}}//is) {
        my %h = parse_template ($1, 'date', 'oldid', 'page', 'topic');
        my $revid = $h{oldid};
        my $display_date = $h{date};
        my $gaid = $h{page} // 1;
        my $link = "$talk/GA$gaid";
        if (!defined $revid || $revid !~ /\d+/) {
            my ($time, $date) = parse_display_date ($display_date);
            $revid = get_revid ($article, $date, $time);
        }
        $text = update_article_history ($text, 'GAN', $display_date, $link, 'listed', $revid); 
    }
    return $text;
}

sub add_pr_to_history ($$) {
    my ($page, $text) = @ARG;
    $cred->showtime ("\tFound old Peer Review\n");   
    while ($text =~ s/{{oldpeerreview(.+?)}}//is) {
        my %h = parse_template ($1, 'name', 'archive');

        my $name = $h{name} // $page;
        my $archive = defined $h{archive} ? "/archive$h{archive}" : '';
       
        my $link = "Wikipedia:Peer_review/$name$archive";
        my ($history) = $editor->get_history ($link) or
            error_exit ("Unable to get history of '$link'");
        my $date = $history->{timestamp_date};
        my $time = $history->{timestamp_time};
        my $revid = get_revid ($page, $date, $time);       
        $text = update_article_history ($text, 'PR', $date, $link, 'reviewed ', $revid); 
    }
    return $text;
}

sub promote_update_talk_page ($$$$$) {
    my ($page, $talk, $nomination_page, $date, $time) = @ARG;
   
    $cred->showtime ("\tUpdating the talk page\n");
    my $text = $editor->get_text ($talk) or
        error_exit ("Unable to find '$talk')");

    # Remove the candidacy
    $text =~ s/{{featured article candidates\|.+?}}//;

    # Remove from GA
    if ($text =~ /{{GA[^L].+?}}/gis) {  # NOT GAList - from some really old talk pages that include the GA assessment
        if ($text !~ /{{GA Nominee/i) { # Happens when a GA Nominee is nominated for FAC
            $text = add_ga_to_history ($page, $text, $talk);   
            remove_from_ga ($page);
        }
    } elsif ($text =~ /currentstatus=GA/gi || $text =~ /class=GA/gi) {
            remove_from_ga ($page);
    }

    # add an old Peer review, if any, to the article history
    if ($text =~ /{{oldpeerreview.+?}}/gis) {
        $text = add_pr_to_history ($page, $text);   
    }

    # Update the article history
    my $revid = get_revid ($page, $date, $time);
    $text = update_article_history ($text, 'FAC', $date, $nomination_page, 'promoted', $revid); 
    unless ($text =~ s/currentstatus\s*=\s*\w+/currentstatus=FA/is) {
        $text =~ s/{{Article\s*History/{{ArticleHistory\n|currentstatus=FA\n/is;
    }

    # Add DYK, if any, to the article history   
    my $tag;
    if ($text =~ /\{\{(DYK talk|dyktalk)/) {
        $tag = $1;
    }
    if ($tag) {
        my $has_nested_text = has_nested_text ($tag, $text);
        if ($text =~ s/{{$tag\|(.+?)\|(\d+)\|entry=(.+?)}}//is) {
#            print "\tFound DYK\n";
            my $dykdate="$1 $2";
            my $dykentry = $3;   
            $text =~ s/currentstatus=FA/currentstatus=FA\n\n|dykdate=$dykdate\n|dykentry=$dykentry/is;         
        }
        if ($has_nested_text) {
            reset_nested_text ($text);       
        }
    }
   
    # Update the class for all projects
    $text =~ s/([^-]class)\s*=\s*(\w+)/$1=FA/igs;

    $editor->edit ({
        page => $talk,
        text => $text,
        summary => "Promoting '$page' to Featured Article status",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$talk'");
}

sub archive_update_talk_page ($$$$$) {
    my ($page, $talk, $nomination_page, $date, $time) = @ARG;
   
    $cred->showtime ("\tUpdating the talk page\n");
    my $text = $editor->get_text ($talk) or
        error_exit ("Unable to find '$talk')");

    # Remove the candidacy
    $text =~ s/{{featured article candidates\|.+?}}//;

    # Add the GA review, if any, to the article history
    if ($text =~ /{{GA[^L].+?}}/gis) {
        $text = add_ga_to_history ($page, $text, $talk);   
    }

    # add an old Peer review, if any, to the article history
    if ($text =~ /{{oldpeerreview.+?}}/gis) {
        $text = add_pr_to_history ($page, $text);   
    }

    # Update the article history
    my $revid = get_revid ($page, $date, $time);
    $text = update_article_history ($text, 'FAC', $date, $nomination_page, 'failed', $revid); 
   
    # Update the current status
    unless ($text =~ /currentstatus\s*=\s*.+/is) {
        if ($text =~ /class\s*=\s*GA/is) {
            $text =~ s/{{Article\s*History/{{ArticleHistory\n|currentstatus=GA\n/is;
        }
    }
           
    # Add DYK, if any, to the article history   
    my $tag;
    if ($text =~ /\{\{(DYK talk|dyktalk)/) {
        $tag = $1;
    }
    if ($tag) {
        my $has_nested_text = has_nested_text ($tag, $text);
        if ($text =~ s/{{$tag\|(.+?)\|(\d+)\|entry=(.+?)}}//is) {
#            print "\tFound DYK\n";
            my $dykdate="$1 $2";
            my $dykentry = $3;   
            $text =~ s/currentstatus=FA/currentstatus=FA\n\n|dykdate=$dykdate\n|dykentry=$dykentry/is;         
        }
        if ($has_nested_text) {
            reset_nested_text ($text);       
        }
    }

    $editor->edit ({
        page => $talk,
        text => $text,
        summary => "Updating '$page' after unsuccessful Featured Article nomination",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$talk'");
}


# Find the nomination page
sub nomination ($) {
    my ($talk) = @ARG;
    my $text = $editor->get_text ($talk) or
        error_exit ("Unable to find '$talk')");
    $text =~ /{{featured article candidates\|(.+?\/archive\d+)/;
    error_exit ("Unable to find featured article candidates template in '$talk'") unless $1;
    my $nomination = "Wikipedia:Featured article candidates/$1";
    $encoded_nomination = $nomination;
    $nomination =~ s/&#([0-9a-f]+);/chr($1)/ige;
    $cred->showtime ("\t$nomination\n");
    return $nomination;
}

sub update_announcements_page  ($) {
    $cred->showtime ("\tUpdating the announcements page\n");
    my ($article) = @ARG;
   
    my $text = $editor->get_text ($announcements) or
        error_exit ("Unable to find '$announcements'");
    $cred->error ("no bots allowed on '$announcements'") unless $cred->allow_bots ($text);

    if ($text =~ /\Q$article\E/) {
        $cred->showtime ("\t\tAlready updated -- skipping\n");
        return;
    }

    my $in_list_section = 0;
    my $section_max;
    my @input_lines = split /\n/, $text;
    my @output_lines;
    foreach (@input_lines) {
        if (/<!-- Articles \((\d+), most recent first\) -->/) {
            $section_max = $1;
            $in_list_section++;
            my $a = $article;
            push @output_lines, $ARG, "* [[$article]]";
            next;
        }
        if ($in_list_section) {
            if (/^$|<\/div>|<!-- Topics \(\d+, most recent first\) -->/) {
                $in_list_section = 0;
            } elsif ($in_list_section < $section_max) {
                $in_list_section++;
            } else {
                next;
            }
        }
        push @output_lines, $ARG;
    }   

    $text = join "\n", @output_lines;

    $editor->edit ({
        page => $announcements,
        text => $text,
        summary => "$article promoted to Featured Article status",
        minor => 0,
    }) or
        error_exit ("unable to edit '$announcements'");
}

sub text_to_month ($) {
    my ($month) = @ARG;
    for my $i (0..11) {
        if ($months[$i] eq $month) {
            return $i + 1;
        }
    }       
}

sub update_goings_on_page  ($$) {
    $cred->showtime ("\tUpdating the goings_on page\n");
    my ($article, $timestamp) = @ARG;
   
    my $text = $editor->get_text ($goings_on) or
        error_exit ("Unable to find '$goings_on'");
    $cred->error ("no bots allowed on '$goings_on'") unless $cred->allow_bots ($text);

    my ($m, $d, $y) = $text =~ /week starting Sunday, \[\[(\w+) (\d+)\]\], \[\[(\d+)\]\]/;
#    print "$d $m $y\n";
    my $delta_days = Delta_Days ($y, text_to_month ($m), $d, $timestamp->{YEAR}, text_to_month ($timestamp ->{MONTH}) ,$timestamp->{DAY});
#    print "delta days=$delta_days\n"; # Normally positive
    if ($delta_days < 0) {
        $cred->showtime ("\t\tArticle dated $timestamp->{DAY} $timestamp->{MONTH} $timestamp->{YEAR} but page is $d $m $y -- skipping\n");
        return;
    }

    if ($text =~ /\Q$article\E/) {
        $cred->showtime ("\t\tAlready updated -- skipping\n");
        return;
    }

    my $abbr = substr ($timestamp->{MONTH}, 0, 3);
    my $day = $timestamp->{DAY};
    $day =~ s/^0//;
    my $date = "$day $abbr";

    my $in_list_section = 0;
    my @input_lines = split /\n/, $text;
    my @output_lines;
    foreach (@input_lines) {
        if (/Wikipedia:Featured articles/) {
            $in_list_section = 1;
        }
        if ($in_list_section) {
            if (/^$|Wikipedia:Featured lists/) {
                $in_list_section = 0;
                push @output_lines, "* [[$article]] ($date)";
            }
        }
        push @output_lines, $ARG;
    }   

    $text = join "\n", @output_lines;

    $editor->edit ({
        page => $goings_on,
        text => $text,
        summary => "$article promoted to Featured Article status",
        minor => 0,
    }) or
        error_exit ("unable to edit '$goings_on'");
}

sub add_to_showcase ($) {
    my ($article) = @ARG;
       
    my $showcase_text = $editor->get_text ($showcase_fa) or
        error_exit ("Unable to find '$showcase_fa'");
    $cred->error ("no bots allowed on '$showcase_fa'") unless $cred->allow_bots ($showcase_text);

    my $showcase = new showcase ($showcase_text);
    $showcase->add ($article);

    $editor->edit ({
        page => $showcase_fa,
        text => $showcase->text,
        summary => "'$article' has been promoted to Featured Article status",
#            bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$showcase_fa'");
}

sub remove_from_showcase ($) {
    my ($article) = @ARG;
       
    my $showcase_text = $editor->get_text ($showcase_a) or
        error_exit ("Unable to find '$showcase_a'");
    $cred->error ("no bots allowed on '$showcase_a'") unless $cred->allow_bots ($showcase_text);

    my $showcase = new showcase ($showcase_text);
    my $found = $showcase->del ($article);

    if ($found) {   
        $editor->edit ({
            page => $showcase_a,
            text => $showcase->text,
            summary => "'$article' has been promoted to Featured Article status",
#            bot => 1,
            minor => 0,
        }) or
            error_exit ("unable to edit '$showcase_a'");
    }
    return $found;
}

sub is_older_nomination ($$) {
    my ($nomination, $twenty_days_ago) = @ARG;   

#    print "$nomination\n";

    my @history = $editor->get_history ($nomination) or
        error_exit ("Unable to get history of '$nomination'");

    my $revision = pop @history;
#    print "$revision->{timestamp_date}\n";

    my $is_older_nomination = $revision->{timestamp_date} lt $twenty_days_ago;
#    print $is_older_nomination ? "\tis older than twenty_days_ago\n" : "\tis NOT older than twenty_days_ago\n" ;
   
    return $is_older_nomination;
}

sub move_the_daily_marker () {
    my $text = $editor->get_text ($candidates_page) or
        error_exit ("Unable to find '$candidates_page'");
    $cred->error ("no bots allowed on '$candidates_page'") unless $cred->allow_bots ($text);

    my @input = split /\n/, $text;
    my @output;
    my $nominations = 0;
    my @older_nominations;
    my $older_nominations = 0;
   
    $cred->showtime ("Move the daily marker\n");
    my $twenty_days_ago = strftime ('%Y-%m-%d', gmtime(time () - 20 * 24 * 60 * 60));
#    print "twenty days ago was $twenty_days_ago\n";

    foreach (@input) {
        if (/<!--|-->/) {
        }elsif (/==Nominations==/) {
            $nominations = 1;           
        } elsif (/==Older nominations==/) {
            $older_nominations = 1;
            $nominations = 0;
        } elsif ($nominations) {
            if (/{{(Wikipedia:Featured article candidates.+)}}/) {
                my $nomination = $1;
                if (is_older_nomination ($nomination, $twenty_days_ago)) {
                    push @older_nominations, "{{$nomination}}";
                    next;   
                }   
            }   
        } elsif ($older_nominations) {
            if (@older_nominations) {
                push @output, @older_nominations;   
                $older_nominations = 0;
            }
        }
        push @output, $ARG;
    }
   
    unless (@older_nominations) {
        $cred->showtime ("No need to reset daily marker\n");
        return;
    }
   
    $text = join "\n", @output;
   
    $editor->edit ({
        page => $candidates_page,
        text => $text,
        summary => 'update daily marker',
        minor => 0,
    }) or
        error_exit ("unable to edit '$candidates_page'");

    $cred->showtime ("Daily marker reset\n");
}

$editor->login ({
    username => $cred->user,
    password => $cred->password
}) or error_exit ("login failed");

$cred->showtime ("========== Commenced ==========\n");

move_the_daily_marker ();

# First, we need to find the nomination pages
my @candidates = $editor->get_pages_in_category ('Wikipedia featured article candidates');
foreach my $talk (@candidates) {

    my $article = $talk;
    $article =~ s/Talk:// or
        next;

    my $nomination = nomination ($talk);
    if (my ($display_date, $day, $month, $year) = has_been_closed ($nomination)) {
        $cred->showtime ("\t$nomination closed on $display_date\n");
        if (has_been_promoted ($nomination, $month, $year)) {
            my ($user, $date, $time, $diff) = whodunnit ($article, $nomination, 'promoted');
            promote_update_talk_page ($article, $talk, $nomination, $date, $time);
            promote_update_nomination_page ($article, $nomination, $user, $display_date, $diff);
            promote_update_article_page ($article);
            update_announcements_page ($article);
            update_goings_on_page ($article, {YEAR => $year, MONTH => $month, DAY => $day});
            my $found = remove_from_showcase ($article);
            add_to_showcase ($article) if $found;
        } elsif (has_been_archived ($nomination, $month, $year)) {
            my ($user, $date, $time, $diff) = whodunnit ($article, $nomination, 'archived');
            archive_update_talk_page ($article, $talk, $nomination, $date, $time);
            archive_update_nomination_page ($article, $nomination, $user, $display_date, $diff);
        } else {
            $cred->showtime ("WARNING: FAC closed but $nomination has NOT been moved to the archive page\n");
        }       
    } else {
        $cred->showtime ("\t$nomination is still current\n");
    }
}
$cred->showtime ("finished okay\n");
exit 0;