3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-10-03 01:48:38 +02:00

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 feature 'switch';
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use PBot::DualIndexHashObject; use PBot::DualIndexHashObject;
use PBot::LagChecker; use PBot::LagChecker;
@ -45,14 +42,10 @@ sub initialize {
$self->{NICKSERV_VALIDATED} = (1<<0); $self->{NICKSERV_VALIDATED} = (1<<0);
$self->{NEEDS_CHECKBAN} = (1<<1); $self->{NEEDS_CHECKBAN} = (1<<1);
$self->{ENTER_ABUSE_MAX_LINES} = 4; $self->{channels} = {}; # per-channel statistics, e.g. for optimized tracking of last spoken nick for enter-abuse detection, etc
$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->{nickflood} = {}; # statistics to track nickchange flooding $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} = PBot::DualIndexHashObject->new(name => 'BanWhitelist', filename => $filename);
$self->{ban_whitelist}->load; $self->{ban_whitelist}->load;
@ -212,7 +205,7 @@ sub check_flood {
} }
# do not do flood processing for bot messages # 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; $self->{channels}->{$channel}->{last_spoken_nick} = $nick;
return; return;
} }
@ -229,9 +222,9 @@ sub check_flood {
} }
} }
if($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_NICK_MESSAGES; truncating.\n"); $self->{pbot}->logger->log("Warning: max_messages greater than max_messages limit; truncating.\n");
$max_messages = $self->{pbot}->{MAX_NICK_MESSAGES}; $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 # 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}) { 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}); 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}) { my $enter_abuse_max_lines = $self->{pbot}->{registry}->get_value('antiflood', 'enter_abuse_max_lines');
if(++$channel_data->{enter_abuse} >= $self->{ENTER_ABUSE_MAX_LINES} - 1) { my $enter_abuse_max_seconds = $self->{pbot}->{registry}->get_value('antiflood', 'enter_abuse_max_seconds');
$channel_data->{enter_abuse} = $self->{ENTER_ABUSE_MAX_LINES} / 2 - 1; my $enter_abuse_max_offenses = $self->{pbot}->{registry}->get_value('antiflood', 'enter_abuse_max_offenses');
if(++$channel_data->{enter_abuses} >= $self->{ENTER_ABUSE_MAX_OFFENSES}) {
my $offenses = $channel_data->{enter_abuses} - $self->{ENTER_ABUSE_MAX_OFFENSES} + 1; 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; my $ban_length = $offenses ** $offenses * $offenses * 30;
$self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $ban_length); $self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $ban_length);
$ban_length = duration($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); $self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
} else { } else {
if($channel_data->{enter_abuse} > 0) { 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; $channel_data->{enter_abuse} = 0;
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data); $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 ($self, $from, $nick, $user, $host, $arguments) = @_;
my $channel = lc $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>"; return "/msg $nick Usage: unbanme <channel>";
} }

View File

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

View File

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

View File

