Main 'master control' program:
use strict;
use Data::Dumper;
use Time::HiRes qw(sleep);
my $data_root = 'data/';
our(%shared_data);
%shared_data =
(
job_list => [],
add_job => sub {my ($r_job , $timing) = @_;push (@{$shared_data{job_list}} , [$r_job , (time()+$timing)]);}
);
my(%plugins);
open(CFG,'HBC_MCP.cfg');
sysread(CFG, my $cfg, -s(CFG));
close(CFG);
eval($cfg);
warn "Initializing plugins...\n";
foreach my $name (keys(%plugins))
{
my $obj;
$plugins{$name}{shared} = \%shared_data;
$plugins{$name}{files} = $data_root.$name.'/';
mkdir ($data_root.$name.'/') unless (-d($data_root.$name.'/'));
my $plugin_command = 'use HBCPlugins::'.$name.';$obj = HBCPlugins::'.$name.'->new($plugins{\''.$name.'\'});';
eval $plugin_command;
$shared_data{$obj->{label}} = $obj;
}
warn "Initialization complete.\n\n";
until (6 == 9) # Infinite loop, a serpent biting it's own tail.
{
my $ra_job_list = $shared_data{job_list};
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(@{$ra_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 to 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};
}
}
else # If it is not time yet, save it for later
{
push(@kept_jobs , $job)
}
}
push (@{$ra_job_list} , @kept_jobs); # Keep jobs that are still pending
}
RenameChecker:
package HBCPlugins::RenameChecker;
use Encode;
use MediaWiki;
use strict;
use Data::Dumper;
use URI::Escape;
our $self;
sub new
{
shift;
$self = shift;
bless($self);
warn "RenameChecker active.\n";
my(@pages) =
(
'Wikipedia:Changing username/Usurpations',
'Wikipedia:Changing username'
);
my $timing = 0;
&{$self->{shared}{add_job}}(\&login,0);
&{$self->{shared}{add_job}}(\&contact_LogWatcher_plugin, 0);
&{$self->{shared}{add_job}}([\&contact_irc_plugin,\@pages] , 0);
foreach my $page (@pages)
{
&{$self->{shared}{add_job}}([\&parse_page,undef,$page], $timing);
$timing += 30;
}
return $self;
}
sub login
{
warn "Connecting to Wikipedia...\n";
my $c = MediaWiki->new;
$c->setup
({
'bot' => {'user' => $self->{params}{username},'pass' => $self->{params}{password}},
'wiki' => {'host' => 'en.wikipedia.org','path' => 'w'}
}) || warn "Failed to log in\n";
my $whoami = $c->user();
warn "$whoami connected\n";
$self->{WP_obj} = $c;
&{$self->{shared}{add_job}}(\&login,3600);
}
sub contact_LogWatcher_plugin
{
$self->{LogMonitor} = $self->{shared}{$self->{params}{log_label}} || die;
$self->{LogMonitor}->add_job
(
type => 'renameuser', # The type of log to read
start_point => 'all', # Where to start reading the log from, timestamp or 'all'(for everything) or 'now'(to only log from now)
catch_up_frequency => 0, # Delay between reading while catching up to current state
regular_frequency => 36000, # Delay between reading after catching up to current state
step_size => 500 # How many entries to load per attempt. Limit of 500 for users, 5000 for bots and admins
);
}
sub contact_irc_plugin
{
my $ra_pages = shift;
$self->{IRCFeed} = $self->{shared}{$self->{params}{irc_label}} || die;
my $esc = chr(0x03);
# my $rename_pattern = ($esc.'07Special:Log/renameuser'.$esc.'14');
# $self->{IRCFeed}->add_hook
# ({
# 'check' => sub {return $_[0] =~ m|$rename_pattern|i;},
# 'callback' => sub {
# sleep 3;
# $self->{LogMonitor}->update_now('renameuser');
# warn "Rename detected, checking.\n";
# foreach my $page (@{$ra_pages})
# {
# &{$self->{shared}{add_job}}([\&parse_page,undef,$page],2);
# }
# warn "Page checks called.\n";
# },
# });
foreach my $page (@{$ra_pages})
{
my $pattern = ($esc.'07('.$page.')'.$esc.'14');
$self->{IRCFeed}->add_hook
({
'check' => sub {(($_[0] !~ m|HBC RenameClerkBot|) && ($_[0] =~ m|$pattern|) );return $1;},
'callback' => [\&parse_page,$page],
});
}
return;
}
sub parse_page
{
my $page = $_[1];
my $ra_name_history;
unless ($self->{LogMonitor}{params}{jobs}{renameuser}{current})
{
warn "Delaying 10 seconds till logs are loaded...\n";
&{$self->{shared}{add_job}}([\&parse_page,undef,$page], 10);
return;
}
warn "Loading $page\n";
my $page_obj = $self->{WP_obj}->get($page,'rw');
my $start_content = $page_obj->{'content'};
my(@lines) = split("\n", $page_obj->{'content'});
my @new_content;
my $current_name;
my $wanted_name;
my $report_count;
my $has_rename_count;
my $need_save = 0;
warn "Parsing page.\n";
my %status_table;
while (scalar(@lines))
{
my $line = shift(@lines);
if ($line =~ m/\*\s?Current (user)?name:.*\{\{User13\|(.*?)\}\}/i)
{
$current_name = $2;
$lines[0] =~ m/\*\s?(Target|Requested) (user)?name:.*\{\{(User13|Listuser)\|(.*?)\}\}/i;
$wanted_name = $4;
unless ($wanted_name)
{
$current_name = undef;
}
}
if ($lines[0] =~ m/'''Robot clerk's notes'''/) #'
{
$status_table{$current_name} = $lines[0];
$status_table{$current_name} =~ s/\s\[\[User:HBC RenameClerkBot\|HBC RenameClerkBot\]\] .*$// || die;
}
push(@new_content, $line) unless ($line =~ m/'''Robot clerk's notes'''/); #'
if ((($line =~ m/For bureaucrat use/) || ($line =~ m|\* Reason: |) || (scalar(@lines) < 1)) && $current_name)
{
my $ra_name_history = [];
$ra_name_history = find_rename_history($wanted_name, $ra_name_history) if ($wanted_name);
unless ($ra_name_history)
{
$ra_name_history = find_rename_history($current_name, $ra_name_history);
}
if ($ra_name_history)
{
foreach (@{$ra_name_history}) {$_ = "'''".$_."'''" if ($_ =~ m/\|$current_name\]/);}
my $rename_string = join(' ← ', @{$ra_name_history});
my $addition = "*'''Robot clerk's notes''': Rename history of \"\[\[User:$current_name|$current_name\]\]\": \"".$rename_string."\"";
# warn "\n\n$addition\n\n";
push(@new_content, $addition.' ~~~~');
if ($addition ne $status_table{$current_name})
{
$need_save = 1;
$has_rename_count++;
$report_count++;
}
}
else
{
my $addition = "*'''Robot clerk's notes''': \[\[User:$current_name|$current_name\]\] does not have any history of being renamed in the logs";
push(@new_content, $addition.' ~~~~');
if ($addition ne $status_table{$current_name})
{
$need_save = 1;
$report_count++;
}
}
$current_name = undef;
}
}
my $new_content = join("\n", @new_content);
unless ($need_save)
{
warn "Don't need change\n";
return;
}
$has_rename_count ||= 0;
$report_count ||= 0;
$page_obj->{'content'} = $new_content;
$page_obj->{'summary'} = "(Testing) Updating rename history on $report_count user".(($report_count != 1) ? ('s') : ('')).", $has_rename_count user".(($has_rename_count != 1) ? ('s') : (''))." renamed.";
warn "saving...\n";
warn $page_obj->save();
# warn $page_obj->{'summary'};
# warn $page_obj->{'content'};
warn "I have saved $page\n";
}
sub find_rename_history
{
my $name = shift;
my $ra_name_history = shift;
foreach my $check (@{$ra_name_history})
{
return $ra_name_history if ($check =~ m/\|$name\]\]/);
}
# warn "Adding: $name\n";
my $name_string = encode_utf8($name);
push(@{$ra_name_history}, "\[\[User:$name_string|$name_string\]\]");
my $ra_logs = $self->{LogMonitor}{params}{jobs}{renameuser}{log};
foreach my $rh_log (@{$ra_logs})
{
${$rh_log}{comment} =~ m/\[\[User:(.*?)\|.*?\]\].*\[\[User:(.*?)\|.*\]\]/;
my $old_name = $1;
my $new_name = $2;
if ($name eq $new_name)
{
find_rename_history($old_name, $ra_name_history);
}
}
if (@{$ra_name_history} > 1)
{
return $ra_name_history;
}
else
{
return undef;
}
}
1;
LogMonitor <syntaxhighlight lang=perl>
package HBCPlugins::LogMonitor; use strict; use XML::Simple; use Data::Dumper; use URI::Escape; use LWP::UserAgent; our $self; sub new { shift; $self = shift; bless($self); $self->{params}{jobs} = {}; $self->{UA} = my $ua = LWP::UserAgent->new('agent' => 'LogMonitor .0001b'); warn "LogMonitor active.\n"; return $self; } sub add_job { my $self = shift; my %params = @_; my $type = $params{type}; $self->{params}{jobs}{$type} = \%params; $self->{params}{jobs}{$type}{offset} = ((lc($params{start_point}) eq 'all') ? (0) : ($params{start_point})); $self->{params}{jobs}{$type}{log} = []; $self->{params}{jobs}{$type}{step_size} ||= 250; $self->{params}{jobs}{$type}{current} = 0; warn "Set initial offset for $type to ".$self->{params}{jobs}{$type}{offset}."\n\n"; &{$self->{shared}{add_job}}([\&handle_jobs,$type],0); } sub update_now { my $self = shift; my $type = shift; warn "Forcing manual update for '$type' log.\n"; &{$self->{shared}{add_job}}([\&handle_jobs,$type],0); } sub handle_jobs { my $type = shift; my $url_template = 'http://en.wikipedia.org/w/api.php?action=query&format=xml&list=logevents&letype=<TYPE>&lelimit=<GRAB><OFFSET>& ledir=newer'; my $url = $url_template; $self->{params}{jobs}{$type}{offset} ||= 0; warn "Reading up to ".$self->{params}{jobs}{$type}{step_size}." log entries from $type starting at: ".$self->{params}{jobs}{$type} {offset}."\n"; $url =~ s|<TYPE>|$type|; $url =~ s|<GRAB>|$self->{params}{jobs}{$type}{step_size}|; my $offset_line = ('&lestart='.$self->{params}{jobs}{$type}{offset}); if ($self->{params}{jobs}{$type}{offset}){$url =~ s|<OFFSET>|$offset_line|} else {$url =~ s|<OFFSET>||} my $rh_xml = XMLin($self->{UA}->get($url)->content()); my $ra_renames = ${$rh_xml}{query}{logevents}{item}; ($ra_renames = [$ra_renames]) if (ref($ra_renames) eq 'HASH'); shift(@{$ra_renames}) if ($self->{params}{jobs}{$type}{offset} > 0); push(@{$self->{params}{jobs}{$type}{log}}, @{$ra_renames}); @{$self->{params}{jobs}{$type}{log}} = sort {return ${$a}{timestamp} <=> ${$b}{timestamp}} (@{$self->{params}{jobs}{$type}{log}}); $self->{params}{jobs}{$type}{offset} = ${${$self->{params}{jobs}{$type}{log}}[scalar(@{$self->{params}{jobs}{$type}{log}}) - 1]}{'timestamp'}; unless (scalar(@{$ra_renames}) < ($self->{params}{jobs}{$type}{step_size}-1)) # Unless we got less than what we asked for, ask again using the last timestamp as an offset { &{$self->{shared}{add_job}}([\&handle_jobs,$type],$self->{params}{jobs}{$type}{catch_up_frequency}); $self->{params}{jobs}{$type}{current} = 0; } else { &{$self->{shared}{add_job}}([\&handle_jobs,$type],$self->{params}{jobs}{$type}{regular_frequency}); $self->{params}{jobs}{$type}{current} = 1; } warn "Added ".scalar(@{$ra_renames})." log entries on this pass.\n"; warn "Current total of: ".scalar(@{$self->{params}{jobs}{$type}{log}})."\n"; warn ((($self->{params}{jobs}{$type}{current}) ? ('This is current') : ('This is not current'))."\n\n"); }
1;