Add centralized configuration registry module

Allows changing of bot configuration values without needing to restart
bot instance or needing to edit pbot.pl script.

Registry will initially be populated with default values from pbot.pl,
but if a registry file exists then the registry values will take
precedence over the pbot.pl values. For instance, if you regset the
bot trigger to '%' then the trigger will be '%' even if pbot.pl has '!'
or something else explicitly set.

Some registry items can have trigger hooks associated with them.  For
instance, the irc->botnick registry entry has a change_botnick_trigger
associated with it which changes the IRC nick on the server when a new
value is set via regset/regadd.

Tons of other fixes and improvements throughout.
This commit is contained in:
Pragmatic Software 2014-05-17 20:08:19 +00:00
parent d8d26b1cea
commit d955bfa06c
33 changed files with 615 additions and 307 deletions

View File

@ -14,9 +14,6 @@ use strict;
use feature 'switch';
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use PBot::DualIndexHashObject;
use PBot::LagChecker;
@ -45,14 +42,10 @@ sub initialize {
$self->{NICKSERV_VALIDATED} = (1<<0);
$self->{NEEDS_CHECKBAN} = (1<<1);
$self->{ENTER_ABUSE_MAX_LINES} = 4;
$self->{ENTER_ABUSE_MAX_OFFENSES} = 3;
$self->{ENTER_ABUSE_MAX_SECONDS} = 20;
$self->{channels} = {}; # per-channel statistics, e.g. for optimized tracking of last spoken nick for enter-abuse detection, etc
$self->{channels} = {}; # per-channel statistics, e.g. for optimized tracking of last spoken nick for enter-abuse detection, etc
$self->{nickflood} = {}; # statistics to track nickchange flooding
my $filename = delete $conf{banwhitelist_file} // $self->{pbot}->{data_dir} . '/ban_whitelist';
my $filename = delete $conf{banwhitelist_file} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/ban_whitelist';
$self->{ban_whitelist} = PBot::DualIndexHashObject->new(name => 'BanWhitelist', filename => $filename);
$self->{ban_whitelist}->load;
@ -212,7 +205,7 @@ sub check_flood {
}
# do not do flood processing for bot messages
if($nick eq $self->{pbot}->botnick) {
if($nick eq $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
$self->{channels}->{$channel}->{last_spoken_nick} = $nick;
return;
}
@ -229,9 +222,9 @@ sub check_flood {
}
}
if($max_messages > $self->{pbot}->{MAX_NICK_MESSAGES}) {
$self->{pbot}->logger->log("Warning: max_messages greater than MAX_NICK_MESSAGES; truncating.\n");
$max_messages = $self->{pbot}->{MAX_NICK_MESSAGES};
if($max_messages > $self->{pbot}->{registry}->get_value('messagehistory', 'max_messages')) {
$self->{pbot}->logger->log("Warning: max_messages greater than max_messages limit; truncating.\n");
$max_messages = $self->{pbot}->{registry}->get_value('messagehistory', 'max_messages');
}
# check for ban evasion if channel begins with # (not private message) and hasn't yet been validated against ban evasion
@ -257,11 +250,15 @@ sub check_flood {
if(defined $self->{channels}->{$channel}->{last_spoken_nick} and $nick eq $self->{channels}->{$channel}->{last_spoken_nick}) {
my $messages = $self->{pbot}->{messagehistory}->{database}->get_recent_messages($account, $channel, 2, $self->{pbot}->{messagehistory}->{MSG_CHAT});
if($messages->[1]->{timestamp} - $messages->[0]->{timestamp} <= $self->{ENTER_ABUSE_MAX_SECONDS}) {
if(++$channel_data->{enter_abuse} >= $self->{ENTER_ABUSE_MAX_LINES} - 1) {
$channel_data->{enter_abuse} = $self->{ENTER_ABUSE_MAX_LINES} / 2 - 1;
if(++$channel_data->{enter_abuses} >= $self->{ENTER_ABUSE_MAX_OFFENSES}) {
my $offenses = $channel_data->{enter_abuses} - $self->{ENTER_ABUSE_MAX_OFFENSES} + 1;
my $enter_abuse_max_lines = $self->{pbot}->{registry}->get_value('antiflood', 'enter_abuse_max_lines');
my $enter_abuse_max_seconds = $self->{pbot}->{registry}->get_value('antiflood', 'enter_abuse_max_seconds');
my $enter_abuse_max_offenses = $self->{pbot}->{registry}->get_value('antiflood', 'enter_abuse_max_offenses');
if($messages->[1]->{timestamp} - $messages->[0]->{timestamp} <= $enter_abuse_max_seconds) {
if(++$channel_data->{enter_abuse} >= $enter_abuse_max_lines - 1) {
$channel_data->{enter_abuse} = $enter_abuse_max_lines / 2 - 1;
if(++$channel_data->{enter_abuses} >= $enter_abuse_max_offenses) {
my $offenses = $channel_data->{enter_abuses} - $enter_abuse_max_offenses + 1;
my $ban_length = $offenses ** $offenses * $offenses * 30;
$self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $ban_length);
$ban_length = duration($ban_length);
@ -276,7 +273,7 @@ sub check_flood {
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
} else {
if($channel_data->{enter_abuse} > 0) {
#$self->{pbot}->logger->log("$nick $channel more than $self->{ENTER_ABUSE_MAX_SECONDS} seconds since last message, enter abuse counter reset\n");
#$self->{pbot}->logger->log("$nick $channel more than $enter_abuse_max_seconds seconds since last message, enter abuse counter reset\n");
$channel_data->{enter_abuse} = 0;
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
}
@ -396,7 +393,7 @@ sub unbanme {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $channel = lc $arguments;
if(not defined $arguments or not defined $channel) {
if(not $arguments or not $channel) {
return "/msg $nick Usage: unbanme <channel>";
}

View File

@ -11,9 +11,6 @@ package PBot::BanTracker;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Time::HiRes qw/gettimeofday/;
use Time::Duration;
use Data::Dumper;

View File

@ -8,9 +8,6 @@ package PBot::BotAdminCommands;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Carp ();
sub new {

View File

@ -8,9 +8,6 @@ package PBot::BotAdmins;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use PBot::DualIndexHashObject;
use Carp ();
@ -58,7 +55,7 @@ sub initialize {
sub add_admin {
my $self = shift;
my ($name, $channel, $hostmask, $level, $password) = @_;
my ($name, $channel, $hostmask, $level, $password, $dont_save) = @_;
$channel = lc $channel;
$hostmask = lc $hostmask;
@ -69,7 +66,7 @@ sub add_admin {
$self->{pbot}->logger->log("Adding new level $level admin: [$name] [$hostmask] for channel [$channel]\n");
$self->save_admins;
$self->save_admins unless $dont_save;
}
sub remove_admin {
@ -144,7 +141,7 @@ sub export_admins {
sub find_admin {
my ($self, $from, $hostmask) = @_;
$from = $self->{pbot}->botnick if not defined $from;
$from = $self->{pbot}->{registry}->get_value('irc', 'botnick') if not defined $from;
$hostmask = '.*' if not defined $hostmask;
my $result = eval {

View File

@ -8,9 +8,6 @@ package PBot::ChanOpCommands;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Carp ();
sub new {
@ -62,7 +59,8 @@ sub ban_user {
$length = 60 * 60; # one hour
}
return "" if $target =~ /\Q$self->{pbot}->botnick\E/i;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
return "" if $target =~ /\Q$botnick\E/i;
$self->{pbot}->chanops->ban_user_timed($target, $from, $length);
return "/msg $nick $target banned in $from for $length seconds";

View File

@ -8,9 +8,6 @@ package PBot::ChanOps;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Time::HiRes qw(gettimeofday);
sub new {
@ -34,7 +31,10 @@ sub initialize {
}
$self->{pbot} = $pbot;
$self->{unban_timeout} = PBot::DualIndexHashObject->new(pbot => $pbot, name => 'Unban Timeouts', filename => "$pbot->{data_dir}/unban_timeouts");
$self->{unban_timeout} = PBot::DualIndexHashObject->new(pbot => $pbot, name => 'Unban Timeouts', filename => $pbot->{registry}->get_value('general', 'data_dir') . '/unban_timeouts');
$self->{unban_timeout}->load;
$self->{op_commands} = {};
$self->{is_opped} = {};
@ -57,7 +57,7 @@ sub gain_ops {
sub lose_ops {
my $self = shift;
my $channel = shift;
$self->{pbot}->conn->privmsg("chanserv", "op $channel -" . $self->{pbot}->botnick);
$self->{pbot}->conn->privmsg("chanserv", "op $channel -" . $self->{pbot}->{registry}->get_value('irc', 'botnick'));
}
sub add_op_command {
@ -68,6 +68,7 @@ sub add_op_command {
sub perform_op_commands {
my $self = shift;
my $channel = shift;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
$self->{pbot}->logger->log("Performing op commands...\n");
while(my $command = shift @{ $self->{op_commands}->{$channel} }) {
@ -75,7 +76,7 @@ sub perform_op_commands {
$self->{pbot}->conn->mode($1, $2);
$self->{pbot}->logger->log(" executing mode $1 $2\n");
} elsif($command =~ /^kick (.*?) (.*?) (.*)/i) {
$self->{pbot}->conn->kick($1, $2, $3) unless $1 =~ /\Q$self->{pbot}->botnick\E/i;
$self->{pbot}->conn->kick($1, $2, $3) unless $1 =~ /\Q$botnick\E/i;
$self->{pbot}->logger->log(" executing kick on $1 $2 $3\n");
}
}

View File

@ -8,9 +8,6 @@ package PBot::Channels;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Carp ();
use PBot::HashObject;
@ -29,17 +26,16 @@ sub new {
sub initialize {
my ($self, %conf) = @_;
my $pbot = delete $conf{pbot} // Carp::croak("Missing pbot reference to Channels");
my $filename = delete $conf{filename};
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to Channels");
$self->{pbot} = $pbot;
$self->{channels} = PBot::HashObject->new(pbot => $pbot, name => 'Channels', filename => $filename);
$self->{channels} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Channels', filename => delete $conf{filename});
$self->load_channels;
$pbot->commands->register(sub { $self->set(@_) }, "chanset", 40);
$pbot->commands->register(sub { $self->unset(@_) }, "chanunset", 40);
$pbot->commands->register(sub { $self->add(@_) }, "chanadd", 40);
$pbot->commands->register(sub { $self->remove(@_) }, "chanrem", 40);
$pbot->commands->register(sub { $self->list(@_) }, "chanlist", 10);
$self->{pbot}->commands->register(sub { $self->set(@_) }, "chanset", 40);
$self->{pbot}->commands->register(sub { $self->unset(@_) }, "chanunset", 40);
$self->{pbot}->commands->register(sub { $self->add(@_) }, "chanadd", 40);
$self->{pbot}->commands->register(sub { $self->remove(@_) }, "chanrem", 40);
$self->{pbot}->commands->register(sub { $self->list(@_) }, "chanlist", 10);
}
sub set {

View File

@ -13,9 +13,6 @@ use strict;
use base 'PBot::Registerable';
use vars qw($VERSION);
$VERSION = '1.0.0';
use Carp ();
sub new {

View File

@ -9,9 +9,6 @@ package PBot::DualIndexHashObject;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = "1.0";
use Text::Levenshtein qw(fastdistance);
use Carp ();
@ -30,18 +27,9 @@ sub new {
sub initialize {
my ($self, %conf) = @_;
my $name = delete $conf{name};
if(not defined $name) {
$name = "dual index hash object";
}
my $filename = delete $conf{filename};
if(not defined $filename) {
Carp::carp("Missing filename to DualIndexHashObject, will not be able to save to or load from file.");
}
$self->{name} = $name;
$self->{filename} = $filename;
$self->{name} = delete $conf{name} // 'Dual Index hash object';
$self->{filename} = delete $conf{filename} // Carp::carp("Missing filename to DualIndexHashObject, will not be able to save to or load from file.");
$self->{ignore_duplicates} = delete $conf{ignore_duplicates} // 0;
$self->{hash} = {};
}
@ -50,7 +38,7 @@ sub load_hash_add {
my ($self, $primary_index_key, $secondary_index_key, $hash, $i, $filename) = @_;
if(defined $hash) {
if(exists $self->hash->{$primary_index_key}->{$secondary_index_key}) {
if(not $self->{ignore_duplicates} and exists $self->hash->{$primary_index_key}->{$secondary_index_key}) {
if($i) {
Carp::croak "Duplicate secondary_index_key '$secondary_index_key' found in $filename around line $i\n";
} else {
@ -67,10 +55,9 @@ sub load_hash_add {
}
sub load {
my $self = shift;
my $filename;
my ($self, $filename) = @_;
if(@_) { $filename = shift; } else { $filename = $self->filename; }
$filename = $self->filename if not defined $filename;
if(not defined $filename) {
Carp::carp "No $self->{name} filename specified -- skipping loading from file";
@ -93,19 +80,13 @@ sub load {
if($line =~ /^\[(.*)\]$/) {
$primary_index_key = $1;
if(exists $self->hash->{$primary_index_key} and $primary_index_key ne '.*') {
Carp::croak "Duplicate primary_index_key '$primary_index_key' at line $i of $filename\n";
}
$self->hash->{$primary_index_key} = {};
next;
}
if($line =~ /^<(.*)>$/) {
$secondary_index_key = $1;
if(exists $self->hash->{$primary_index_key}->{$secondary_index_key}) {
if(not $self->{ignore_duplicates} and exists $self->hash->{$primary_index_key}->{$secondary_index_key}) {
Carp::croak "Duplicate secondary_index_key '$secondary_index_key' at line $i of $filename\n";
}
@ -237,12 +218,12 @@ sub levenshtein_matches {
}
sub set {
my ($self, $primary_index_key, $secondary_index_key, $key, $value) = @_;
my ($self, $primary_index_key, $secondary_index_key, $key, $value, $dont_save) = @_;
my $primary = $self->find_index($primary_index_key);
if(not $primary) {
my $result = "No such $self->{name} object group '$primary_index_key'; similiar matches: ";
my $result = "No such $self->{name} object [$primary_index_key]; similiar matches: ";
$result .= $self->levenshtein_matches($primary_index_key);
return $result;
}
@ -250,13 +231,13 @@ sub set {
my $secondary = $self->find_index($primary, $secondary_index_key);
if(not $secondary) {
my $result = "No such $self->{name} object '$secondary_index_key'; similiar matches: ";
my $result = "No such $self->{name} object [$primary_index_key] $secondary_index_key; similiar matches: ";
$result .= $self->levenshtein_matches($primary, $secondary_index_key);
return $result;
}
if(not defined $key) {
my $result = "[$self->{name}] (" . ($primary eq '.*' ? 'global' : $primary) . ") $secondary keys: ";
my $result = "[" . ($primary eq '.*' ? 'global' : $primary) . "] $secondary keys: ";
my $comma = '';
foreach my $key (sort keys %{ $self->hash->{$primary}->{$secondary} }) {
$result .= $comma . "$key => " . $self->hash->{$primary}->{$secondary}->{$key};
@ -270,11 +251,11 @@ sub set {
$value = $self->hash->{$primary}->{$secondary}->{$key};
} else {
$self->hash->{$primary}->{$secondary}->{$key} = $value;
$self->save();
$self->save unless $dont_save;
}
$primary = 'global' if $primary eq '.*';
return "[$self->{name}] ($primary) $secondary: '$key' " . (defined $value ? "set to '$value'" : "is not set.");
return "[$primary] $secondary: '$key' " . (defined $value ? "set to '$value'" : "is not set.");
}
sub unset {

View File

@ -8,9 +8,6 @@ package PBot::FactoidCommands;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Carp ();
use Time::Duration;
use Time::HiRes qw(gettimeofday);
@ -87,7 +84,7 @@ sub call_factoid {
my ($chan, $keyword, $args) = split / /, $arguments, 3;
if(not defined $chan or not defined $keyword) {
return "Usage: !fact <channel> <keyword> [arguments]";
return "Usage: fact <channel> <keyword> [arguments]";
}
my ($channel, $trigger) = $self->{pbot}->factoids->find_factoid($chan, $keyword, $args, 1);
@ -105,7 +102,7 @@ sub factset {
my ($channel, $trigger, $key, $value) = split / /, $arguments, 4 if defined $arguments;
if(not defined $channel or not defined $trigger) {
return "Usage: factset <channel> <factoid> [key <value>]"
return "Usage: factset <channel> <factoid> [key [value]]";
}
my $admininfo = $self->{pbot}->admins->loggedin($from, "$nick!$user\@$host");
@ -194,13 +191,14 @@ sub factunset {
sub list {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $botnick = $self->{pbot}->botnick;
my $text;
if(not defined $arguments) {
return "/msg $nick Usage: list <modules|factoids|commands|admins>";
}
# TODO - update this to use new MessageHistory API
=cut
if($arguments =~/^messages\s+(.*)$/) {
my ($mask_search, $channel_search, $text_search) = split / /, $1;
@ -215,12 +213,12 @@ sub list {
$nickserv = $self->{pbot}->antiflood->message_history->{$history_mask}->{nickserv_account} if exists $self->{pbot}->antiflood->message_history->{$history_mask}->{nickserv_account};
if($history_mask =~ m/$mask_search/i) {
my $bot_trigger = $self->{pbot}->{registry}->get_value('general', 'trigger');
foreach my $history_channel (keys %{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels} }) {
if($history_channel =~ m/$channel_search/i) {
my @messages = @{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{messages} };
for(my $i = 0; $i <= $#messages; $i++) {
next if $messages[$i]->{msg} =~ /^\Q$self->{pbot}->{trigger}\E?login/; # don't reveal login passwords
next if $messages[$i]->{msg} =~ /^\Q$bot_trigger\E?login/; # don't reveal login passwords
print "$history_mask, $history_channel\n";
print "joinwatch: ", $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{join_watch}, "\n";
@ -264,6 +262,7 @@ sub list {
$self->{pbot}->logger->log($text);
return "Messages:\n\n$text";
}
=cut
if($arguments =~ /^modules$/i) {
$from = '.*' if not defined $from or $from !~ /^#/;

View File

@ -8,9 +8,6 @@ package PBot::FactoidModuleLauncher;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use POSIX qw(WNOHANG); # for children process reaping
use Carp ();
use Text::Balanced qw(extract_delimited);
@ -55,7 +52,7 @@ sub execute_module {
}
my $module = $self->{pbot}->factoids->factoids->hash->{$channel}->{$trigger}->{action};
my $module_dir = $self->{pbot}->module_dir;
my $module_dir = $self->{pbot}->{registry}->get_value('general', 'module_dir');
$self->{pbot}->logger->log("(" . (defined $from ? $from : "(undef)") . "): $nick!$user\@$host: Executing module $module $arguments\n");
@ -187,8 +184,8 @@ sub module_pipe_reader {
my ($self, $buf) = @_;
my ($channel, $text) = split / /, $buf, 2;
return if not defined $text or not length $text;
$text = $self->{pbot}->interpreter->truncate_result($channel, $self->{pbot}->{botnick}, 'undef', $text, $text, 0);
$self->{pbot}->antiflood->check_flood($channel, $self->{pbot}->{botnick}, $self->{pbot}->{username}, 'localhost', $text, 0, 0, 0);
$text = $self->{pbot}->interpreter->truncate_result($channel, $self->{pbot}->{registry}->get_value('irc', 'botnick'), 'undef', $text, $text, 0);
$self->{pbot}->antiflood->check_flood($channel, $self->{pbot}->{registry}->get_value('irc', 'botnick'), $self->{pbot}->{registry}->get_value('irc', 'username'), 'localhost', $text, 0, 0, 0);
}
1;

View File

@ -8,14 +8,12 @@ package PBot::Factoids;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use HTML::Entities;
use Time::HiRes qw(gettimeofday);
use Carp ();
use POSIX qw(strftime);
use PBot::PBot qw($VERSION);
use PBot::FactoidModuleLauncher;
use PBot::DualIndexHashObject;
@ -48,6 +46,9 @@ sub initialize {
$self->{factoidmodulelauncher} = PBot::FactoidModuleLauncher->new(pbot => $pbot);
$self->{pbot}->{atexit}->register(sub { $self->save_factoids; return; });
$self->load_factoids;
$self->add_factoid('text', '.*', $self->{pbot}->{registry}->get_value('irc', 'botnick'), 'version', "/say $VERSION", 1);
}
sub load_factoids {
@ -80,7 +81,7 @@ sub save_factoids {
sub add_factoid {
my $self = shift;
my ($type, $channel, $owner, $trigger, $action) = @_;
my ($type, $channel, $owner, $trigger, $action, $dont_save) = @_;
$type = lc $type;
$channel = lc $channel;
@ -94,7 +95,7 @@ sub add_factoid {
$self->factoids->hash->{$channel}->{$trigger}->{ref_user} = "nobody";
$self->factoids->hash->{$channel}->{$trigger}->{rate_limit} = 15;
$self->save_factoids;
$self->save_factoids unless $dont_save;
}
sub remove_factoid {
@ -104,6 +105,11 @@ sub remove_factoid {
$channel = lc $channel;
delete $self->factoids->hash->{$channel}->{$trigger};
if(not scalar keys $self->factoids->hash->{$channel}) {
delete $self->factoids->hash->{$channel};
}
$self->save_factoids;
}
@ -449,7 +455,7 @@ sub interpreter {
if(defined $tonick) { # !tell foo about bar
$self->{pbot}->logger->log("($from): $nick!$user\@$host) sent to $tonick\n");
my $botnick = $self->{pbot}->botnick;
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
# get rid of original caller's nick
$result =~ s/^\/([^ ]+) \Q$nick\E:\s+/\/$1 /;

View File

@ -9,9 +9,6 @@ package PBot::HashObject;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Text::Levenshtein qw(fastdistance);
use Carp ();

View File

@ -8,9 +8,6 @@ package PBot::IRCHandlers;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Carp();
use Time::HiRes qw(gettimeofday);
@ -70,15 +67,16 @@ sub on_public {
my $host = $event->host;
my $text = $event->{args}[0];
$self->pbot->interpreter->process_line($from, $nick, $user, $host, $text);
$self->{pbot}->interpreter->process_line($from, $nick, $user, $host, $text);
}
sub on_msg {
my ($self, $conn, $event) = @_;
my ($nick, $host) = ($event->nick, $event->host);
my $text = $event->{args}[0];
my $bot_trigger = $self->{pbot}->{registry}->get_value('general', 'trigger');
$text =~ s/^\Q$self->{pbot}->{trigger}\E?(.*)/$self->{pbot}->{trigger}$1/;
$text =~ s/^\Q$bot_trigger\E?(.*)/$bot_trigger$1/;
$event->{to}[0] = $nick;
$event->{args}[0] = $text;
$self->on_public($conn, $event);
@ -93,7 +91,7 @@ sub on_notice {
if($nick eq "NickServ" && $text =~ m/This nickname is registered/) {
$self->{pbot}->logger->log("Identifying with NickServ . . .\n");
$conn->privmsg("nickserv", "identify " . $self->pbot->identify_password);
$conn->privmsg("nickserv", "identify " . $self->{pbot}->{registry}->get_value('irc', 'identify_password'));
}
if($nick eq "NickServ" && $text =~ m/You are now identified/) {
@ -143,7 +141,7 @@ sub on_mode {
$self->{pbot}->bantracker->track_mode("$nick!$user\@$host", $mode, $target, $channel);
}
if(defined $target && $target eq $self->{pbot}->botnick) { # bot targeted
if(defined $target && $target eq $self->{pbot}->{registry}->get_value('irc', 'botnick')) { # bot targeted
if($mode eq "+o") {
$self->{pbot}->logger->log("$nick opped me in $channel\n");
$self->{pbot}->chanops->{is_opped}->{$channel}{timeout} = gettimeofday + 300; # 5 minutes
@ -165,7 +163,7 @@ sub on_mode {
$self->{pbot}->chanops->{unban_timeout}->save;
}
}
elsif($mode eq "+e" && $channel eq $self->{pbot}->botnick) {
elsif($mode eq "+e" && $channel eq $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
foreach my $chan (keys %{ $self->{pbot}->channels->channels->hash }) {
if($self->channels->channels->hash->{$chan}{enabled}) {
$self->{pbot}->logger->log("Joining channel: $chan\n");
@ -185,7 +183,7 @@ sub on_join {
my $message_account = $self->{pbot}->{messagehistory}->get_message_account($nick, $user, $host);
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, "JOIN", $self->{pbot}->{messagehistory}->{MSG_JOIN});
$self->{pbot}->antiflood->check_flood($channel, $nick, $user, $host, "JOIN", 4, 60 * 30, $self->{pbot}->{messagehistory}->{MSG_JOIN});
$self->{pbot}->antiflood->check_flood($channel, $nick, $user, $host, "JOIN", $self->{pbot}->{registry}->get_value('antiflood', 'max_join_flood'), 60 * 30, $self->{pbot}->{messagehistory}->{MSG_JOIN});
}
sub on_kick {
@ -203,7 +201,7 @@ sub on_kick {
my $text = "KICKED by $nick!$user\@$host ($reason)";
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, $text, $self->{pbot}->{messagehistory}->{MSG_DEPARTURE});
$self->{pbot}->antiflood->check_flood($channel, $target_nick, $target_user, $target_host, $text, 4, 60 * 30, $self->{pbot}->{messagehistory}->{MSG_DEPARTURE});
$self->{pbot}->antiflood->check_flood($channel, $target_nick, $target_user, $target_host, $text, $self->{pbot}->{registry}->get_value('antiflood', 'max_join_flood'), 60 * 30, $self->{pbot}->{messagehistory}->{MSG_DEPARTURE});
}
}
@ -227,7 +225,7 @@ sub on_departure {
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, $text, $self->{pbot}->{messagehistory}->{MSG_DEPARTURE});
}
$self->{pbot}->antiflood->check_flood($channel, $nick, $user, $host, $text, 4, 60 * 30, $self->{pbot}->{messagehistory}->{MSG_DEPARTURE});
$self->{pbot}->antiflood->check_flood($channel, $nick, $user, $host, $text, $self->{pbot}->{registry}->get_value('antiflood', 'max_join_flood'), 60 * 30, $self->{pbot}->{messagehistory}->{MSG_DEPARTURE});
my $admin = $self->{pbot}->admins->find_admin($channel, "$nick!$user\@$host");
if(defined $admin and $admin->{loggedin}) {
@ -255,12 +253,7 @@ sub on_nickchange {
$self->{pbot}->{messagehistory}->{database}->devalidate_all_channels($newnick_account);
$self->{pbot}->{messagehistory}->{database}->update_hostmask_data($newnick_account, { last_seen => scalar gettimeofday });
$self->{pbot}->antiflood->check_flood("$nick!$user\@$host", $nick, $user, $host, "NICKCHANGE $newnick", 3, 60 * 30, $self->{pbot}->{messagehistory}->{MSG_NICKCHANGE});
}
sub pbot {
my $self = shift;
return $self->{pbot};
$self->{pbot}->antiflood->check_flood("$nick!$user\@$host", $nick, $user, $host, "NICKCHANGE $newnick", $self->{pbot}->{registry}->get_value('antiflood', 'max_nick_flood'), 60 * 30, $self->{pbot}->{messagehistory}->{MSG_NICKCHANGE});
}
1;

View File

@ -8,9 +8,6 @@ package PBot::IgnoreList;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Time::HiRes qw(gettimeofday);
sub new {
@ -28,20 +25,16 @@ sub new {
sub initialize {
my ($self, %conf) = @_;
my $pbot = delete $conf{pbot};
if(not defined $pbot) {
Carp::croak("Missing pbot reference to Channels");
}
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to Channels");
$self->{filename} = delete $conf{filename};
my $filename = delete $conf{filename};
$self->{pbot} = $pbot;
$self->{ignore_list} = {};
$self->{ignore_flood_counter} = {};
$self->{last_timestamp} = {};
$self->{filename} = $filename;
$pbot->timer->register(sub { $self->check_ignore_timeouts }, 10);
$self->load_ignores;
$self->{pbot}->timer->register(sub { $self->check_ignore_timeouts }, 10);
}
sub add {

View File

@ -8,9 +8,6 @@ package PBot::IgnoreListCommands;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Time::HiRes qw(gettimeofday);
use Carp ();

View File

@ -13,9 +13,6 @@ use base 'PBot::Registerable';
use LWP::UserAgent;
use Carp ();
use vars qw($VERSION);
$VERSION = '1.0.0';
sub new {
if(ref($_[1]) eq 'HASH') {
Carp::croak("Options to Interpreter should be key/value pairs, not hash reference");
@ -91,7 +88,7 @@ sub process_line {
my $has_url;
my $has_code;
my $nick_override;
my $mynick = $self->pbot->botnick;
my $mynick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
$from = lc $from if defined $from;
@ -100,7 +97,7 @@ sub process_line {
my $message_account = $pbot->{messagehistory}->get_message_account($nick, $user, $host);
$pbot->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $from, $text, $pbot->{messagehistory}->{MSG_CHAT});
$pbot->antiflood->check_flood($from, $nick, $user, $host, $text, $pbot->{MAX_FLOOD_MESSAGES}, 10, $pbot->{messagehistory}->{MSG_CHAT}) if defined $from;
$pbot->antiflood->check_flood($from, $nick, $user, $host, $text, $pbot->{registry}->get_value('antiflood', 'max_chat_flood'), 10, $pbot->{messagehistory}->{MSG_CHAT}) if defined $from;
$text =~ s/^\s+//;
$text =~ s/\s+$//;
@ -109,10 +106,12 @@ sub process_line {
my $cmd_text = $text;
$cmd_text =~ s/^\/me\s+//;
if($cmd_text =~ /^$pbot->{trigger}?\s*{\s*(.*)\s*}\s*$/) {
my $bot_trigger = $pbot->{registry}->get_value('general', 'trigger');
if($cmd_text =~ /^$bot_trigger?\s*{\s*(.*)\s*}\s*$/) {
$has_code = $1 if length $1;
$preserve_whitespace = 1;
} elsif($cmd_text =~ /^\Q$pbot->{trigger}\E(.*)$/) {
} elsif($cmd_text =~ /^\Q$bot_trigger\E(.*)$/) {
$command = $1;
} elsif($cmd_text =~ /^.?$mynick.?\s+(.*?)$/i) {
$command = $1;
@ -147,8 +146,9 @@ sub process_line {
sub truncate_result {
my ($self, $from, $nick, $text, $original_result, $result, $paste) = @_;
my $max_msg_len = $self->{pbot}->{registry}->get_value('irc', 'max_msg_len');
if(length $result > $self->{pbot}->max_msg_len) {
if(length $result > $max_msg_len) {
my $link;
if($paste) {
$link = paste_sprunge("[" . (defined $from ? $from : "stdin") . "] <$nick> $text\n\n$original_result");
@ -159,7 +159,7 @@ sub truncate_result {
my $trunc = "... [truncated; see $link for full text.]";
$self->{pbot}->logger->log("Message truncated -- pasted to $link\n") if $paste;
my $trunc_len = length $result < $self->{pbot}->max_msg_len ? length $result : $self->{pbot}->max_msg_len;
my $trunc_len = length $result < $max_msg_len ? length $result : $max_msg_len;
$result = substr($result, 0, $trunc_len);
substr($result, $trunc_len - length $trunc) = $trunc;
}
@ -169,7 +169,7 @@ sub truncate_result {
sub handle_result {
my ($self, $from, $nick, $user, $host, $text, $command, $result, $checkflood, $preserve_whitespace) = @_;
my ($pbot, $mynick) = ($self->{pbot}, $self->{pbot}->{botnick});
my ($pbot, $mynick) = ($self->{pbot}, $self->{pbot}->{registry}->get_value('irc', 'botnick'));
if(not defined $result or length $result == 0) {
return;
@ -194,10 +194,10 @@ sub handle_result {
if($result =~ s/^\/say\s+//i) {
$pbot->conn->privmsg($from, $result) if defined $from && $from !~ /\Q$mynick\E/i;
$pbot->antiflood->check_flood($from, $pbot->{botnick}, $pbot->{username}, 'localhost', $result, 0, 0, 0) if $checkflood;
$pbot->antiflood->check_flood($from, $mynick, $pbot->{registry}->get_value('irc', 'username'), 'localhost', $result, 0, 0, 0) if $checkflood;
} elsif($result =~ s/^\/me\s+//i) {
$pbot->conn->me($from, $result) if defined $from && $from !~ /\Q$mynick\E/i;
$pbot->antiflood->check_flood($from, $pbot->{botnick}, $pbot->{username}, 'localhost', '/me ' . $result, 0, 0, 0) if $checkflood;
$pbot->antiflood->check_flood($from, $mynick, $pbot->{registry}->get_value('irc', 'username'), 'localhost', '/me ' . $result, 0, 0, 0) if $checkflood;
} elsif($result =~ s/^\/msg\s+([^\s]+)\s+//i) {
my $to = $1;
if($to =~ /,/) {
@ -208,15 +208,15 @@ sub handle_result {
}
elsif($result =~ s/^\/me\s+//i) {
$pbot->conn->me($to, $result) if $to !~ /\Q$mynick\E/i;
$pbot->antiflood->check_flood($to, $pbot->{botnick}, $pbot->{username}, 'localhost', '/me ' . $result, 0, 0, 0) if $checkflood;
$pbot->antiflood->check_flood($to, $mynick, $pbot->{registry}->get_value('irc', 'username'), 'localhost', '/me ' . $result, 0, 0, 0) if $checkflood;
} else {
$result =~ s/^\/say\s+//i;
$pbot->conn->privmsg($to, $result) if $to !~ /\Q$mynick\E/i;
$pbot->antiflood->check_flood($to, $pbot->{botnick}, $pbot->{username}, 'localhost', $result, 0, 0, 0) if $checkflood;
$pbot->antiflood->check_flood($to, $mynick, $pbot->{registry}->get_value('irc', 'username'), 'localhost', $result, 0, 0, 0) if $checkflood;
}
} else {
$pbot->conn->privmsg($from, $result) if defined $from && $from !~ /\Q$mynick\E/i;
$pbot->antiflood->check_flood($from, $pbot->{botnick}, $pbot->{username}, 'localhost', $result, 0, 0, 0) if $checkflood;
$pbot->antiflood->check_flood($from, $mynick, $pbot->{registry}->get_value('irc', 'username'), 'localhost', $result, 0, 0, 0) if $checkflood;
}
$pbot->logger->log("---------------------------------------------\n");
}

View File

@ -11,9 +11,6 @@ use strict;
use feature 'switch';
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Time::HiRes qw(gettimeofday tv_interval);
use Time::Duration;
use Carp ();

View File

@ -3,9 +3,6 @@ package PBot::Logger;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = '1.0.0';
use Carp ();
sub new {

View File

@ -33,7 +33,7 @@ sub new {
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{filename} = delete $conf{filename} // $self->{pbot}->{data_dir} . '/message_history.sqlite3';
$self->{filename} = delete $conf{filename} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/message_history.sqlite3';
$self->{database} = PBot::MessageHistory_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database}->begin();

View File

@ -27,7 +27,7 @@ sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference in " . __FILE__);
$self->{filename} = delete $conf{filename} // $self->{pbot}->{data_dir} . '/message_history.sqlite3';
$self->{filename} = delete $conf{filename} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/message_history.sqlite3';
$self->{pbot}->timer->register(sub { $self->commit_message_history }, 5);
$self->{new_entries} = 0;
@ -433,8 +433,10 @@ sub recall_message_by_count {
$self->{pbot}->logger->log($@) if $@;
if(defined $ignore_command) {
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
my $bot_trigger = $self->{pbot}->{registry}->get_value('general', 'trigger');
foreach my $message (@$messages) {
next if $message->{msg} =~ m/^$self->{pbot}->{botnick}. $ignore_command/ or $message->{msg} =~ m/^$self->{pbot}->{trigger}$ignore_command/;
next if $message->{msg} =~ m/^$botnick. $ignore_command/ or $message->{msg} =~ m/^$bot_trigger$ignore_command/;
return $message;
}
return undef;
@ -474,8 +476,10 @@ sub recall_message_by_text {
$self->{pbot}->logger->log($@) if $@;
if(defined $ignore_command) {
my $bot_trigger = $self->{pbot}->{registry}->get_value('general', 'trigger');
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
foreach my $message (@$messages) {
next if $message->{msg} =~ m/^$self->{pbot}->{botnick}. $ignore_command/ or $message->{msg} =~ m/^$self->{pbot}->{trigger}$ignore_command/;
next if $message->{msg} =~ m/^$botnick. $ignore_command/ or $message->{msg} =~ m/^$bot_trigger$ignore_command/;
return $message;
}
return undef;

View File

@ -12,8 +12,14 @@ use warnings;
use PBot::VERSION;
use vars qw($VERSION);
$VERSION = PBot::VERSION::BUILD_NAME . " revision " . PBot::VERSION::BUILD_REVISION . " " . PBot::VERSION::BUILD_DATE;
BEGIN {
use Exporter;
our @ISA = 'Exporter';
our @EXPORT = qw($VERSION);
our $VERSION = PBot::VERSION::BUILD_NAME . " revision " . PBot::VERSION::BUILD_REVISION . " " . PBot::VERSION::BUILD_DATE;
print "PBot version $VERSION\n";
}
# unbuffer stdout
STDOUT->autoflush(1);
@ -21,6 +27,8 @@ STDOUT->autoflush(1);
use Carp ();
use PBot::Logger;
use PBot::Registry;
use PBot::SelectHandler;
use PBot::StdinReader;
@ -69,73 +77,70 @@ sub new {
sub initialize {
my ($self, %conf) = @_;
my $log_file = delete $conf{log_file};
# logger created first to allow other modules to log things
$self->{logger} = PBot::Logger->new(log_file => delete $conf{log_file});
$self->{config_dir} = delete $conf{config_dir} // "$ENV{HOME}/pbot/config";
$self->{data_dir} = delete $conf{data_dir} // "$ENV{HOME}/pbot/data";
$self->{module_dir} = delete $conf{module_dir} // "$ENV{HOME}/pbot/modules";
$self->{atexit} = PBot::Registerable->new();
$self->{timer} = PBot::Timer->new(timeout => 10);
$self->{commands} = PBot::Commands->new(pbot => $self);
$self->{ircserver} = delete $conf{ircserver} // "irc.freenode.net";
$self->{port} = delete $conf{port} // 6667;
$self->{SSL} = delete $conf{SSL} // 0;
$self->{SSL_ca_file} = delete $conf{SSL_ca_file} // undef;
$self->{SSL_ca_path} = delete $conf{SSL_ca_path} // undef;
$self->{botnick} = delete $conf{botnick} // "pbot3";
$self->{username} = delete $conf{username} // "pbot3";
$self->{ircname} = delete $conf{ircname} // "http://code.google.com/p/pbot2-pl/";
$self->{identify_password} = delete $conf{identify_password} // "";
my $config_dir = delete $conf{config_dir} // "$ENV{HOME}/pbot/config";
$self->{max_msg_len} = delete $conf{max_msg_len} // 425;
$self->{MAX_FLOOD_MESSAGES} = delete $conf{MAX_FLOOD_MESSAGES} // 4;
$self->{MAX_NICK_MESSAGES} = delete $conf{MAX_NICK_MESSAGES} // 32;
# registry created, but not yet loaded, to allow modules to create default values and triggers
$self->{registry} = PBot::Registry->new(pbot => $self, filename => delete $conf{registry_file} // "$config_dir/registry");
$self->{trigger} = delete $conf{trigger} // '!';
$self->{registry}->add_default('text', 'general', 'config_dir', $config_dir);
$self->{registry}->add_default('text', 'general', 'data_dir', delete $conf{data_dir} // "$ENV{HOME}/pbot/data");
$self->{registry}->add_default('text', 'general', 'module_dir', delete $conf{module_dir} // "$ENV{HOME}/pbot/modules");
$self->{registry}->add_default('text', 'general', 'trigger', delete $conf{trigger} // '!');
my $messagehistory_file = delete $conf{message_history_file};
my $channels_file = delete $conf{channels_file};
my $admins_file = delete $conf{admins_file};
my $ignorelist_file = delete $conf{ignorelist_file};
$self->{registry}->add_default('text', 'irc', 'max_msg_len', delete $conf{max_msg_len} // 425);
$self->{registry}->add_default('text', 'irc', 'ircserver', delete $conf{ircserver} // "irc.freenode.net");
$self->{registry}->add_default('text', 'irc', 'port', delete $conf{port} // 6667);
$self->{registry}->add_default('text', 'irc', 'SSL', delete $conf{SSL} // 0);
$self->{registry}->add_default('text', 'irc', 'SSL_ca_file', delete $conf{SSL_ca_file} // 'none');
$self->{registry}->set('irc', 'SSL_ca_file', 'private', 1);
$self->{registry}->add_default('text', 'irc', 'SSL_ca_path', delete $conf{SSL_ca_path} // 'none');
$self->{registry}->set('irc', 'SSL_ca_path', 'private', 1);
$self->{registry}->add_default('text', 'irc', 'botnick', delete $conf{botnick} // "pbot3");
$self->{registry}->add_default('text', 'irc', 'username', delete $conf{username} // "pbot3");
$self->{registry}->add_default('text', 'irc', 'ircname', delete $conf{ircname} // "http://code.google.com/p/pbot2-pl/");
$self->{registry}->add_default('text', 'irc', 'identify_password', delete $conf{identify_password} // "");
$self->{registry}->set('irc', 'identify_password', 'private', 1);
my $factoids_file = delete $conf{factoids_file};
my $export_factoids_path = delete $conf{export_factoids_path};
my $export_factoids_site = delete $conf{export_factoids_site};
$self->{registry}->add_trigger('irc', 'botnick', sub { $self->change_botnick_trigger(@_) });
my $quotegrabs_file = delete $conf{quotegrabs_file};
my $export_quotegrabs_path = delete $conf{export_quotegrabs_path};
my $export_quotegrabs_site = delete $conf{export_quotegrabs_site};
$self->{registry}->add_default('text', 'antiflood', 'max_join_flood', delete $conf{max_join_flood} // 4);
$self->{registry}->add_default('text', 'antiflood', 'max_chat_flood', delete $conf{max_chat_flood} // 4);
$self->{registry}->add_default('text', 'antiflood', 'max_enter_flood', delete $conf{max_enter_flood} // 4);
$self->{registry}->add_default('text', 'antiflood', 'max_nick_flood', delete $conf{max_nick_flood} // 3);
$self->{logger} = PBot::Logger->new(log_file => $log_file);
$self->{commands} = PBot::Commands->new(pbot => $self);
$self->{timer} = PBot::Timer->new(timeout => 10);
$self->{atexit} = PBot::Registerable->new();
$self->{registry}->add_default('text', 'antiflood', 'enter_abuse_max_lines', delete $conf{enter_abuse_max_lines} // 4);
$self->{registry}->add_default('text', 'antiflood', 'enter_abuse_max_seconds', delete $conf{enter_abuse_max_seconds} // 20);
$self->{registry}->add_default('text', 'antiflood', 'enter_abuse_max_offenses', delete $conf{enter_abuse_max_offenses} // 3);
$self->{registry}->add_default('text', 'messagehistory', 'max_messages', delete $conf{max_messages} // 32);
$self->{select_handler} = PBot::SelectHandler->new(pbot => $self);
$self->{stdin_reader} = PBot::StdinReader->new(pbot => $self);
$self->{admins} = PBot::BotAdmins->new(pbot => $self, filename => $admins_file);
$self->{admins} = PBot::BotAdmins->new(pbot => $self, filename => delete $conf{admins_file});
$self->admins->load_admins();
$self->admins->add_admin($self->{botnick}, '.*', "$self->{botnick}!stdin\@localhost", 60, 'admin');
$self->admins->login($self->{botnick}, "$self->{botnick}!stdin\@localhost", 'admin');
$self->{factoids} = PBot::Factoids->new(
pbot => $self,
filename => $factoids_file,
export_path => $export_factoids_path,
export_site => $export_factoids_site,
filename => delete $conf{factoids_file},
export_path => delete $conf{export_factoids_path},
export_site => delete $conf{export_factoids_site},
);
$self->factoids->load_factoids() if defined $factoids_file;
$self->factoids->add_factoid('text', '.*', $self->{botnick}, 'version', "/say $VERSION");
$self->{bantracker} = PBot::BanTracker->new(pbot => $self);
$self->{lagchecker} = PBot::LagChecker->new(pbot => $self);
$self->{messagehistory} = PBot::MessageHistory->new(pbot => $self, filename => $messagehistory_file);
$self->{messagehistory} = PBot::MessageHistory->new(pbot => $self, filename => delete $conf{messagehistory_file});
$self->{antiflood} = PBot::AntiFlood->new(pbot => $self);
$self->{ignorelist} = PBot::IgnoreList->new(pbot => $self, filename => $ignorelist_file);
$self->{ignorelist}->load_ignores() if defined $ignorelist_file;
$self->{ignorelist} = PBot::IgnoreList->new(pbot => $self, filename => delete $conf{ignorelist_file});
$self->interpreter(PBot::Interpreter->new(pbot => $self));
$self->interpreter->register(sub { return $self->commands->interpreter(@_); });
@ -148,21 +153,27 @@ sub initialize {
$self->{irc} = PBot::IRC->new();
$self->{irchandlers} = PBot::IRCHandlers->new(pbot => $self);
$self->{channels} = PBot::Channels->new(pbot => $self, filename => $channels_file);
$self->channels->load_channels() if defined $channels_file;
$self->{channels} = PBot::Channels->new(pbot => $self, filename => delete $conf{channels_file});
$self->{chanops} = PBot::ChanOps->new(pbot => $self);
$self->{chanopcmds} = PBot::ChanOpCommands->new(pbot => $self);
$self->{chanops}->{unban_timeout}->load;
$self->{quotegrabs} = PBot::Quotegrabs->new(
pbot => $self,
filename => $quotegrabs_file,
export_path => $export_quotegrabs_path,
export_site => $export_quotegrabs_site,
filename => delete $conf{quotegrabs_file},
export_path => delete $conf{export_quotegrabs_path},
export_site => delete $conf{export_quotegrabs_site},
);
# load registry entries from file to overwrite defaults
$self->{registry}->load;
# create implicit bot-admin account for bot
my $botnick = $self->{registry}->get_value('irc', 'botnick');
$self->admins->add_admin($botnick, '.*', "$botnick!stdin\@localhost", 60, 'admin', 1);
$self->admins->login($botnick, "$botnick!stdin\@localhost", 'admin');
# start timer
$self->timer->start();
}
@ -171,47 +182,47 @@ sub initialize {
sub connect {
my ($self, $server) = @_;
$server = $self->ircserver if not defined $server;
if($self->{connected}) {
# TODO: disconnect, clean-up, etc
}
$server = $self->{registry}->get_value('irc', 'ircserver') if not defined $server;
$self->logger->log("Connecting to $server ...\n");
$self->conn($self->irc->newconn(
Nick => $self->{botnick},
Username => $self->{username},
Ircname => $self->{ircname},
Nick => $self->{registry}->get_value('irc', 'botnick'),
Username => $self->{registry}->get_value('irc', 'username'),
Ircname => $self->{registry}->get_value('irc', 'ircname'),
Server => $server,
SSL => $self->{SSL},
SSL_ca_file => $self->{SSL_ca_file},
SSL_ca_path => $self->{SSL_ca_path},
Port => $self->{port}))
SSL => $self->{registry}->get_value('irc', 'SSL'),
SSL_ca_file => $self->{registry}->get_value('irc', 'SSL_ca_file'),
SSL_ca_path => $self->{registry}->get_value('irc', 'SSL_ca_path'),
Port => $self->{registry}->get_value('irc', 'port')))
or Carp::croak "$0: Can't connect to IRC server.\n";
$self->{connected} = 1;
#set up default handlers for the IRC engine
$self->conn->add_handler([ 251,252,253,254,302,255 ], sub { $self->irchandlers->on_init(@_) });
$self->conn->add_handler(376 , sub { $self->irchandlers->on_connect(@_) });
$self->conn->add_handler('disconnect' , sub { $self->irchandlers->on_disconnect(@_) });
$self->conn->add_handler('notice' , sub { $self->irchandlers->on_notice(@_) });
$self->conn->add_handler('caction' , sub { $self->irchandlers->on_action(@_) });
$self->conn->add_handler('public' , sub { $self->irchandlers->on_public(@_) });
$self->conn->add_handler('msg' , sub { $self->irchandlers->on_msg(@_) });
$self->conn->add_handler('mode' , sub { $self->irchandlers->on_mode(@_) });
$self->conn->add_handler('part' , sub { $self->irchandlers->on_departure(@_) });
$self->conn->add_handler('join' , sub { $self->irchandlers->on_join(@_) });
$self->conn->add_handler('kick' , sub { $self->irchandlers->on_kick(@_) });
$self->conn->add_handler('quit' , sub { $self->irchandlers->on_departure(@_) });
$self->conn->add_handler('nick' , sub { $self->irchandlers->on_nickchange(@_) });
$self->conn->add_handler('pong' , sub { $self->lagchecker->on_pong(@_) });
$self->conn->add_handler('whoisaccount' , sub { $self->antiflood->on_whoisaccount(@_) });
$self->conn->add_handler([ 251,252,253,254,302,255 ], sub { $self->irchandlers->on_init(@_) });
$self->conn->add_handler(376 , sub { $self->irchandlers->on_connect(@_) });
$self->conn->add_handler('disconnect' , sub { $self->irchandlers->on_disconnect(@_) });
$self->conn->add_handler('notice' , sub { $self->irchandlers->on_notice(@_) });
$self->conn->add_handler('caction' , sub { $self->irchandlers->on_action(@_) });
$self->conn->add_handler('public' , sub { $self->irchandlers->on_public(@_) });
$self->conn->add_handler('msg' , sub { $self->irchandlers->on_msg(@_) });
$self->conn->add_handler('mode' , sub { $self->irchandlers->on_mode(@_) });
$self->conn->add_handler('part' , sub { $self->irchandlers->on_departure(@_) });
$self->conn->add_handler('join' , sub { $self->irchandlers->on_join(@_) });
$self->conn->add_handler('kick' , sub { $self->irchandlers->on_kick(@_) });
$self->conn->add_handler('quit' , sub { $self->irchandlers->on_departure(@_) });
$self->conn->add_handler('nick' , sub { $self->irchandlers->on_nickchange(@_) });
$self->conn->add_handler('pong' , sub { $self->lagchecker->on_pong(@_) });
$self->conn->add_handler('whoisaccount' , sub { $self->antiflood->on_whoisaccount(@_) });
$self->conn->add_handler('banlist' , sub { $self->bantracker->on_banlist_entry(@_) });
$self->conn->add_handler('endofnames' , sub { $self->bantracker->get_banlist(@_) });
# freenode quietlist
$self->conn->add_handler(728 , sub { $self->bantracker->on_quietlist_entry(@_) });
$self->conn->add_handler('endofnames' , sub { $self->bantracker->get_banlist(@_) });
}
#main loop
@ -228,7 +239,7 @@ sub do_one_loop {
sub start {
my $self = shift;
if(not defined $self->{connected} or $self->{connected} == 0) {
if(not $self->{connected}) {
$self->connect();
}
@ -247,6 +258,14 @@ sub atexit {
$self->{atexit}->execute_all;
}
sub change_botnick_trigger {
my ($self, $section, $item, $newvalue) = @_;
if($self->{connected}) {
$self->conn->nick($newvalue);
}
}
#-----------------------------------------------------------------------------------
# Getters/Setters
#-----------------------------------------------------------------------------------
@ -310,30 +329,6 @@ sub commands {
return $self->{commands};
}
sub botnick {
my $self = shift;
if(@_) { $self->{botnick} = shift; }
return $self->{botnick};
}
sub identify_password {
my $self = shift;
if(@_) { $self->{identify_password} = shift; }
return $self->{identify_password};
}
sub max_msg_len {
my $self = shift;
if(@_) { $self->{max_msg_len} = shift; }
return $self->{max_msg_len};
}
sub module_dir {
my $self = shift;
if(@_) { $self->{module_dir} = shift; }
return $self->{module_dir};
}
sub ignorelist {
my $self = shift;
if(@_) { $self->{ignorelist} = shift; }
@ -370,10 +365,4 @@ sub chanops {
return $self->{chanops};
}
sub ircserver {
my $self = shift;
if(@_) { $self->{ircserver} = shift; }
return $self->{ircserver};
}
1;

View File

@ -8,9 +8,6 @@ package PBot::Quotegrabs;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use HTML::Entities;
use Time::Duration;
use Time::HiRes qw(gettimeofday);

View File

@ -8,9 +8,6 @@ package PBot::Quotegrabs_Hashtable;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use HTML::Entities;
use Time::Duration;
use Time::HiRes qw(gettimeofday);

View File

@ -8,9 +8,6 @@ package PBot::Quotegrabs_SQLite;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use DBI;
use Carp qw(shortmess);

View File

@ -8,9 +8,6 @@ package PBot::Registerable;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = '1.0.0';
use Carp ();
sub new {

156
PBot/Registry.pm Normal file
View File

@ -0,0 +1,156 @@
# File: Registry.pm
# Author: pragma_
#
# Purpose: Provides a centralized registry of configuration settings that can
# easily be examined and updated via set/unset commands without restarting.
package PBot::Registry;
use warnings;
use strict;
use Time::HiRes qw(gettimeofday);
use Carp ();
use PBot::DualIndexHashObject;
use PBot::RegistryCommands;
sub new {
if(ref($_[1]) eq 'HASH') {
Carp::croak("Options to " . __FILE__ . " should be item/value pairs, not hash reference");
}
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
my $filename = delete $conf{filename};
$self->{registry} = PBot::DualIndexHashObject->new(name => 'Registry', filename => $filename, ignore_duplicates => 1);
$self->{triggers} = {};
$self->{pbot}->{atexit}->register(sub { $self->save; return; });
PBot::RegistryCommands->new(pbot => $self->{pbot});
}
sub load {
my $self = shift;
$self->{pbot}->logger->log("Loading registry from " . $self->{registry}->{filename} . " ...\n");
$self->{registry}->load;
foreach my $section (keys %{ $self->{registry}->hash }) {
foreach my $item (keys %{ $self->{registry}->hash->{$section} }) {
$self->process_trigger($section, $item, $self->{registry}->hash->{$section}->{$item}->{value});
}
}
$self->{pbot}->logger->log("Done.\n");
}
sub save {
my $self = shift;
$self->{registry}->save;
}
sub add_default {
my ($self, $type, $section, $item, $value) = @_;
$self->add($type, $section, $item, $value, 1);
}
sub add {
my $self = shift;
my ($type, $section, $item, $value, $is_default) = @_;
$type = lc $type;
$section = lc $section;
$item = lc $item;
$self->{registry}->hash->{$section}->{$item}->{value} = $value;
$self->{registry}->hash->{$section}->{$item}->{type} = $type;
$self->process_trigger($section, $item, $value) unless $is_default;
$self->save_registry unless $is_default;
}
sub remove {
my $self = shift;
my ($section, $item) = @_;
$section = lc $section;
delete $self->{registry}->hash->{$section}->{$item};
if(not scalar keys $self->{registry}->hash->{$section}) {
delete $self->{registry}->hash->{$section};
}
$self->save_registry;
}
sub set {
my ($self, $section, $item, $key, $value) = @_;
$section = lc $section;
$item = lc $item;
$key = lc $key if defined $key;
my $oldvalue = $self->get_value($section, $item, 1) if defined $value;
$oldvalue = '' if not defined $oldvalue;
my $result = $self->{registry}->set($section, $item, $key, $value, 1);
if(defined $key and $key eq 'value' and defined $value and $oldvalue ne $value) {
$self->process_trigger($section, $item, $value);
}
return $result;
}
sub unset {
my ($self, $section, $item, $key) = @_;
$section = lc $section;
$item = lc $item;
$key = lc $key;
return $self->{registry}->unset($section, $item, $key);
}
sub get_value {
my ($self, $section, $item, $as_text) = @_;
if(exists $self->{registry}->hash->{$section} and exists $self->{registry}->hash->{$section}->{$item}) {
if(not $as_text and $self->{registry}->hash->{$section}->{$item}->{type} eq 'array') {
return split /\s*,\s*/, $self->{registry}->hash->{$section}->{$item}->{value};
} else {
return $self->{registry}->hash->{$section}->{$item}->{value};
}
}
return undef;
}
sub add_trigger {
my ($self, $section, $item, $subref) = @_;
$self->{triggers}->{$section}->{$item} = $subref;
}
sub process_trigger {
my $self = shift;
my ($section, $item) = @_;
if(exists $self->{triggers}->{$section} and exists $self->{triggers}->{$section}->{$item}) {
return &{ $self->{triggers}->{$section}->{$item} }(@_);
}
return undef;
}
1;

240
PBot/RegistryCommands.pm Normal file
View File

@ -0,0 +1,240 @@
# File: RegistryCommands.pm
# Author: pragma_
#
# Purpose: Commands to introspect and update Registry
package PBot::RegistryCommands;
use warnings;
use strict;
use Carp ();
sub new {
if(ref($_[1]) eq 'HASH') {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference");
}
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
my $pbot = delete $conf{pbot} // Carp::croak("Missing pbot reference to FactoidCommands");
$self->{pbot} = $pbot;
$pbot->commands->register(sub { return $self->regadd(@_) }, "regadd", 60);
$pbot->commands->register(sub { return $self->regrem(@_) }, "regrem", 60);
$pbot->commands->register(sub { return $self->regshow(@_) }, "regshow", 0);
$pbot->commands->register(sub { return $self->regset(@_) }, "regset", 60);
$pbot->commands->register(sub { return $self->regunset(@_) }, "regunset", 60);
$pbot->commands->register(sub { return $self->regchange(@_) }, "regchange", 60);
$pbot->commands->register(sub { return $self->regfind(@_) }, "regfind", 0);
}
sub regset {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($section, $item, $key, $value) = split / /, $arguments, 4 if defined $arguments;
if(not defined $section or not defined $item) {
return "Usage: regset <section> <item> [key [value]]";
}
$key = undef if not length $key;
$value = undef if not length $value;
return $self->{pbot}->{registry}->set($section, $item, $key, $value);
}
sub regunset {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($section, $item, $key) = split / /, $arguments, 3 if defined $arguments;
if(not defined $section or not defined $item or not defined $key) {
return "Usage: regunset <section> <item> <key>"
}
return $self->{pbot}->{registry}->unset($section, $item, $key);
}
sub regadd {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($section, $item, $value) = split / /, $arguments, 3 if defined $arguments;
if(not defined $section or not defined $item or not defined $value) {
return "/msg $nick Usage: regadd <section> <item> <value>";
}
$self->{pbot}->{registry}->add('text', $section, $item, $value);
$self->{pbot}->logger->log("$nick!$user\@$host added registry entry [$section] $item => $value\n");
return "/msg $nick [$section] $item set to $value";
}
sub regrem {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($section, $item) = split / /, $arguments if defined $arguments;
if(not defined $section or not defined $item) {
return "/msg $nick Usage: regrem <section> <item>";
}
if(not exists $self->{pbot}->{registry}->{registry}->hash->{$section}) {
return "/msg $nick No such registry section $section.";
}
if(not exists $self->{pbot}->{registry}->{registry}->hash->{$section}->{$item}) {
return "/msg $nick No such item $item in section $section.";
}
$self->{pbot}->logger->log("$nick!$user\@$host removed registry item [$section][$item]\n");
$self->{pbot}->{registry}->remove($section, $item);
return "/msg $nick Registry item $item removed from section $section.";
}
sub regshow {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $registry = $self->{pbot}->{registry}->{registry}->hash;
my ($section, $item) = split / /, $arguments if defined $arguments;
if(not defined $section or not defined $item) {
return "Usage: regshow <section> <item>";
}
if(not exists $registry->{$section}) {
return "/msg $nick No such registry section $section.";
}
if(not exists $registry->{$section}->{$item}) {
return "/msg $nick No such registry item $item in section $section.";
}
if($registry->{$section}->{$item}->{private}) {
return "/msg $nick [$section] $item is private.";
}
my $result = "[$section] $item: $registry->{$section}->{$item}->{value}";
if($registry->{$section}->{$item}->{type} eq 'array') {
$result .= ' [array]';
}
return $result;
}
sub regfind {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $registry = $self->{pbot}->{registry}->{registry}->hash;
if(not defined $arguments) {
return "/msg $nick Usage: regfind [-section section] <text>";
}
my $section;
$section = $1 if $arguments =~ s/-section\s+([^\b\s]+)//i;
$arguments =~ s/^\s+//;
$arguments =~ s/\s+$//;
$arguments =~ s/\s+/ /g;
if($arguments eq "") {
return "/msg $nick Usage: regfind [-section section] <text>";
}
my ($text, $last_item, $last_section, $i);
$last_section = "";
$i = 0;
eval {
foreach my $section_key (sort keys %{ $registry }) {
next if defined $section and $section_key !~ /^$section$/i;
foreach my $item_key (sort keys %{ $registry->{$section_key} }) {
next if $registry->{$section_key}->{$item_key}->{private};
next if $registry->{$section_key}->{$item_key}->{value} !~ /$arguments/i and $item_key !~ /$arguments/i;
$i++;
if($section_key ne $last_section) {
$text .= "[$section_key] ";
$last_section = $section_key;
}
$text .= "$item_key ";
$last_item = $item_key;
}
}
};
return "/msg $nick $arguments: $@" if $@;
if($i == 1) {
chop $text;
return "Found one registry entry: [$last_section] $last_item: $registry->{$last_section}->{$last_item}->{value}";
} else {
return "found $i registry entries: $text" unless $i == 0;
my $sections = (defined $section ? "section $section" : 'any sections');
return "No registry entries matching query found in $sections.";
}
}
sub regchange {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my ($section, $item, $delim, $tochange, $changeto, $modifier);
if(defined $arguments) {
if($arguments =~ /^([^\s]+) ([^\s]+)\s+s(.)/) {
$section = $1;
$item = $2;
$delim = $3;
}
if($arguments =~ /$delim(.*?)$delim(.*)$delim(.*)?$/) {
$tochange = $1;
$changeto = $2;
$modifier = $3;
}
}
if(not defined $section or not defined $item or not defined $changeto) {
return "Usage: regchange <section> <item> s/<pattern>/<replacement>/";
}
my $registry = $self->{pbot}->{registry}->{registry}->hash;
if(not exists $registry->{$section}) {
return "/msg $nick No such registry section $section.";
}
if(not exists $registry->{$section}->{$item}) {
return "/msg $nick No such registry item $item in section $section.";
}
my $ret = eval {
use re::engine::RE2 -strict => 1;
if(not $registry->{$section}->{$item}->{value} =~ s|$tochange|$changeto|) {
$self->{pbot}->logger->log("($from) $nick!$user\@$host: failed to change [$section] $item 's$delim$tochange$delim$changeto$delim$modifier\n");
return "/msg $nick Change [$section] $item failed.";
} else {
$self->{pbot}->logger->log("($from) $nick!$user\@$host: changed [$section] $item 's/$tochange/$changeto/\n");
$self->{pbot}->{registry}->process_trigger($section, $item, 'value', $registry->{$section}->{$item}->{value});
$self->{pbot}->{registry}->save;
return "Changed: [$section] $item set to $registry->{$section}->{$item}->{value}";
}
};
return "/msg $nick Change [$section] $item: $@" if $@;
return $ret;
}
1;

View File

@ -3,9 +3,6 @@ package PBot::SelectHandler;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = '1.0.0';
use IO::Select;
use Carp ();

View File

@ -3,9 +3,6 @@ package PBot::StdinReader;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = '1.0.0';
use POSIX qw(tcgetpgrp getpgrp); # to check whether process is in background or foreground
use Carp ();
@ -46,13 +43,13 @@ sub stdin_reader {
if($input =~ m/^~([^ ]+)\s+(.*)/) {
$from = $1;
$text = "$self->{pbot}->{trigger}$2";
$text = $self->{pbot}->{registry}->get_value('general', 'trigger') . $2;
} else {
$from = "$self->{pbot}->{botnick}!stdin\@localhost";
$text = "$self->{pbot}->{trigger}$input";
$from = $self->{pbot}->{registry}->get_value('irc', 'botnick') . "!stdin\@localhost";
$text = $self->{pbot}->{registry}->get_value('general', 'trigger') . $input;
}
return $self->{pbot}->interpreter->process_line($from, $self->{pbot}->{botnick}, "stdin", "localhost", $text);
return $self->{pbot}->interpreter->process_line($from, $self->{pbot}->{registry}->get_value('irc', 'botnick'), "stdin", "localhost", $text);
}
1;

View File

@ -10,9 +10,6 @@ package PBot::Timer;
use warnings;
use strict;
use vars qw($VERSION);
$VERSION = '1.0.0';
use Carp ();
our $min_timeout = 10;

View File

@ -13,8 +13,8 @@ use warnings;
# These are set automatically by the build/commit script
use constant {
BUILD_NAME => "PBot",
BUILD_REVISION => 581,
BUILD_DATE => "2014-05-16",
BUILD_REVISION => 582,
BUILD_DATE => "2014-05-17",
};
1;

View File

@ -77,8 +77,8 @@ my %config = (
# You shouldn't need to change anything below this line.
# -----------------------------------------------------
# Maximum messages to remember per nick/hostmask
MAX_NICK_MESSAGES => 256,
# Maximum messages to remember per nick/hostmask in message history
MAX_MESSAGES => 256,
# Path to data directory
data_dir => "$bothome/data",
@ -94,6 +94,9 @@ my %config = (
log_file => "$bothome/log/log",
);
# Location of file containing configuration registry
$config{registry_file} = "$config{config_dir}/registry";
# Location of file containing bot admin information
$config{admins_file} = "$config{config_dir}/admins";