@ -8,9 +8,6 @@ package PBot::BotAdmins;
use warnings; use warnings;
use strict; use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use PBot::DualIndexHashObject; use PBot::DualIndexHashObject;
use Carp (); use Carp ();
@ -58,7 +55,7 @@ sub initialize {
sub add_admin { sub add_admin {
my $self = shift; my $self = shift;
my ($name, $channel, $hostmask, $level, $password) = @_; my ($name, $channel, $hostmask, $level, $password, $dont_save) = @_;
$channel = lc $channel; $channel = lc $channel;
$hostmask = lc $hostmask; $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->{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 { sub remove_admin {
@ -144,7 +141,7 @@ sub export_admins {
sub find_admin { sub find_admin {
my ($self, $from, $hostmask) = @_; 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; $hostmask = '.*' if not defined $hostmask;
my $result = eval { my $result = eval {

View File

@ -8,9 +8,6 @@ package PBot::ChanOpCommands;
use warnings; use warnings;
use strict; use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Carp (); use Carp ();
sub new { sub new {
@ -62,7 +59,8 @@ sub ban_user {
$length = 60 * 60; # one hour $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); $self->{pbot}->chanops->ban_user_timed($target, $from, $length);
return "/msg $nick $target banned in $from for $length seconds"; return "/msg $nick $target banned in $from for $length seconds";

View File

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

View File

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

View File

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

View File

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

View File

@ -8,9 +8,6 @@ package PBot::FactoidCommands;
use warnings; use warnings;
use strict; use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Carp (); use Carp ();
use Time::Duration; use Time::Duration;
use Time::HiRes qw(gettimeofday); use Time::HiRes qw(gettimeofday);
@ -87,7 +84,7 @@ sub call_factoid {
my ($chan, $keyword, $args) = split / /, $arguments, 3; my ($chan, $keyword, $args) = split / /, $arguments, 3;
if(not defined $chan or not defined $keyword) { 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); 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; my ($channel, $trigger, $key, $value) = split / /, $arguments, 4 if defined $arguments;
if(not defined $channel or not defined $trigger) { 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"); my $admininfo = $self->{pbot}->admins->loggedin($from, "$nick!$user\@$host");
@ -194,13 +191,14 @@ sub factunset {
sub list { sub list {
my $self = shift; my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_; my ($from, $nick, $user, $host, $arguments) = @_;
my $botnick = $self->{pbot}->botnick;
my $text; my $text;
if(not defined $arguments) { if(not defined $arguments) {
return "/msg $nick Usage: list <modules|factoids|commands|admins>"; return "/msg $nick Usage: list <modules|factoids|commands|admins>";
} }
# TODO - update this to use new MessageHistory API
=cut
if($arguments =~/^messages\s+(.*)$/) { if($arguments =~/^messages\s+(.*)$/) {
my ($mask_search, $channel_search, $text_search) = split / /, $1; 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}; $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) { 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} }) { foreach my $history_channel (keys %{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels} }) {
if($history_channel =~ m/$channel_search/i) { if($history_channel =~ m/$channel_search/i) {
my @messages = @{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{messages} }; my @messages = @{ $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{messages} };
for(my $i = 0; $i <= $#messages; $i++) { 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 "$history_mask, $history_channel\n";
print "joinwatch: ", $self->{pbot}->antiflood->message_history->{$history_mask}->{channels}->{$history_channel}{join_watch}, "\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); $self->{pbot}->logger->log($text);
return "Messages:\n\n$text"; return "Messages:\n\n$text";
} }
=cut
if($arguments =~ /^modules$/i) { if($arguments =~ /^modules$/i) {
$from = '.*' if not defined $from or $from !~ /^#/; $from = '.*' if not defined $from or $from !~ /^#/;

View File

@ -8,9 +8,6 @@ package PBot::FactoidModuleLauncher;
use warnings; use warnings;
use strict; use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use POSIX qw(WNOHANG); # for children process reaping use POSIX qw(WNOHANG); # for children process reaping
use Carp (); use Carp ();
use Text::Balanced qw(extract_delimited); 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 = $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"); $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 ($self, $buf) = @_;
my ($channel, $text) = split / /, $buf, 2; my ($channel, $text) = split / /, $buf, 2;
return if not defined $text or not length $text; return if not defined $text or not length $text;
$text = $self->{pbot}->interpreter->truncate_result($channel, $self->{pbot}->{botnick}, 'undef', $text, $text, 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}->{botnick}, $self->{pbot}->{username}, 'localhost', $text, 0, 0, 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; 1;

View File

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

View File

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

View File

@ -8,9 +8,6 @@ package PBot::IRCHandlers;
use warnings; use warnings;
use strict; use strict;
use vars qw($VERSION);
$VERSION = $PBot::PBot::VERSION;
use Carp(); use Carp();
use Time::HiRes qw(gettimeofday); use Time::HiRes qw(gettimeofday);
@ -70,15 +67,16 @@ sub on_public {
my $host = $event->host; my $host = $event->host;
my $text = $event->{args}[0]; 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 { sub on_msg {
my ($self, $conn, $event) = @_; my ($self, $conn, $event) = @_;
my ($nick, $host) = ($event->nick, $event->host); my ($nick, $host) = ($event->nick, $event->host);
my $text = $event->{args}[0]; 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->{to}[0] = $nick;
$event->{args}[0] = $text; $event->{args}[0] = $text;
$self->on_public($conn, $event); $self->on_public($conn, $event);
@ -93,7 +91,7 @@ sub on_notice {
if($nick eq "NickServ" && $text =~ m/This nickname is registered/) { if($nick eq "NickServ" && $text =~ m/This nickname is registered/) {
$self->{pbot}->logger->log("Identifying with NickServ . . .\n"); $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/) { 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); $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") { if($mode eq "+o") {
$self->{pbot}->logger->log("$nick opped me in $channel\n"); $self->{pbot}->logger->log("$nick opped me in $channel\n");
$self->{pbot}->chanops->{is_opped}->{$channel}{timeout} = gettimeofday + 300; # 5 minutes $self->{pbot}->chanops->{is_opped}->{$channel}{timeout} = gettimeofday + 300; # 5 minutes
@ -165,7 +163,7 @@ sub on_mode {
$self->{pbot}->chanops->{unban_timeout}->save; $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 }) { foreach my $chan (keys %{ $self->{pbot}->channels->channels->hash }) {
if($self->channels->channels->hash->{$chan}{enabled}) { if($self->channels->channels->hash->{$chan}{enabled}) {
$self->{pbot}->logger->log("Joining channel: $chan\n"); $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); 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}->{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 { sub on_kick {
@ -203,7 +201,7 @@ sub on_kick {
my $text = "KICKED by $nick!$user\@$host ($reason)"; 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}->{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}->{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"); my $admin = $self->{pbot}->admins->find_admin($channel, "$nick!$user\@$host");
if(defined $admin and $admin->{loggedin}) { 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}->devalidate_all_channels($newnick_account);
$self->{pbot}->{messagehistory}->{database}->update_hostmask_data($newnick_account, { last_seen => scalar gettimeofday }); $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}); $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});
}
sub pbot {
my $self = shift;
return $self->{pbot};
} }
1; 1;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ sub new {
sub initialize { sub initialize {
my ($self, %conf) = @_; my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__); $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} = PBot::MessageHistory_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database}->begin(); $self->{database}->begin();

View File

@ -27,7 +27,7 @@ sub initialize {
my ($self, %conf) = @_; my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference in " . __FILE__); $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->{pbot}->timer->register(sub { $self->commit_message_history }, 5);
$self->{new_entries} = 0; $self->{new_entries} = 0;
@ -433,8 +433,10 @@ sub recall_message_by_count {
$self->{pbot}->logger->log($@) if $@; $self->{pbot}->logger->log($@) if $@;
if(defined $ignore_command) { 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) { 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 $message;
} }
return undef; return undef;
@ -474,8 +476,10 @@ sub recall_message_by_text {
$self->{pbot}->logger->log($@) if $@; $self->{pbot}->logger->log($@) if $@;
if(defined $ignore_command) { 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) { 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 $message;
} }
return undef; return undef;

View File

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

View File

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

View File

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

View File

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

View File

@ -8,9 +8,6 @@ package PBot::Registerable;
use warnings; use warnings;
use strict; use strict;
use vars qw($VERSION);
$VERSION = '1.0.0';
use Carp (); use Carp ();
sub new { 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 warnings;
use strict; use strict;
use vars qw($VERSION);
$VERSION = '1.0.0';
use IO::Select; use IO::Select;
use Carp (); use Carp ();

View File

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

View File

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

View File

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

View File

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