User:HBC AIV helperbot/source
Appearance
This source is released under GFDL.
This code relies on mwAPI.pm which is a simplified interface to the MediaWiki API. This code now references HBC AIV helperbot5 specifically as all other helperbots are no longer active. User:HBC AIV helperbot14 does not use this code - the code for the rewrite is stored on Github |
#!/usr/bin/perl
# This script is released under the GFDL license, see
# http://en.wikipedia.org/w/index.php?title=User:HBC_AIV_helperbot/source&action=history
# for a full list of contributors
use strict;
use warnings;
use DateTime;
use DateTime::Format::Duration;
use mwAPI;
use Net::Netmask;
use POSIX qw(strftime);
use Time::Local;
use URI::Escape;
use Data::Dumper;
my $mw = mwAPI->new();
### Configuration ###
my $read_rate = 30;
my $write_rate = 30;
my (%pages_to_watch) =
(
'Wikipedia:Administrator intervention against vandalism' => $read_rate,
'Wikipedia:Administrator intervention against vandalism/TB2' => $read_rate,
'Wikipedia:Usernames for administrator attention' => $read_rate * 2, # lots of users slows it down, check less often
'Wikipedia:Usernames for administrator attention/Bot' => $read_rate * 2, # lots of users slows it down, check less often
# 'User:HBC AIV helperbot5/sandbox' => 10
);
# Pattern to match examples used in the instructions
my $example_pattern = qr/(?:IP ?address|username)/i;
my @desired_parameters = qw(
RemoveBlocked MergeDuplicates AutoMark FixInstructions AutoBacklog
);
### End Configuration ###
my $version_number = '2.0.33';
my $VERSION = "HBC AIV helperbot v$version_number";
my %special_ips;
my %notable_cats;
my $instructions = '';
local $SIG{'__WARN__'} = \&mywarn;
open(PASS,'password'); sysread(PASS, $mw->{password}, -s(PASS)); close(PASS);
open(USER,'username'); sysread(USER, $mw->{username}, -s(USER)); close(USER);
# The program runs in this loop which handles a queue of jobs.
my(@job_list);
my $timing = 0;
add_job( [ \&login ], 0 );
add_job( [ \&get_ip_list ], 0 );
add_job( [ \&get_instructions ], 0 );
foreach my $page (keys %pages_to_watch)
{
add_job([\&check_page, $page],$timing);
$timing += 5;
}
while (1) # Infinite loop, a serpent biting it's own tail.
{
sleep(1); # Important in all infinite loops to keep it calm
my (@kept_jobs); # A place to put jobs not ready to run yet
while (my $job = shift(@job_list)) # Go through each job pending
{
my($r_job , $timing) = @{$job};
if ($timing < time()) # If it is time to run it then run it
{
if (ref($r_job) eq 'ARRAY') # Callback style, reference an array with a sub followed by paramaters
{
my $cmd = shift(@{$r_job});
&{$cmd}(@{$r_job});
}
elsif (ref($r_job) eq 'CODE') # Otherwise just the reference to the sub
{
&{$r_job};
}
warn( "Loop: ".scalar(@job_list)." jobs in queue, ".scalar(@kept_jobs)." put aside.\n" ) if( $ENV{LOOP_DEBUG} );
}
else # If it is not time yet, save it for later
{
push(@kept_jobs , $job)
}
}
push (@job_list , @kept_jobs); # Keep jobs that are still pending
}
###################
### SUBROUTINES ###
###################
sub add_job {
my ($r_job , $timing) = @_;
push (@job_list , [$r_job , (time()+$timing)]);
}
sub login {
warn "$VERSION logging in to en.wikipedia.org...\n";
my( $username, $password ) = @_;
my $result = $mw->login( $username, $password );
return $result;
}
sub get_ip_list
{
warn "Fetching Special IP list...\n";
my $ip_table = $mw->get('User:HBC AIV helperbot/Special IPs');
unless ($ip_table) {
warn "Failed to load page - will try again in 2 minutes.\n";
add_job([\&get_ip_list],120);
return;
}
%special_ips = (); # Clear any old list
foreach my $line (split("\n",$ip_table))
{
if ($line =~ m|^\* \[\[:Category:(.*?)\]\]$|)
{
$notable_cats{$1} = 1;
next;
}
next unless ($line =~ m|^;(.*?):(.*)$|);
my ($ip, $comment) = ($1, $2);
next unless ($ip =~ m|^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:/\d{1,2})?$|);
$special_ips{$ip} = "This IP matches the mask ($ip) in my [[User:HBC AIV helperbot/Special IPs|special IP list]] which is marked as: \"$comment\"";
}
warn "Done, will check again in 10 minutes.\n";
add_job([\&get_ip_list],600); # Run myself in 10 minutes
}
sub get_instructions {
warn "Fetching instructions...\n";
my $content = $mw->get('Wikipedia:Administrator intervention against vandalism/instructions');
unless ($content) {
warn "Failed to load page - will try again in 2 minutes.\n";
add_job([\&get_instructions],120);
return;
}
$instructions = ''; # start with a clean slate
my $keep = 0;
foreach my $line (split("\n",$content)) {
if (!$keep && $line =~ m/^<!-- HBC AIV helperbot BEGIN INSTRUCTIONS -->$/) {
$keep = 1;
next;
} elsif ($keep && $line =~ m/^<!-- HBC AIV helperbot END INSTRUCTIONS -->$/) {
$keep = 0;
}
next unless $keep;
$instructions .= "$line\n";
}
chomp($instructions);
warn "Done, will check again in 30 minutes.\n";
add_job([\&get_instructions],1800);
}
sub check_instructions {
my ($page, $content) = @_;
unless ($content =~ m/\Q$instructions\E/s) {
add_job([\&fix_instructions,$page],0);
return 0;
}
return 1;
}
sub check_page {
# Read the page and gather usernames, give each use a check_user job on the queue
# Then add check_page to the queue scheduled for $read_rate seconds
my $page = shift;
# Get page, read only
my $content = $mw->get( $page );
unless ($content && $content =~ m|\{\{((?:no)? ?admin ?backlog)\|bot=HBC AIV helperbot5\}\}|i)
{
warn "Could not find backlog tag, not doing anything: $page\n";
add_job( [ \&check_page, $page ], $pages_to_watch{ $page } );
return;
}
my $ab_current = $1;
unless ($content && $content =~ m|<\!-- (?:HBC AIV helperbot )?v([\d.]+) ((?:\w+=\S+\s+)+)-->|i)
{
warn "Could not find parameter string, not doing anything: $page\n";
add_job( [ \&check_page, $page ], $pages_to_watch{ $page } );
return;
}
my ($active_version, $parameters) = ($1,$2);
unless (check_version($active_version)) {
warn "Current version $version_number not allowed by active version $active_version on $page! Will check again in $pages_to_watch{ $page } minutes.\n";
add_job( [ \&check_page, $page ], $pages_to_watch{ $page } ); # Schedule myself 2 minutes later
return;
}
my $params = parse_parameters($parameters);
add_job( [ \&check_page, $page ], $pages_to_watch{ $page } );
($params->{'AutoBacklog'} = '') if ($params->{'AddLimit'} <= $params->{'RemoveLimit'});
if ($params->{'FixInstructions'} eq 'on') {
return unless check_instructions($page,$content);
}
my @content = split("\n",$content); # Split into lines
my $report_count = 0;
my (%user_count, @IP_comments_needed, $merge_called, $in_comment);
foreach my $line (@content)
{
my $bare_line;
($in_comment,$bare_line, undef) = comment_handler($line, $in_comment);
next if ($in_comment && ($line eq $bare_line));
($bare_line =~ m/{\{((?:ip)?vandal|userlinks|user-uaa)\|\s*(.+?)\s*\}}/i) || next(); # Go to next line if there is not a vandal template on this one.
my $user = $2; # Extract username from template
my $user2;
if ($user =~ m/^((?:1|user)=)/i) {
$user2 = $user;
$user =~ s/^$1//i;
}
$report_count++;
$user_count{$user}++;
if (($user_count{$user} > 1) && !($merge_called) && ($params->{'MergeDuplicates'} eq 'on'))
{
warn "Calling merge because of $user on $page\n";
add_job([\&merge_duplicate_reports,$page],0);
$merge_called = 1;
}
add_job([\&check_user,$user,$page,$line],0) if ($params->{'RemoveBlocked'} eq 'on'); # Queue a check_user job for the user to run ASAP
my(@cats) = check_cats($user);
if (scalar(@cats))
{
$special_ips{$user} = 'User is in the '.((scalar(@cats) > 1) ? ('categories') : ('category')).': ';
foreach (@cats)
{
$_ = '[[:Category:'.$_.'|'.$_.']]'
}
$special_ips{$user} .= join(', ',@cats);
$special_ips{$user} .= '.';
}
if ($params->{'AutoMark'} eq 'on' && !$merge_called)
{
if ($line !~ m|<\!-- Marked -->|)
{
foreach my $mask (keys(%special_ips))
{
if ($mask =~ m|^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:/\d{1,2})?$| && $user =~ m|^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$|) {
if (Net::Netmask->new($mask)->match($user))
{
push (@IP_comments_needed, [\&comment_special_IP,$page,$user,$mask]);
last; # only match one mask
}
} else {
if ($mask eq $user) {
push (@IP_comments_needed, [\&comment_special_IP,$page,$user,$mask]);
last; # only match one mask
}
}
}
}
}
}
foreach my $ra_param (@IP_comments_needed) {
add_job([@{$ra_param},$report_count],0);
}
if ($params->{'AutoBacklog'} eq 'on' && !$merge_called)
{
add_job([\&set_backlog,$page,$report_count,$params->{'AddLimit'},$params->{'RemoveLimit'}],0)
if ((($report_count >= $params->{'AddLimit'}) && ($ab_current eq 'noadminbacklog')) ||
(($report_count <= $params->{'RemoveLimit'}) && ($ab_current eq 'adminbacklog')));
}
return;
}
sub check_version {
my ($active_version) = @_;
my @active_parts = split(/\./, $active_version);
my @my_parts = split(/\./, $version_number);
return 0 if scalar(@active_parts) > scalar(@my_parts); # should never happen
foreach (@active_parts) {
my $check_part = shift(@my_parts);
last if $check_part > $_;
next if $_ <= $check_part;
return 0;
}
return 1;
}
sub parse_parameters {
my ($parameters) = @_;
my %result;
foreach my $item (split(/\s+/, $parameters)) {
my ($key, $value) = split(/=/, $item);
$result{$key} = lc($value);
}
foreach (@desired_parameters) {
$result{$_} ||= 'off';
}
if ($result{'AutoBacklog'} eq 'on') {
$result{'AddLimit'} ||= 0;
$result{'RemoveLimit'} ||= 0;
}
return \%result;
}
sub comment_handler {
my ($line, $in_comment) = @_;
my ($comment_starts, $comment_ends, $remainder) = (0,0,'');
if ($in_comment) {
# check if an opened comment ends in this line
if ($line =~ m|-->|) {
$line =~ s|(.*?-->)||;
$in_comment = 0;
$comment_ends = 1;
$remainder = $1;
}
}
# remove any self-contained comments
$line =~ s|<!--.*?-->||g;
if ($line =~ s|<!--.*||) {
$in_comment = 1;
$comment_starts = 1;
}
return (wantarray) ? ($in_comment, $line, $remainder) :
$in_comment;
}
sub check_cats
{
my ($user) = @_;
my (@response);
my $data = $mw->api2data( 'query', [ prop => 'categories', titles => "User talk :$user" ] );
return () unless( exists $data->{query}{pages}{page}{categories}{cl} );
my $cat_list = $data->{query}{pages}{page}{categories}{cl};
$cat_list = ( ( ref( $cat_list ) eq 'ARRAY' ) ? ( $cat_list ) : ( [ $cat_list ] ) );
foreach my $cat ( @{ $cat_list } ) {
$cat->{title} =~ s/^Category://;
push(@response, $cat->{title}) if ( $notable_cats{ $cat->{title} } );
}
return @response;
}
sub check_user {
# Determine if the user is blocked, and if so gather information about the block
# and schedule a remove_name job with all the information passed along
my ($user,$page,$line) = @_;
my $search_key = 'bkusers';
if ($user =~ /^(?:\d{1,3}\.){3}\d{1,3}$/ or $user =~ /^(?:[0-9a-f]{1,4}:){7}[0-9a-f]{1,4}$/i) {
$search_key = 'bkip';
}
my $data = $mw->api2data( 'query', [ list => 'blocks', bkprop => 'id|user|by|timestamp|expiry|flags|restrictions', $search_key => $user ] );
my $block = $data->{query}{blocks}{block};
return unless $block;
return unless $block->{id}; # there are multiple blocks; don't touch the AIV report
foreach ( keys( %{ $block } ) ) {
$block->{$_} = 1 if ( !$block->{$_} && defined $block->{$_} )
};
foreach my $time ( qw( expiry timestamp ) ) {
if ( $block->{$time} =~ m|(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})| ) {
$block->{$time.'_epoch'} = DateTime->new(year=>$1,month=>$2,day=>$3,hour=>$4,minute=>$5,second=>$6,time_zone=>'UTC');
} elsif ( $block->{$time} eq 'infinity' ) {
$block->{duration} = 'indef';
} else { $block->{duration} = 'unknown' }
}
$block->{duration} ||= timeconv( $block->{expiry_epoch}, $block->{timestamp_epoch} );
delete( $block->{expiry_epoch} ); delete( $block->{timestamp_epoch} );
if ($block->{partial}) {
if ($line !~ m/<!-- PBMarked -->/) {
add_job([\&comment_partial_blocked,$page,$user,$block],0);
}
} else {
my(@flags);
push(@flags,'AO' ) if ($block->{anononly});
push(@flags,'TPD') if (!$block->{allowusertalk});
push(@flags,'ACB') if ($block->{nocreate});
push(@flags,'EMD') if ($block->{noemail});
push(@flags,'Partial') if ($block->{partial});
my $block_type = '';
$block_type = '[[User:HBC AIV helperbot/Legend|('.join(' ',@flags).')]]' if (scalar(@flags));
add_job([\&remove_name,$user,$block->{user},$block->{by},$block->{duration},$block_type,$page],0);
}
}
sub timeconv {
my($expiry, $block_time) = @_;
my $duration = $expiry - $block_time;
my $formatter = DateTime::Format::Duration->new(
pattern => '%Y years, %m months, %e days, %H hours, %M minutes, %S seconds',
normalize => 1,
base => $block_time,
);
my %normalized = $formatter->normalize($duration);
my @periods = ('years','months','days','hours','minutes','seconds');
my $output;
if ($normalized{'minutes'} || $normalized{'seconds'}) {
$output = sprintf('until %s %s ', $expiry->ymd, $expiry->hms);
} else {
foreach (@periods) {
$output .= sprintf('%s %s, ', $normalized{$_}, $_) if $normalized{$_};
if ($normalized{$_} == 1) {
my $singular = $_;
$singular =~ s/s$//;
$output =~ s/$_/$singular/;
}
}
$output =~ s/, $/ /;
# special cases
my %special_cases = (
'1 day, 7 hours ' => '31 hours ',
'1 day, 12 hours ' => '36 hours ',
'2 days, 12 hours ' => '60 hours ',
);
$output = $special_cases{$output} if( exists $special_cases{$output} );
}
return $output;
}
sub merge_duplicate_reports {
my ($page_name) = @_;
my( $page, $edit_token ) = $mw->get($page_name);
return unless $page && $edit_token;
my(@content) = split("\n",$page); # Split into lines
my (@new_content, %user_table, $report_count, $in_comment);
while (scalar(@content)) {
my $line = shift(@content);
my $bare_line;
($in_comment,$bare_line,undef) = comment_handler($line, $in_comment);
next if $line eq "\n";
if (($in_comment && ($line eq $bare_line)) || $bare_line !~ m/{\{((?:ip)?vandal|userlinks|user-uaa)\|\s*(.*?)\s*\}}/i) {
push(@new_content,$line); next;
}
my $user = $2;
if ($user =~ m/^((?:1|user)=)/i) {
$user =~ s/^$1//i;
}
if ($user) {
unless ($user_table{$user}) {
push(@new_content,$line);
$user_table{$user} = \$new_content[scalar(@new_content)-1];
while ((scalar(@content)) && !($content[0] =~ m/{\{((?:ip)?vandal|userlinks|user-uaa)\|/i) && !($content[0] =~ m|<\!--|)) {
my $comment = shift(@content);
$in_comment = comment_handler($comment, $in_comment);
${$user_table{$user}} .= "\n$comment"
}
$report_count++;
}
else {
$line =~ s|^\*||;
$line =~ s/{\{((?:ip)?vandal|userlinks|user-uaa)\|\s*(.*?)\s*\}}//i;
${$user_table{$user}} .= "\n*:$line <small><sup>(Moved by bot)</sup></small>";
}
}
}
my $tally;
$tally = 'Empty.' if ($report_count == 0);
$tally ||= ($report_count.' report'.(($report_count > 1) ? ('s remaining.') : (' remaining.')));
my $save_content = join("\n",@new_content);
my $save_summary = "$tally Duplicate entries merged";
$mw->put( $page_name, $save_summary, $save_content, 1, $edit_token );
}
sub comment_special_IP
{
my($page_name,$user,$mask,$report_count) = @_;
my( $page, $edit_token) = $mw->get($page_name); # Get page read/write
return unless $page;
my(@content) = split("\n",$page); # Split into lines
my (@new_content, $in_comment); # Place to put replacement content
foreach my $line (@content) {
$in_comment = comment_handler($line, $in_comment);
if (($line =~ m|\Q$user\E|) && ($line =~ m/{\{((?:ip)?vandal|userlinks|user-uaa)/i))
{
return if ($line =~ m|<\!-- Marked -->|);
$line .= ' -->' if $in_comment;
$line .= ' <!-- Marked -->'."\n*:'''Note''': $special_ips{$mask} ~~~~";
$line .= ' <!-- ' if $in_comment;
}
push(@new_content,$line);
}
my $tally;
$tally = 'Empty.' if ($report_count == 0);
$tally ||= ($report_count.' report'.(($report_count > 1) ? ('s remaining.') : (' remaining.')));
my $save_content = join("\n",@new_content);
my $save_summary = $tally." Commenting on $user: $special_ips{$mask}";
$mw->put( $page_name, $save_summary, $save_content, undef, $edit_token );
warn "$user matched $mask, marked as: $special_ips{$mask}\n";
return 1;
}
sub comment_partial_blocked
{
my($page_name,$user,$block) = @_;
my( $page, $edit_token) = $mw->get($page_name); # Get page read/write
return unless $page;
my(@content) = split("\n",$page); # Split into lines
my (@new_content, $in_comment); # Place to put replacement content
my $report_count = 0;
foreach my $line (@content) {
my $bare_line;
($in_comment,$bare_line, undef) = comment_handler($line, $in_comment);
if (not ($in_comment && ($line eq $bare_line))) {
if (($line =~ m/{\{((?:ip)?vandal|userlinks|user-uaa)/i)) {
$report_count++;
if ($line =~ m|\Q$user\E|) {
return if ($line =~ m|<\!-- PBMarked -->|);
$line .= ' -->' if $in_comment;
$line .= ' <!-- PBMarked -->'."\n*:'''Note''': $block->{user} is currently partial blocked. ~~~~";
$line .= ' <!-- ' if $in_comment;
}
}
}
push(@new_content,$line);
}
my $tally;
$tally = 'Empty.' if ($report_count == 0);
$tally ||= ($report_count.' report'.(($report_count > 1) ? ('s remaining.') : (' remaining.')));
my $save_content = join("\n",@new_content);
my $save_summary = $tally." Commenting on $user\'s partial block";
$mw->put( $page_name, $save_summary, $save_content, undef, $edit_token );
warn "$user is partial blocked, marked\n";
return 1;
}
sub set_backlog
{
my ( $page_name, $report_count,$ab_add,$ab_remove) = @_;
$report_count ||= '0';
my( $page, $edit_token ) = $mw->get($page_name); # Get page read/write
return unless $page;
my(@content) = split("\n",$page); # Split into lines
my(@new_content); # Place to put replacement content
my $summary;
foreach my $line (@content)
{
if ($line =~ m|^\{\{(?:no)?adminbacklog\|bot=HBC AIV helperbot5\}\}|i)
{
my $tally;
$tally = 'Empty.' if ($report_count == 0);
$tally ||= ($report_count.' report'.(($report_count > 1) ? ('s remaining.') : (' remaining.')));
if ($report_count >= $ab_add)
{
warn "Backlog tag added to: $page_name\n";
$summary = ($tally.' Noticeboard is backlogged.');
$line =~ s|^\{\{noadminbacklog|\{\{adminbacklog|i;
push (@new_content,$line);
}
elsif ($report_count <= $ab_remove)
{
warn "Backlog tag removed from: $page_name\n";
$summary = ($tally.' Noticeboard is no longer backlogged.');
$line =~ s|^\{\{adminbacklog|\{\{noadminbacklog|i;
push (@new_content,$line);
}
}
else
{
push(@new_content,$line);
}
}
my $content = join("\n",@new_content);
return unless($content);
$mw->put( $page_name, $summary, $content, 1, $edit_token );
}
sub mywarn {
my ($msg) = @_;
if ($^O eq 'MSWin32')
{
CORE::warn($msg);
}
else
{
CORE::warn('['.strftime('%F %T UTC',gmtime()).'] '.$msg);
}
}
sub fix_instructions {
my ($page_name) = @_;
my( $content, $edit_token ) = $mw->get($page_name);
return unless $content;
my $summary;
if ($content =~ m|===\s*User-reported\s*===\n|s) {
$content =~ s|<!-- HagermanBot Auto-Unsigned -->|RE-ADD-HAGERMAN|;
my @content = split("\n", $content);
my (@reports_to_move, $in_comment, $report_count, $msg);
foreach my $line (@content) {
my ($bare_line,$remainder);
($in_comment,$bare_line,$remainder) = comment_handler($line, $in_comment);
if ($line =~ m/{\{((?:ip)?vandal|userlinks|user-uaa)\|\s*(?!$example_pattern)/i) {
push(@reports_to_move, $line) if $in_comment;
$report_count++;
} elsif ($remainder =~ m/{\{((?:ip)?vandal|userlinks|user-uaa)\|\s*(?!$example_pattern)/i) {
$remainder =~ s/-->//;
push(@reports_to_move, $remainder);
}
}
if ($content =~ m|===\s*User-reported\s*===\s+<!--|s) {
$content =~ s:(===\s*User-reported\s*===\s+)<!--.*?(-->|$):$1$instructions:s;
$msg = '';
} else {
$content =~ s|(===\s*User-reported\s*===\n)|$1$instructions\n|s;
$msg = ' Old instructions not found, please check page for problems.';
}
my $remaining_text;
if ($report_count) {
$remaining_text = ($report_count > 1) ? "$report_count reports remaining." : "$report_count report remaining.";
} else {
$remaining_text = "Empty.";
}
if (@reports_to_move) {
my $reports_moved = scalar(@reports_to_move);
if ($reports_moved > 50) {
$summary = "$remaining_text Reset [[WP:AIV/I|instruction block]], WARNING: tried to move more than 50 reports, aborting - check history for lost reports.$msg";
} else {
foreach my $report (@reports_to_move) {
if ($report =~ m|RE-ADD-HAGERMAN|) {
$report =~ s|RE-ADD-HAGERMAN|<!-- HagermanBot Auto-Unsigned -->|;
$report =~ s|~~~~||;
} else {
$report =~ s|~~~~|~~~~ <small><sup>(Original signature lost - report made inside comment)</sup></small>|;
}
$content .= "$report\n";
}
$summary = "$remaining_text Reset [[WP:AIV/I|instruction block]], $reports_moved report(s) moved to end of page.$msg";
}
} else {
$summary = "$remaining_text Reset [[WP:AIV/I|instruction block]].$msg";
}
$content =~ s|RE-ADD-HAGERMAN|<!-- HagermanBot Auto-Unsigned -->|;
$mw->put( $page_name, $summary, $content, undef, $edit_token);
warn "Reset instruction block: $page_name\n";
} else {
warn "FATAL ERROR: User-reported header not found on $page_name! Sleeping 2 minutes.\n";
unless ($content =~ m|<!-- HBC AIV helperbot WARNING -->|) {
$content .= "<!-- HBC AIV helperbot WARNING -->\n";
$summary = 'WARNING: User-reported header not found!';
$mw->put( $page_name, $summary, $content, undef, $edit_token);
}
sleep(120);
return;
}
}
sub remove_name
{
my ($user,$block_target,$blocker,$duration,$block_type,$page_name) = @_;
my( $page, $edit_token) = $mw->get($page_name); # Get page read/write
return unless $page;
my($ips_left,$users_left) = ('0','0'); # Start these with 0 instead of undef
my(@content) = split("\n",$page); # Split into lines
my (@new_content, $found, $lines_skipped, $in_comment);
while (scalar(@content)) {
my $line = shift(@content);
my ($bare_line,$remainder);
($in_comment,$bare_line,$remainder) = comment_handler($line, $in_comment);
unless (!$in_comment && $line =~ m/{\{((?:ip)?vandal|userlinks|user-uaa)\|\s*(?:1=|user=)?\Q$user\E\s*\}}/i)
{
push(@new_content,$line);
next if ($in_comment && ($line eq $bare_line));
if($bare_line =~ m/{\{IPvandal\|/i)
{
$ips_left++;
}
if($bare_line =~ m/{\{(vandal|userlinks|user-uaa)\|/i)
{
$users_left++;
}
}
else
{
$found = 1;
push(@new_content,$remainder) if $remainder;
while ((scalar(@content)) && !($content[0] =~ m/{\{((?:ip)?vandal|userlinks|user-uaa)\|/i) && !($content[0] =~ m|^<\!--|) && !($content[0] =~ m/^=/))
{
my $removed = shift(@content);
if (length($removed) > 0) {
$lines_skipped++;
$in_comment = comment_handler($removed, $in_comment);
}
}
}
}
$page = join("\n",@new_content);
return unless($found); # Cancel if could not find the entry attempting to be removed.
return unless($page); # Cancel if result would blank the page.
my $length = ((defined($duration)) ? (' '.$duration) : (' '));
$length = ' indef ' if (defined($duration) && $duration eq 'indef');
my $tally;
if ($ips_left || $users_left)
{
$tally = join(' & ',
(
(($ips_left) ? ($ips_left.' IP'.(($ips_left > 1) ? ('s') : (''))) : ()),
(($users_left) ? ($users_left.' user'.(($users_left > 1) ? ('s') : (''))) : ()),
)).' left.';
}
else
{
$tally = 'Empty.'
}
my $skipped = (($lines_skipped) ? (" $lines_skipped comment(s) removed.") : (''));
my $rangecomment = ($user eq $block_target) ? "blocked" : "[[Special:Contributions/$block_target|$block_target]] rangeblocked";
my $summary = $tally.' rm [[Special:Contributions/'.$user.'|'.$user.']] ('.$rangecomment.$length.'by [[User:'.$blocker.'|'.$blocker.']] '.$block_type.'). '.$skipped;
$mw->put( $page_name, $summary, $page, undef, $edit_token );
warn "rm '$user': $page_name\n";
sleep($write_rate);
}