3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-12-26 12:42:49 +01:00

Finish moving commands from Core to Commands

This commit is contained in:
Pragmatic Software 2021-07-23 16:41:32 -07:00
parent 4b80131473
commit 4b3c5d58cf
13 changed files with 788 additions and 563 deletions

View File

@ -33,10 +33,6 @@ sub initialize {
$self->{whois_pending} = {}; # prevents multiple whois for nick joining multiple channels at once
$self->{changinghost} = {}; # tracks nicks changing hosts/identifying to strongly link them
my $filename = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/ban-exemptions';
$self->{'ban-exemptions'} = PBot::Storage::DualIndexHashObject->new(name => 'Ban exemptions', filename => $filename, pbot => $self->{pbot});
$self->{'ban-exemptions'}->load;
$self->{pbot}->{event_queue}->enqueue(sub { $self->adjust_offenses }, 60 * 60 * 1, 'Adjust anti-flood offenses');
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'enforce', $conf{enforce_antiflood} // 1);
@ -60,178 +56,12 @@ sub initialize {
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'debug_checkban', $conf{debug_checkban} // 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_unbanme(@_) }, "unbanme", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_ban_exempt(@_) }, "ban-exempt", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-ban-exempt', 1);
$self->{pbot}->{event_dispatcher}->register_handler('irc.whoisaccount', sub { $self->on_whoisaccount(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.whoisuser', sub { $self->on_whoisuser(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofwhois', sub { $self->on_endofwhois(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.account', sub { $self->on_accountnotify(@_) });
}
sub cmd_ban_exempt {
my ($self, $context) = @_;
my $arglist = $context->{arglist};
$self->{pbot}->{interpreter}->lc_args($arglist);
my $command = $self->{pbot}->{interpreter}->shift_arg($arglist);
return "Usage: ban-exempt <command>, where commands are: list, add, remove" if not defined $command;
given ($command) {
when ($_ eq 'list') {
my $text = "Ban-evasion exemptions:\n";
my $entries = 0;
foreach my $channel ($self->{'ban-exemptions'}->get_keys) {
$text .= ' ' . $self->{'ban-exemptions'}->get_key_name($channel) . ":\n";
foreach my $mask ($self->{'ban-exemptions'}->get_keys($channel)) {
$text .= " $mask,\n";
$entries++;
}
}
$text .= "none" if $entries == 0;
return $text;
}
when ("add") {
my ($channel, $mask) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: ban-exempt add <channel> <mask>" if not defined $channel or not defined $mask;
my $data = {
owner => $context->{hostmask},
created_on => scalar gettimeofday
};
$self->{'ban-exemptions'}->add($channel, $mask, $data);
return "/say $mask exempted from ban-evasions in channel $channel";
}
when ("remove") {
my ($channel, $mask) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: ban-exempt remove <channel> <mask>" if not defined $channel or not defined $mask;
return $self->{'ban-exemptions'}->remove($channel, $mask);
}
default { return "Unknown command '$command'; commands are: list, add, remove"; }
}
}
sub cmd_unbanme {
my ($self, $context) = @_;
my $unbanned;
my %aliases = $self->{pbot}->{messagehistory}->{database}->get_also_known_as($context->{nick});
foreach my $alias (keys %aliases) {
next if $aliases{$alias}->{type} == $self->{pbot}->{messagehistory}->{database}->{alias_type}->{WEAK};
next if $aliases{$alias}->{nickchange} == 1;
my $join_flood_channel = $self->{pbot}->{registry}->get_value('antiflood', 'join_flood_channel') // '#stop-join-flood';
my ($anick, $auser, $ahost) = $alias =~ m/([^!]+)!([^@]+)@(.*)/;
my $banmask = $self->address_to_mask($ahost);
my $mask = "*!$auser\@$banmask\$$join_flood_channel";
my @channels = $self->{pbot}->{messagehistory}->{database}->get_channels($aliases{$alias}->{id});
foreach my $channel (@channels) {
next if exists $unbanned->{$channel} and exists $unbanned->{$channel}->{$mask};
next if not $self->{pbot}->{banlist}->{banlist}->exists($channel . '-floodbans', $mask);
my $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account($anick, $auser, $ahost);
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
push @nickserv_accounts, undef;
foreach my $nickserv_account (@nickserv_accounts) {
my $baninfos = $self->{pbot}->{banlist}->get_baninfo($channel, "$anick!$auser\@$ahost", $nickserv_account);
if (defined $baninfos) {
foreach my $baninfo (@$baninfos) {
my $u = $self->{pbot}->{users}->loggedin($baninfo->{channel}, $context->{hostmask});
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
if ($self->ban_exempted($baninfo->{channel}, $baninfo->{mask}) || $whitelisted) {
$self->{pbot}->{logger}->log("anti-flood: [unbanme] $anick!$auser\@$ahost banned as $baninfo->{mask} in $baninfo->{channel}, but allowed through whitelist\n");
} else {
if ($channel eq lc $baninfo->{channel}) {
my $mode = $baninfo->{type} eq 'b' ? "banned" : "quieted";
$self->{pbot}->{logger}->log("anti-flood: [unbanme] $anick!$auser\@$ahost $mode as $baninfo->{mask} in $baninfo->{channel} by $baninfo->{owner}, unbanme rejected\n");
return "/msg $context->{nick} You have been $mode as $baninfo->{mask} by $baninfo->{owner}, unbanme will not work until it is removed.";
}
}
}
}
}
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'unbanmes');
if ($channel_data->{unbanmes} <= 2) {
$channel_data->{unbanmes}++;
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
}
$unbanned->{$channel}->{$mask} = $channel_data->{unbanmes};
}
}
if (keys %$unbanned) {
my $channels = '';
my $sep = '';
my $channels_warning = '';
my $sep_warning = '';
my $channels_disabled = '';
my $sep_disabled = '';
foreach my $channel (keys %$unbanned) {
foreach my $mask (keys %{$unbanned->{$channel}}) {
if ($self->{pbot}->{channels}->is_active_op("${channel}-floodbans")) {
if ($unbanned->{$channel}->{$mask} <= 2) {
$self->{pbot}->{banlist}->unban_user($channel . '-floodbans', 'b', $mask);
$channels .= "$sep$channel";
$sep = ", ";
}
if ($unbanned->{$channel}->{$mask} == 1) {
$channels_warning .= "$sep_warning$channel";
$sep_warning = ", ";
} else {
$channels_disabled .= "$sep_disabled$channel";
$sep_disabled = ", ";
}
}
}
}
$self->{pbot}->{banlist}->flush_unban_queue();
$channels =~ s/(.*), /$1 and /;
$channels_warning =~ s/(.*), /$1 and /;
$channels_disabled =~ s/(.*), /$1 and /;
my $warning = '';
if (length $channels_warning) {
$warning =
" You may use `unbanme` one more time today for $channels_warning; please ensure that your client or connection issues are resolved.";
}
if (length $channels_disabled) {
$warning .=
" You may not use `unbanme` again for several hours for $channels_disabled.";
}
if (length $channels) { return "/msg $context->{nick} You have been unbanned from $channels.$warning"; }
else { return "/msg $context->{nick} $warning"; }
} else {
return "/msg $context->{nick} There is no join-flooding ban set for you.";
}
}
sub ban_exempted {
my ($self, $channel, $hostmask) = @_;
$channel = lc $channel;
$hostmask = lc $hostmask;
return 1 if $self->{'ban-exemptions'}->exists($channel, $hostmask);
return 0;
}
sub update_join_watch {
my ($self, $account, $channel, $text, $mode) = @_;
@ -829,7 +659,7 @@ sub check_bans {
my $tnickserv = defined $nickserv ? $nickserv : "[undefined]";
$self->{pbot}->{logger}->log("anti-flood: [check-bans] checking blacklist for $alias in channel $channel using gecos '$tgecos' and nickserv '$tnickserv'\n")
if $debug_checkban >= 5;
if ($self->{pbot}->{blacklist}->check_blacklist($alias, $channel, $nickserv, $gecos)) {
if ($self->{pbot}->{blacklist}->is_blacklisted($alias, $channel, $nickserv, $gecos)) {
my $u = $self->{pbot}->{users}->loggedin($channel, $mask);
if ($self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted')) {
$self->{pbot}->{logger}->log("anti-flood: [check-bans] $mask [$alias] blacklisted in $channel, but allowed through whitelist\n");
@ -867,7 +697,7 @@ sub check_bans {
my $u = $self->{pbot}->{users}->loggedin($baninfo->{channel}, $mask);
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
if ($self->ban_exempted($baninfo->{channel}, $baninfo->{mask}) || $whitelisted) {
if ($self->{pbot}->{banlist}->ban_exempted($baninfo->{channel}, $baninfo->{mask}) || $whitelisted) {
#$self->{pbot}->{logger}->log("anti-flood: [check-bans] $mask [$alias] evaded $baninfo->{mask} in $baninfo->{channel}, but allowed through whitelist\n");
next;
}

View File

@ -10,105 +10,20 @@ use parent 'PBot::Core::Class';
use PBot::Imports;
use Time::HiRes qw(gettimeofday);
use POSIX qw/strftime/;
sub initialize {
my ($self, %conf) = @_;
my $filename = $conf{spamkeywords_file} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/spam_keywords';
$self->{keywords} = PBot::Storage::DualIndexHashObject->new(name => 'SpamKeywords', filename => $filename, pbot => $self->{pbot});
my $filename = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/spam_keywords';
$self->{keywords} = PBot::Storage::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'SpamKeywords',
filename => $filename,
);
$self->{keywords}->load;
$self->{pbot}->{registry}->add_default('text', 'antispam', 'enforce', $conf{enforce_antispam} // 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_antispam(@_) }, "antispam", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-antispam', 1);
}
sub cmd_antispam {
my ($self, $context) = @_;
my $arglist = $context->{arglist};
my $command = $self->{pbot}->{interpreter}->shift_arg($arglist);
return "Usage: antispam <command>, where commands are: list/show, add, remove, set, unset" if not defined $command;
given ($command) {
when ($_ eq "list" or $_ eq "show") {
my $text = "Spam keywords:\n";
my $entries = 0;
foreach my $namespace ($self->{keywords}->get_keys) {
$text .= ' ' . $self->{keywords}->get_key_name($namespace) . ":\n";
foreach my $keyword ($self->{keywords}->get_keys($namespace)) {
$text .= ' ' . $self->{keywords}->get_key_name($namespace, $keyword) . ",\n";
$entries++;
}
}
$text .= "none" if $entries == 0;
return $text;
}
when ("set") {
my ($namespace, $keyword, $flag, $value) = $self->{pbot}->{interpreter}->split_args($arglist, 4);
return "Usage: antispam set <namespace> <regex> [flag [value]]" if not defined $namespace or not defined $keyword;
if (not $self->{keywords}->exists($namespace)) { return "There is no such namespace `$namespace`."; }
if (not $self->{keywords}->exists($namespace, $keyword)) {
return "There is no such regex `$keyword` for namespace `" . $self->{keywords}->get_key_name($namespace) . '`.';
}
if (not defined $flag) {
my $text = "Flags:\n";
my $comma = '';
foreach $flag ($self->{keywords}->get_keys($namespace, $keyword)) {
if ($flag eq 'created_on') {
my $timestamp = strftime "%a %b %e %H:%M:%S %Z %Y", localtime $self->{keywords}->get_data($namespace, $keyword, $flag);
$text .= $comma . "created_on: $timestamp";
} else {
$value = $self->{keywords}->get_data($namespace, $keyword, $flag);
$text .= $comma . "$flag: $value";
}
$comma = ",\n ";
}
return $text;
}
if (not defined $value) {
$value = $self->{keywords}->get_data($namespace, $keyword, $flag);
if (not defined $value) { return "/say $flag is not set."; }
else { return "/say $flag is set to $value"; }
}
$self->{keywords}->set($namespace, $keyword, $flag, $value);
return "Flag set.";
}
when ("unset") {
my ($namespace, $keyword, $flag) = $self->{pbot}->{interpreter}->split_args($arglist, 3);
return "Usage: antispam unset <namespace> <regex> <flag>" if not defined $namespace or not defined $keyword or not defined $flag;
if (not $self->{keywords}->exists($namespace)) { return "There is no such namespace `$namespace`."; }
if (not $self->{keywords}->exists($namespace, $keyword)) { return "There is no such keyword `$keyword` for namespace `$namespace`."; }
if (not $self->{keywords}->exists($namespace, $keyword, $flag)) { return "There is no such flag `$flag` for regex `$keyword` for namespace `$namespace`."; }
return $self->{keywords}->remove($namespace, $keyword, $flag);
}
when ("add") {
my ($namespace, $keyword) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: antispam add <namespace> <regex>" if not defined $namespace or not defined $keyword;
my $data = {
owner => $context->{hostmask},
created_on => scalar gettimeofday
};
$self->{keywords}->add($namespace, $keyword, $data);
return "/say Added `$keyword`.";
}
when ("remove") {
my ($namespace, $keyword) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: antispam remove <namespace> <regex>" if not defined $namespace or not defined $keyword;
return $self->{keywords}->remove($namespace, $keyword);
}
default { return "Unknown command '$command'; commands are: list/show, add, remove"; }
}
}
sub is_spam {
@ -121,7 +36,9 @@ sub is_spam {
my $ret = eval {
foreach my $space ($self->{keywords}->get_keys) {
if ($all_namespaces or $lc_namespace eq $space) {
foreach my $keyword ($self->{keywords}->get_keys($space)) { return 1 if $text =~ m/$keyword/i; }
foreach my $keyword ($self->{keywords}->get_keys($space)) {
return 1 if $text =~ m/$keyword/i;
}
}
}
return 0;
@ -131,6 +48,7 @@ sub is_spam {
$self->{pbot}->{logger}->log("Error in is_spam: $@");
return 0;
}
$self->{pbot}->{logger}->log("AntiSpam: spam detected!\n") if $ret;
return $ret;
}

View File

@ -31,24 +31,33 @@ sub initialize {
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofbanlist', sub { $self->compare_banlist(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofquietlist', sub { $self->compare_quietlist(@_) });
my $data_dir = $self->{pbot}->{registry}->get_value('general', 'data_dir');
$self->{'ban-exemptions'} = PBot::Storage::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Ban exemptions',
filename => "$data_dir/ban-exemptions",
);
$self->{banlist} = PBot::Storage::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Ban List',
filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/banlist',
filename => "$data_dir/banlist",
save_queue_timeout => 15,
);
$self->{quietlist} = PBot::Storage::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Quiet List',
filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quietlist',
filename => "$data_dir/quietlist",
save_queue_timeout => 15,
);
$self->{'ban-exemptions'}->load;
$self->{banlist}->load;
$self->{quietlist}->load;
$self->enqueue_timeouts($self->{banlist}, 'b');
$self->enqueue_timeouts($self->{banlist}, 'b');
$self->enqueue_timeouts($self->{quietlist}, $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char'));
$self->{ban_queue} = {};
@ -276,6 +285,14 @@ sub track_mode {
}
}
sub ban_exempted {
my ($self, $channel, $hostmask) = @_;
$channel = lc $channel;
$hostmask = lc $hostmask;
return 1 if $self->{'ban-exemptions'}->exists($channel, $hostmask);
return 0;
}
sub ban_user {
my ($self, $channel, $mode, $mask, $immediately) = @_;
$mode ||= 'b';

View File

@ -10,111 +10,52 @@ use parent 'PBot::Core::Class';
use PBot::Imports;
use Time::HiRes qw(gettimeofday);
sub initialize {
my ($self, %conf) = @_;
$self->{filename} = $conf{filename};
$self->{blacklist} = {};
$self->{pbot}->{commands}->register(sub { $self->cmd_blacklist(@_) }, "blacklist", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-blacklist', 1);
$self->load_blacklist;
}
sub cmd_blacklist {
my ($self, $context) = @_;
my $arglist = $context->{arglist};
$self->{pbot}->{interpreter}->lc_args($arglist);
my $command = $self->{pbot}->{interpreter}->shift_arg($arglist);
return "Usage: blacklist <command>, where commands are: list/show, add, remove" if not defined $command;
given ($command) {
when ($_ eq "list" or $_ eq "show") {
my $text = "Blacklist:\n";
my $entries = 0;
foreach my $channel (sort keys %{$self->{blacklist}}) {
if ($channel eq '.*') { $text .= " all channels:\n"; }
else { $text .= " $channel:\n"; }
foreach my $mask (sort keys %{$self->{blacklist}->{$channel}}) {
$text .= " $mask,\n";
$entries++;
}
}
$text .= "none" if $entries == 0;
return "/msg $context->{nick} $text";
}
when ("add") {
my ($mask, $channel) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: blacklist add <hostmask regex> [channel]" if not defined $mask;
$channel = '.*' if not defined $channel;
$self->{pbot}->{logger}->log("$context->{hostmask} added [$mask] to blacklist for channel [$channel]\n");
$self->add($channel, $mask);
return "/say $mask blacklisted in channel $channel";
}
when ("remove") {
my ($mask, $channel) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: blacklist remove <hostmask regex> [channel]" if not defined $mask;
$channel = '.*' if not defined $channel;
if (exists $self->{blacklist}->{$channel} and not exists $self->{blacklist}->{$channel}->{$mask}) {
$self->{pbot}->{logger}->log("$context->{hostmask} attempt to remove nonexistent [$mask][$channel] from blacklist\n");
return "/say $mask not found in blacklist for channel $channel (use `blacklist list` to display blacklist)";
}
$self->remove($channel, $mask);
$self->{pbot}->{logger}->log("$context->{hostmask} removed [$mask] from blacklist for channel [$channel]\n");
return "/say $mask removed from blacklist for channel $channel";
}
default { return "Unknown command '$command'; commands are: list/show, add, remove"; }
}
$self->{filename} = $conf{filename};
$self->{storage} = {};
$self->load;
}
sub add {
my ($self, $channel, $hostmask) = @_;
$self->{blacklist}->{lc $channel}->{lc $hostmask} = 1;
$self->save_blacklist();
$self->{storage}->{lc $channel}->{lc $hostmask} = 1;
$self->save;
}
sub remove {
my $self = shift;
my ($channel, $hostmask) = @_;
my ($self, $channel, $hostmask) = @_;
$channel = lc $channel;
$hostmask = lc $hostmask;
if (exists $self->{blacklist}->{$channel}) {
delete $self->{blacklist}->{$channel}->{$hostmask};
if (exists $self->{storage}->{$channel}) {
delete $self->{storage}->{$channel}->{$hostmask};
if (keys %{$self->{blacklist}->{$channel}} == 0) { delete $self->{blacklist}->{$channel}; }
if (not keys %{$self->{storage}->{$channel}}) {
delete $self->{storage}->{$channel};
}
}
$self->save_blacklist();
$self->save;
}
sub clear_blacklist {
my $self = shift;
$self->{blacklist} = {};
sub clear {
my ($self) = @_;
$self->{storage} = {};
}
sub load_blacklist {
my $self = shift;
my $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
sub load {
my ($self) = @_;
if (not defined $filename) {
if (not $self->{filename}) {
$self->{pbot}->{logger}->log("No blacklist path specified -- skipping loading of blacklist");
return;
}
$self->{pbot}->{logger}->log("Loading blacklist from $filename ...\n");
$self->{pbot}->{logger}->log("Loading blacklist from $self->{filename} ...\n");
open(FILE, "< $filename") or Carp::croak "Couldn't open $filename: $!\n";
open(FILE, "< $self->{filename}") or Carp::croak "Couldn't open $self->{filename}: $!\n";
my @contents = <FILE>;
close(FILE);
@ -126,51 +67,55 @@ sub load_blacklist {
my ($channel, $hostmask) = split(/\s+/, $line);
if (not defined $hostmask || not defined $channel) { Carp::croak "Syntax error around line $i of $filename\n"; }
if (not defined $hostmask || not defined $channel) {
Carp::croak "Syntax error around line $i of $self->{filename}\n";
}
if (exists $self->{blacklist}->{$channel}->{$hostmask}) { Carp::croak "Duplicate blacklist entry [$hostmask][$channel] found in $filename around line $i\n"; }
if (exists $self->{storage}->{$channel}->{$hostmask}) {
Carp::croak "Duplicate blacklist entry $hostmask $channel found in $self->{filename} around line $i\n";
}
$self->{blacklist}->{$channel}->{$hostmask} = 1;
$self->{storage}->{$channel}->{$hostmask} = 1;
}
$self->{pbot}->{logger}->log(" $i entries in blacklist\n");
}
sub save_blacklist {
my $self = shift;
my $filename;
sub save {
my ($self) = @_;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
if (not defined $filename) {
if (not $self->{filename}) {
$self->{pbot}->{logger}->log("No blacklist path specified -- skipping saving of blacklist\n");
return;
}
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
open(FILE, "> $self->{filename}") or die "Couldn't open $self->{filename}: $!\n";
foreach my $channel (keys %{$self->{blacklist}}) {
foreach my $hostmask (keys %{$self->{blacklist}->{$channel}}) { print FILE "$channel $hostmask\n"; }
foreach my $channel (keys %{$self->{storage}}) {
foreach my $hostmask (keys %{$self->{storage}->{$channel}}) {
print FILE "$channel $hostmask\n";
}
}
close(FILE);
close FILE;
}
sub check_blacklist {
my $self = shift;
my ($hostmask, $channel, $nickserv, $gecos) = @_;
sub is_blacklisted {
my ($self, $hostmask, $channel, $nickserv, $gecos) = @_;
return 0 if not defined $channel;
my $result = eval {
foreach my $black_channel (keys %{$self->{blacklist}}) {
foreach my $black_hostmask (keys %{$self->{blacklist}->{$black_channel}}) {
my $flag = '';
$flag = $1 if $black_hostmask =~ s/^\$(.)://;
foreach my $black_channel (keys %{$self->{storage}}) {
foreach my $black_hostmask (keys %{$self->{storage}->{$black_channel}}) {
next if $channel !~ /^$black_channel$/i;
my $flag = '';
if ($black_hostmask =~ s/^\$(.)://) {
$flag = $1;
}
if ($flag eq 'a' && defined $nickserv && $nickserv =~ /^$black_hostmask$/i) {
$self->{pbot}->{logger}->log("$hostmask nickserv $nickserv blacklisted in channel $channel (matches [\$a:$black_hostmask] host and [$black_channel] channel)\n");
return 1;
@ -186,8 +131,8 @@ sub check_blacklist {
return 0;
};
if ($@) {
$self->{pbot}->{logger}->log("Error in blacklist: $@\n");
if (my $exception = $@) {
$self->{pbot}->{logger}->log("Error in blacklist: $exception");
return 0;
}

View File

@ -0,0 +1,164 @@
# File: AntiSpam.pm
#
# Purpose: Command to manipulate anti-spam list.
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
package PBot::Core::Commands::AntiSpam;
use parent 'PBot::Core::Class';
use PBot::Imports;
use Time::HiRes qw/gettimeofday/;
use POSIX qw/strftime/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->cmd_antispam(@_) }, "antispam", 1);
# add capability to admin group
$self->{pbot}->{capabilities}->add('admin', 'can-antispam', 1);
}
sub cmd_antispam {
my ($self, $context) = @_;
my $arglist = $context->{arglist};
my $command = $self->{pbot}->{interpreter}->shift_arg($arglist);
if (not defined $command) {
return "Usage: antispam <command>, where commands are: list/show, add, remove, set, unset";
}
my $keywords = $self->{pbot}->{antispam}->{keywords};
given ($command) {
when ($_ eq "list" or $_ eq "show") {
my $text = "Spam keywords:\n";
my $entries = 0;
foreach my $namespace ($keywords->get_keys) {
$text .= ' ' . $keywords->get_key_name($namespace) . ":\n";
foreach my $keyword ($keywords->get_keys($namespace)) {
$text .= ' ' . $keywords->get_key_name($namespace, $keyword) . ",\n";
$entries++;
}
}
$text .= "none" if $entries == 0;
return $text;
}
when ("set") {
my ($namespace, $keyword, $flag, $value) = $self->{pbot}->{interpreter}->split_args($arglist, 4);
if (not defined $namespace or not defined $keyword) {
return "Usage: antispam set <namespace> <regex> [flag [value]]"
}
if (not $keywords->exists($namespace)) {
return "There is no such namespace `$namespace`.";
}
if (not $keywords->exists($namespace, $keyword)) {
return "There is no such regex `$keyword` for namespace `" . $keywords->get_key_name($namespace) . '`.';
}
if (not defined $flag) {
my @flags;
foreach $flag ($keywords->get_keys($namespace, $keyword)) {
if ($flag eq 'created_on') {
my $timestamp = strftime "%a %b %e %H:%M:%S %Z %Y", localtime $keywords->get_data($namespace, $keyword, $flag);
push @flags, "created_on: $timestamp";
} else {
$value = $keywords->get_data($namespace, $keyword, $flag);
push @flags, "$flag: $value";
}
}
my $text = "Flags: ";
if (@flags) {
$text .= join ",\n", @flags;
} else {
$text .= 'none';
}
return $text;
}
if (not defined $value) {
$value = $keywords->get_data($namespace, $keyword, $flag);
if (not defined $value) {
return "/say $flag is not set.";
} else {
return "/say $flag is set to $value";
}
}
$keywords->set($namespace, $keyword, $flag, $value);
return "Flag set.";
}
when ("unset") {
my ($namespace, $keyword, $flag) = $self->{pbot}->{interpreter}->split_args($arglist, 3);
if (not defined $namespace or not defined $keyword or not defined $flag) {
return "Usage: antispam unset <namespace> <regex> <flag>"
}
if (not $keywords->exists($namespace)) {
return "There is no such namespace `$namespace`.";
}
if (not $keywords->exists($namespace, $keyword)) {
return "There is no such keyword `$keyword` for namespace `$namespace`.";
}
if (not $keywords->exists($namespace, $keyword, $flag)) {
return "There is no such flag `$flag` for regex `$keyword` for namespace `$namespace`.";
}
return $keywords->remove($namespace, $keyword, $flag);
}
when ("add") {
my ($namespace, $keyword) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
if (not defined $namespace or not defined $keyword) {
return "Usage: antispam add <namespace> <regex>";
}
my $data = {
owner => $context->{hostmask},
created_on => scalar gettimeofday
};
$keywords->add($namespace, $keyword, $data);
return "/say Added `$keyword`.";
}
when ("remove") {
my ($namespace, $keyword) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
if (not defined $namespace or not defined $keyword) {
return "Usage: antispam remove <namespace> <regex>";
}
return $keywords->remove($namespace, $keyword);
}
default {
return "Unknown command '$command'; commands are: list/show, add, remove";
}
}
}
1;

View File

@ -17,9 +17,15 @@ use POSIX qw/strftime/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->cmd_banlist(@_) }, "banlist", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_checkban(@_) }, "checkban", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_checkmute(@_) }, "checkmute", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_banlist(@_) }, "banlist", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_checkban(@_) }, "checkban", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_checkmute(@_) }, "checkmute", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_unbanme(@_) }, "unbanme", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_ban_exempt(@_) }, "ban-exempt", 1);
# add capability to admin group
$self->{pbot}->{capabilities}->add('admin', 'can-ban-exempt', 1);
}
sub cmd_banlist {
@ -107,4 +113,158 @@ sub cmd_checkmute {
return $self->{pbot}->{banlist}->checkban($channel, $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char'), $target);
}
sub cmd_unbanme {
my ($self, $context) = @_;
my $unbanned;
my %aliases = $self->{pbot}->{messagehistory}->{database}->get_also_known_as($context->{nick});
foreach my $alias (keys %aliases) {
next if $aliases{$alias}->{type} == $self->{pbot}->{messagehistory}->{database}->{alias_type}->{WEAK};
next if $aliases{$alias}->{nickchange} == 1;
my $join_flood_channel = $self->{pbot}->{registry}->get_value('antiflood', 'join_flood_channel') // '#stop-join-flood';
my ($anick, $auser, $ahost) = $alias =~ m/([^!]+)!([^@]+)@(.*)/;
my $banmask = $self->{pbot}->{antiflood}->address_to_mask($ahost);
my $mask = "*!$auser\@$banmask\$$join_flood_channel";
my @channels = $self->{pbot}->{messagehistory}->{database}->get_channels($aliases{$alias}->{id});
foreach my $channel (@channels) {
next if exists $unbanned->{$channel} and exists $unbanned->{$channel}->{$mask};
next if not $self->{pbot}->{banlist}->{banlist}->exists($channel . '-floodbans', $mask);
my $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account($anick, $auser, $ahost);
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
push @nickserv_accounts, undef;
foreach my $nickserv_account (@nickserv_accounts) {
my $baninfos = $self->{pbot}->{banlist}->get_baninfo($channel, "$anick!$auser\@$ahost", $nickserv_account);
if (defined $baninfos) {
foreach my $baninfo (@$baninfos) {
my $u = $self->{pbot}->{users}->loggedin($baninfo->{channel}, $context->{hostmask});
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
if ($self->{pbot}->{banlist}->ban_exempted($baninfo->{channel}, $baninfo->{mask}) || $whitelisted) {
$self->{pbot}->{logger}->log("anti-flood: [unbanme] $anick!$auser\@$ahost banned as $baninfo->{mask} in $baninfo->{channel}, but allowed through whitelist\n");
} else {
if ($channel eq lc $baninfo->{channel}) {
my $mode = $baninfo->{type} eq 'b' ? "banned" : "quieted";
$self->{pbot}->{logger}->log("anti-flood: [unbanme] $anick!$auser\@$ahost $mode as $baninfo->{mask} in $baninfo->{channel} by $baninfo->{owner}, unbanme rejected\n");
return "/msg $context->{nick} You have been $mode as $baninfo->{mask} by $baninfo->{owner}, unbanme will not work until it is removed.";
}
}
}
}
}
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'unbanmes');
if ($channel_data->{unbanmes} <= 2) {
$channel_data->{unbanmes}++;
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
}
$unbanned->{$channel}->{$mask} = $channel_data->{unbanmes};
}
}
if (keys %$unbanned) {
my $channels = '';
my $sep = '';
my $channels_warning = '';
my $sep_warning = '';
my $channels_disabled = '';
my $sep_disabled = '';
foreach my $channel (keys %$unbanned) {
foreach my $mask (keys %{$unbanned->{$channel}}) {
if ($self->{pbot}->{channels}->is_active_op("${channel}-floodbans")) {
if ($unbanned->{$channel}->{$mask} <= 2) {
$self->{pbot}->{banlist}->unban_user($channel . '-floodbans', 'b', $mask);
$channels .= "$sep$channel";
$sep = ", ";
}
if ($unbanned->{$channel}->{$mask} == 1) {
$channels_warning .= "$sep_warning$channel";
$sep_warning = ", ";
} else {
$channels_disabled .= "$sep_disabled$channel";
$sep_disabled = ", ";
}
}
}
}
$self->{pbot}->{banlist}->flush_unban_queue();
$channels =~ s/(.*), /$1 and /;
$channels_warning =~ s/(.*), /$1 and /;
$channels_disabled =~ s/(.*), /$1 and /;
my $warning = '';
if (length $channels_warning) {
$warning =
" You may use `unbanme` one more time today for $channels_warning; please ensure that your client or connection issues are resolved.";
}
if (length $channels_disabled) {
$warning .=
" You may not use `unbanme` again for several hours for $channels_disabled.";
}
if (length $channels) { return "/msg $context->{nick} You have been unbanned from $channels.$warning"; }
else { return "/msg $context->{nick} $warning"; }
} else {
return "/msg $context->{nick} There is no join-flooding ban set for you.";
}
}
sub cmd_ban_exempt {
my ($self, $context) = @_;
my $arglist = $context->{arglist};
$self->{pbot}->{interpreter}->lc_args($arglist);
my $command = $self->{pbot}->{interpreter}->shift_arg($arglist);
return "Usage: ban-exempt <command>, where commands are: list, add, remove" if not defined $command;
given ($command) {
when ($_ eq 'list') {
my $text = "Ban-evasion exemptions:\n";
my $entries = 0;
foreach my $channel ($self->{pbot}->{banlist}->{'ban-exemptions'}->get_keys) {
$text .= ' ' . $self->{pbot}->{banlist}->{'ban-exemptions'}->get_key_name($channel) . ":\n";
foreach my $mask ($self->{pbot}->{banlist}->{'ban-exemptions'}->get_keys($channel)) {
$text .= " $mask,\n";
$entries++;
}
}
$text .= "none" if $entries == 0;
return $text;
}
when ("add") {
my ($channel, $mask) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: ban-exempt add <channel> <mask>" if not defined $channel or not defined $mask;
my $data = {
owner => $context->{hostmask},
created_on => scalar gettimeofday
};
$self->{pbot}->{banlist}->{'ban-exemptions'}->add($channel, $mask, $data);
return "/say $mask exempted from ban-evasions in channel $channel";
}
when ("remove") {
my ($channel, $mask) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
return "Usage: ban-exempt remove <channel> <mask>" if not defined $channel or not defined $mask;
return $self->{pbot}->{banlist}->{'ban-exemptions'}->remove($channel, $mask);
}
default { return "Unknown command '$command'; commands are: list, add, remove"; }
}
}
1;

View File

@ -0,0 +1,97 @@
# File: BlackList.pm
#
# Purpose: Command to manage list of hostmasks that are not allowed
# to join a channel.
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
package PBot::Core::Commands::BlackList;
use parent 'PBot::Core::Class';
use PBot::Imports;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->cmd_blacklist(@_) }, "blacklist", 1);
# add capability to admin group
$self->{pbot}->{capabilities}->add('admin', 'can-blacklist', 1);
}
sub cmd_blacklist {
my ($self, $context) = @_;
my $arglist = $context->{arglist};
$self->{pbot}->{interpreter}->lc_args($arglist);
my $command = $self->{pbot}->{interpreter}->shift_arg($arglist);
if (not defined $command) {
return "Usage: blacklist <command>, where commands are: list/show, add, remove";
}
my $blacklist = $self->{pbot}->{blacklist}->{storage};
given ($command) {
when ($_ eq "list" or $_ eq "show") {
my $text = "Blacklist:\n";
my $entries = 0;
foreach my $channel (sort keys %$blacklist) {
if ($channel eq '.*') {
$text .= " all channels:\n";
} else {
$text .= " $channel:\n";
}
foreach my $mask (sort keys %{$blacklist->{$channel}}) {
$text .= " $mask,\n";
$entries++;
}
}
$text .= "none" if $entries == 0;
return "/msg $context->{nick} $text";
}
when ("add") {
my ($mask, $channel) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
if (not defined $mask) {
return "Usage: blacklist add <hostmask regex> [channel]";
}
$channel = '.*' if not defined $channel;
$self->{pbot}->{logger}->log("$context->{hostmask} added [$mask] to blacklist for channel [$channel]\n");
$self->add($channel, $mask);
return "/say $mask blacklisted in channel $channel";
}
when ("remove") {
my ($mask, $channel) = $self->{pbot}->{interpreter}->split_args($arglist, 2);
if (not defined $mask) {
return "Usage: blacklist remove <hostmask regex> [channel]";
}
$channel = '.*' if not defined $channel;
if (exists $blacklist->{$channel} and not exists $blacklist->{$channel}->{$mask}) {
return "/say $mask not found in blacklist for channel $channel (use `blacklist list` to display blacklist)";
}
$self->remove($channel, $mask);
$self->{pbot}->{logger}->log("$context->{hostmask} removed $mask from blacklist for channel $channel\n");
return "/say $mask removed from blacklist for channel $channel";
}
default {
return "Unknown command '$command'; commands are: list/show, add, remove";
}
}
}
1;

View File

@ -0,0 +1,56 @@
# File: Func.pm
#
# Purpose: Special `func` command that executes built-in functions with
# optional arguments. Usage: func <identifier> [arguments].
#
# Intended usage is with command-substitution (&{}) or pipes (|{}).
#
# For example:
#
# factadd img /call echo https://google.com/search?q=&{func uri_escape $args}&tbm=isch
#
# The above would invoke the function 'uri_escape' on $args and then replace
# the command-substitution with the result, thus escaping $args to be safely
# used in the URL of this simple Google Image Search factoid command.
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
package PBot::Core::Commands::Func;
use parent 'PBot::Core::Class';
use PBot::Imports;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->cmd_func(@_) }, 'func', 0);
}
sub cmd_func {
my ($self, $context) = @_;
my $func = $self->{pbot}->{interpreter}->shift_arg($context->{arglist});
if (not defined $func) {
return "Usage: func <keyword> [arguments]; see also: func help";
}
if (not exists $self->{pbot}->{functions}->{funcs}->{$func}) {
return "[No such func '$func']"
}
my @params;
while (defined(my $param = $self->{pbot}->{interpreter}->shift_arg($context->{arglist}))) {
push @params, $param;
}
my $result = $self->{pbot}->{functions}->{funcs}->{$func}->{subref}->(@params);
$result =~ s/\x1/1/g; # strip CTCP code
return $result;
}
1;

View File

@ -0,0 +1,101 @@
# File: IgnoreList.pm
#
# Purpose: Commands to manage ignore list.
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
package PBot::Core::Commands::IgnoreList;
use parent 'PBot::Core::Class';
use PBot::Imports;
use Time::Duration qw/concise duration/;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->cmd_ignore(@_) }, "ignore", 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_unignore(@_) }, "unignore", 1);
# add capabilites to admin group
$self->{pbot}->{capabilities}->add('admin', 'can-ignore', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-unignore', 1);
# add capabilities to chanop group
$self->{pbot}->{capabilities}->add('chanop', 'can-ignore', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-unignore', 1);
}
sub cmd_ignore {
my ($self, $context) = @_;
my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 3);
if (not defined $target) {
return "Usage: ignore <hostmask> [channel [timeout]] | ignore list";
}
if ($target =~ /^list$/i) {
my $text = "Ignored:\n\n";
my $now = time;
my $ignored = 0;
my $ignorelist = $self->{pbot}->{ignorelist}->{storage};
foreach my $channel (sort $ignorelist->get_keys) {
$text .= $channel eq '.*' ? "global:\n" : "$channel:\n";
my @list;
foreach my $hostmask (sort $ignorelist->get_keys($channel)) {
my $timeout = $ignorelist->get_data($channel, $hostmask, 'timeout');
if ($timeout == -1) {
push @list, " $hostmask";
} else {
push @list, " $hostmask (" . (concise duration $timeout - $now) . ')';
}
$ignored++;
}
$text .= join ";\n", @list;
$text .= "\n";
}
return "Ignore list is empty." if not $ignored;
return "/msg $context->{nick} $text";
}
if (not defined $channel) {
$channel = ".*"; # all channels
}
if (not defined $length) {
$length = -1; # permanently
} else {
my $error;
($length, $error) = $self->{pbot}->{parsedate}->parsedate($length);
return $error if defined $error;
}
return $self->{pbot}->{ignorelist}->add($channel, $target, $length, $context->{hostmask});
}
sub cmd_unignore {
my ($self, $context) = @_;
my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2);
if (not defined $target) {
return "Usage: unignore <hostmask> [channel]";
}
if (not defined $channel) {
$channel = '.*';
}
return $self->{pbot}->{ignorelist}->remove($channel, $target);
}
1;

View File

@ -27,7 +27,6 @@ sub initialize {
$self->{pbot}->{commands}->register(sub { $self->cmd_die(@_) }, 'die', 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_export(@_) }, 'export', 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_eval(@_) }, 'eval', 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_reload(@_) }, 'reload', 1);
# misc capabilities
$self->{pbot}->{capabilities}->add('admin', 'can-in', 1);
@ -149,75 +148,4 @@ sub cmd_eval {
return "/say $ret $result";
}
sub cmd_reload {
my ($self, $context) = @_;
my %reloadables = (
'capabilities' => sub {
$self->{pbot}->{capabilities}->{caps}->load;
return "Capabilities reloaded.";
},
'commands' => sub {
$self->{pbot}->{commands}->{metadata}->load;
return "Commands metadata reloaded.";
},
'blacklist' => sub {
$self->{pbot}->{blacklist}->clear_blacklist;
$self->{pbot}->{blacklist}->load_blacklist;
return "Blacklist reloaded.";
},
'ban-exemptions' => sub {
$self->{pbot}->{antiflood}->{'ban-exemptions'}->load;
return "Ban exemptions reloaded.";
},
'ignores' => sub {
$self->{pbot}->{ignorelist}->{storage}->load;
return "Ignore list reloaded.";
},
'users' => sub {
$self->{pbot}->{users}->load;
return "Users reloaded.";
},
'channels' => sub {
$self->{pbot}->{channels}->{storage}->load;
return "Channels reloaded.";
},
'banlist' => sub {
$self->{pbot}->{event_queue}->dequeue_event('unban #.*');
$self->{pbot}->{event_queue}->dequeue_event('unmute #.*');
$self->{pbot}->{banlist}->{banlist}->load;
$self->{pbot}->{banlist}->{quietlist}->load;
$self->{pbot}->{banlist}->enqueue_timeouts($self->{pbot}->{banlist}->{banlist}, 'b');
$self->{pbot}->{banlist}->enqueue_timeouts($self->{pbot}->{banlist}->{quietlist}, 'q');
return "Ban list reloaded.";
},
'registry' => sub {
$self->{pbot}->{registry}->load;
return "Registry reloaded.";
},
'factoids' => sub {
$self->{pbot}->{factoids}->load_factoids;
return "Factoids reloaded.";
}
);
if (not length $context->{arguments} or not exists $reloadables{$context->{arguments}}) {
my $usage = 'Usage: reload <';
$usage .= join '|', sort keys %reloadables;
$usage .= '>';
return $usage;
}
return $reloadables{$context->{arguments}}();
}
1;

View File

@ -0,0 +1,90 @@
# File: Reload.pm
#
# Purpose: Command to reload various PBot storage files.
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
package PBot::Core::Commands::Reload;
use PBot::Imports;
use parent 'PBot::Core::Class';
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->cmd_reload(@_) }, 'reload', 1);
}
sub cmd_reload {
my ($self, $context) = @_;
my %reloadables = (
'capabilities' => sub {
$self->{pbot}->{capabilities}->{caps}->load;
return "Capabilities reloaded.";
},
'commands' => sub {
$self->{pbot}->{commands}->{metadata}->load;
return "Commands metadata reloaded.";
},
'blacklist' => sub {
$self->{pbot}->{blacklist}->clear_blacklist;
$self->{pbot}->{blacklist}->load_blacklist;
return "Blacklist reloaded.";
},
'ban-exemptions' => sub {
$self->{pbot}->{banlist}->{'ban-exemptions'}->load;
return "Ban exemptions reloaded.";
},
'ignores' => sub {
$self->{pbot}->{ignorelist}->{storage}->load;
return "Ignore list reloaded.";
},
'users' => sub {
$self->{pbot}->{users}->load;
return "Users reloaded.";
},
'channels' => sub {
$self->{pbot}->{channels}->{storage}->load;
return "Channels reloaded.";
},
'banlist' => sub {
$self->{pbot}->{event_queue}->dequeue_event('unban #.*');
$self->{pbot}->{event_queue}->dequeue_event('unmute #.*');
$self->{pbot}->{banlist}->{banlist}->load;
$self->{pbot}->{banlist}->{quietlist}->load;
$self->{pbot}->{banlist}->enqueue_timeouts($self->{pbot}->{banlist}->{banlist}, 'b');
$self->{pbot}->{banlist}->enqueue_timeouts($self->{pbot}->{banlist}->{quietlist}, 'q');
return "Ban list reloaded.";
},
'registry' => sub {
$self->{pbot}->{registry}->load;
return "Registry reloaded.";
},
'factoids' => sub {
$self->{pbot}->{factoids}->load_factoids;
return "Factoids reloaded.";
}
);
if (not length $context->{arguments} or not exists $reloadables{$context->{arguments}}) {
my $usage = 'Usage: reload <';
$usage .= join '|', sort keys %reloadables;
$usage .= '>';
return $usage;
}
return $reloadables{$context->{arguments}}();
}
1;

View File

@ -12,6 +12,8 @@
# The above would invoke the function 'uri_escape' on $args and then replace
# the command-substitution with the result, thus escaping $args to be safely
# used in the URL of this simple Google Image Search factoid command.
#
# See also: Plugin/FuncBuiltins.pm, Plugin/FuncGrep.pm and Plugin/FuncSed.pm
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
@ -24,16 +26,8 @@ use PBot::Imports;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->cmd_func(@_) }, 'func', 0);
$self->register(
'help',
{
desc => 'provides help about a func',
usage => 'help [func]',
subref => sub { $self->func_help(@_) }
}
);
# register `list` and `help` functions used to list
# functions and obtain help about them
$self->register(
'list',
@ -43,32 +37,15 @@ sub initialize {
subref => sub { $self->func_list(@_) }
}
);
}
sub cmd_func {
my ($self, $context) = @_;
my $func = $self->{pbot}->{interpreter}->shift_arg($context->{arglist});
if (not defined $func) {
return "Usage: func <keyword> [arguments]; see also: func help";
}
if (not exists $self->{funcs}->{$func}) {
return "[No such func '$func']"
}
my @params;
while (defined(my $param = $self->{pbot}->{interpreter}->shift_arg($context->{arglist}))) {
push @params, $param;
}
my $result = $self->{funcs}->{$func}->{subref}->(@params);
$result =~ s/\x1/1/g; # strip CTCP code
return $result;
$self->register(
'help',
{
desc => 'provides help about a func',
usage => 'help [func]',
subref => sub { $self->func_help(@_) }
}
);
}
sub register {
@ -81,20 +58,6 @@ sub unregister {
delete $self->{funcs}->{$func};
}
sub func_help {
my ($self, $func) = @_;
if (not length $func) {
return "func: invoke built-in functions; usage: func <keyword> [arguments]; to list available functions: func list [regex]";
}
if (not exists $self->{funcs}->{$func}) {
return "No such func '$func'.";
}
return "$func: $self->{funcs}->{$func}->{desc}; usage: $self->{funcs}->{$func}->{usage}";
}
sub func_list {
my ($self, $regex) = @_;
@ -131,4 +94,18 @@ sub func_list {
return $result;
}
sub func_help {
my ($self, $func) = @_;
if (not length $func) {
return "func: invoke built-in functions; usage: func <keyword> [arguments]; to list available functions: func list [regex]";
}
if (not exists $self->{funcs}->{$func}) {
return "No such func '$func'.";
}
return "$func: $self->{funcs}->{$func}->{desc}; usage: $self->{funcs}->{$func}->{usage}";
}
1;

View File

@ -10,79 +10,21 @@ use parent 'PBot::Core::Class';
use PBot::Imports;
use Time::Duration qw/concise duration/;
use Time::Duration qw/duration/;
sub initialize {
my ($self, %conf) = @_;
$self->{filename} = $conf{filename};
$self->{storage} = PBot::Storage::DualIndexHashObject->new(pbot => $self->{pbot}, name => 'IgnoreList', filename => $self->{filename});
$self->{storage} = PBot::Storage::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'IgnoreList',
filename => $self->{filename}
);
$self->{storage}->load;
$self->enqueue_ignores;
$self->{pbot}->{commands}->register(sub { $self->cmd_ignore(@_) }, "ignore", 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_unignore(@_) }, "unignore", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-ignore', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-unignore', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-ignore', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-unignore', 1);
}
sub cmd_ignore {
my ($self, $context) = @_;
my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 3);
return "Usage: ignore <hostmask> [channel [timeout]] | ignore list" if not defined $target;
if ($target =~ /^list$/i) {
my $text = "Ignored:\n\n";
my $now = time;
my $ignored = 0;
foreach my $channel (sort $self->{storage}->get_keys) {
$text .= $channel eq '.*' ? "global:\n" : "$channel:\n";
my @list;
foreach my $hostmask (sort $self->{storage}->get_keys($channel)) {
my $timeout = $self->{storage}->get_data($channel, $hostmask, 'timeout');
if ($timeout == -1) {
push @list, " $hostmask";
} else {
push @list, " $hostmask (" . (concise duration $timeout - $now) . ')';
}
$ignored++;
}
$text .= join ";\n", @list;
$text .= "\n";
}
return "Ignore list is empty." if not $ignored;
return "/msg $context->{nick} $text";
}
if (not defined $channel) {
$channel = ".*"; # all channels
}
if (not defined $length) {
$length = -1; # permanently
} else {
my $error;
($length, $error) = $self->{pbot}->{parsedate}->parsedate($length);
return $error if defined $error;
}
return $self->add($channel, $target, $length, $context->{hostmask});
}
sub cmd_unignore {
my ($self, $context) = @_;
my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2);
if (not defined $target) { return "Usage: unignore <hostmask> [channel]"; }
if (not defined $channel) { $channel = '.*'; }
return $self->remove($channel, $target);
}
sub enqueue_ignores {