3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-11-19 10:29:30 +01:00

IgnoreList: refactored to use DualIndexHashObject

This commit is contained in:
Pragmatic Software 2020-03-04 13:24:40 -08:00
parent c46783eca1
commit 6c4f56f9df
6 changed files with 119 additions and 246 deletions

View File

@ -8,180 +8,155 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::IgnoreList; package PBot::IgnoreList;
use parent 'PBot::Class'; use parent 'PBot::Class';
use warnings; use strict; use warnings; use strict;
use feature 'unicode_strings'; use feature 'unicode_strings';
use PBot::IgnoreListCommands; use Time::Duration qw/concise duration/;
use Time::HiRes qw(gettimeofday);
sub initialize { sub initialize {
my ($self, %conf) = @_; my ($self, %conf) = @_;
$self->{filename} = $conf{filename}; $self->{filename} = $conf{filename};
$self->{ignore_list} = {}; $self->{ignorelist} = PBot::DualIndexHashObject->new(pbot => $self->{pbot}, name => 'IgnoreList', filename => $self->{filename});
$self->{ignore_flood_counter} = {}; $self->{ignorelist}->load;
$self->{last_timestamp} = {};
$self->{commands} = PBot::IgnoreListCommands->new(pbot => $self->{pbot}); $self->{pbot}->{commands}->register(sub { $self->ignore_cmd(@_) }, "ignore", 1);
$self->load_ignores(); $self->{pbot}->{commands}->register(sub { $self->unignore_cmd(@_) }, "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);
$self->{pbot}->{timer}->register(sub { $self->check_ignore_timeouts }, 10); $self->{pbot}->{timer}->register(sub { $self->check_ignore_timeouts }, 10);
} }
sub add { sub add {
my $self = shift; my ($self, $channel, $hostmask, $length, $owner) = @_;
my ($hostmask, $channel, $length) = @_;
if ($length < 0) { $self->{ignore_list}->{$hostmask}->{$channel} = -1; } if ($hostmask !~ /!/) {
else { $self->{ignore_list}->{$hostmask}->{$channel} = gettimeofday + $length; } $hostmask .= '!*@*';
} elsif ($hostmask !~ /@/) {
$hostmask .= '@*';
}
$self->save_ignores(); my $regex = quotemeta $hostmask;
$regex =~ s/\\\*/.*/g;
$regex =~ s/\\\?/./g;
my $data = {
owner => $owner,
created_on => time,
regex => $regex,
};
if ($length < 0) {
$data->{timeout} = -1;
} else {
$data->{timeout} = time + $length;
}
$self->{ignorelist}->add($channel, $hostmask, $data);
my $duration = $data->{timeout} == -1 ? 'all eternity' : duration $length;
return "$hostmask ignored for $duration";
} }
sub remove { sub remove {
my $self = shift; my ($self, $channel, $hostmask) = @_;
my ($hostmask, $channel) = @_; return $self->{ignorelist}->remove($channel, $hostmask);
delete $self->{ignore_list}->{$hostmask}->{$channel};
if (not keys %{$self->{ignore_list}->{$hostmask}}) { delete $self->{ignore_list}->{$hostmask}; }
$self->save_ignores();
} }
sub clear_ignores { sub is_ignored {
my $self = shift; my ($self, $channel, $hostmask) = @_;
$self->{ignore_list} = {};
}
sub load_ignores { return 0 if $self->{pbot}->{users}->loggedin_admin($channel, $hostmask);
my $self = shift;
my $filename;
if (@_) { $filename = shift; } foreach my $chan ('.*', $channel) {
else { $filename = $self->{filename}; } foreach my $ignored ($self->{ignorelist}->get_keys($chan)) {
my $regex = $self->{ignorelist}->get_data($chan, $ignored, 'regex');
if (not defined $filename) { return 1 if $hostmask =~ /^$regex$/i;
Carp::carp "No ignorelist path specified -- skipping loading of ignorelist";
return;
}
$self->{pbot}->{logger}->log("Loading ignorelist from $filename ...\n");
open(FILE, "< $filename") or Carp::croak "Couldn't open $filename: $!\n";
my @contents = <FILE>;
close(FILE);
my $i = 0;
foreach my $line (@contents) {
chomp $line;
$i++;
my ($hostmask, $channel, $length) = split(/\s+/, $line);
if (not defined $hostmask || not defined $channel || not defined $length) { Carp::croak "Syntax error around line $i of $filename\n"; }
if (exists ${$self->{ignore_list}}{$hostmask}{$channel}) { Carp::croak "Duplicate ignore [$hostmask][$channel] found in $filename around line $i\n"; }
$self->{ignore_list}->{$hostmask}->{$channel} = $length;
}
$self->{pbot}->{logger}->log(" $i entries in ignorelist\n");
}
sub save_ignores {
my $self = shift;
my $filename;
if (@_) { $filename = shift; }
else { $filename = $self->{filename}; }
if (not defined $filename) {
Carp::carp "No ignorelist path specified -- skipping saving of ignorelist\n";
return;
}
open(FILE, "> $filename") or die "Couldn't open $filename: $!\n";
foreach my $hostmask (keys %{$self->{ignore_list}}) {
foreach my $channel (keys %{$self->{ignore_list}->{$hostmask}}) {
my $length = $self->{ignore_list}->{$hostmask}->{$channel};
print FILE "$hostmask $channel $length\n";
} }
} }
close(FILE);
}
sub check_ignore {
my $self = shift;
my ($nick, $user, $host, $channel, $silent) = @_;
my $pbot = $self->{pbot};
$channel = lc $channel;
my $hostmask = "$nick!$user\@$host";
my $now = gettimeofday;
if (defined $channel) { # do not execute following if text is coming from STDIN ($channel undef)
if ($channel =~ /^#/) { $self->{ignore_flood_counter}->{$channel}++; }
if (not exists $self->{last_timestamp}->{$channel}) { $self->{last_timestamp}->{$channel} = $now; }
elsif ($now - $self->{last_timestamp}->{$channel} >= 30) {
$self->{last_timestamp}->{$channel} = $now;
if (exists $self->{ignore_flood_counter}->{$channel} and $self->{ignore_flood_counter}->{$channel} > 0) { $self->{ignore_flood_counter}->{$channel} = 0; }
}
=cut
if (exists $self->{ignore_flood_counter}->{$channel} and $self->{ignore_flood_counter}->{$channel} > 5) {
$self->{commands}->ignore_user("", "floodcontrol", "", "", ".* $channel 300");
$self->{ignore_flood_counter}->{$channel} = 0;
if ($channel =~ /^#/) {
$pbot->{conn}->me($channel, "has been overwhelmed.");
$pbot->{conn}->me($channel, "lies down and falls asleep.");
return 1;
}
}
=cut
}
foreach my $ignored (keys %{$self->{ignore_list}}) {
foreach my $ignored_channel (keys %{$self->{ignore_list}->{$ignored}}) {
my $ignored_channel_escaped = quotemeta $ignored_channel;
my $ignored_escaped = quotemeta $ignored;
$ignored_channel_escaped =~ s/\\(\.|\*)/$1/g;
$ignored_escaped =~ s/\\(\.|\*)/$1/g;
if (($channel =~ /$ignored_channel_escaped/i) && ($hostmask =~ /$ignored_escaped/i)) {
$self->{pbot}->{logger}->log("$nick!$user\@$host message ignored in channel $channel (matches [$ignored] host and [$ignored_channel] channel)\n") unless $silent;
return 1;
}
}
}
return 0; return 0;
} }
sub check_ignore_timeouts { sub check_ignore_timeouts {
my $self = shift; my ($self) = @_;
my $now = gettimeofday(); my $now = time;
foreach my $hostmask (keys %{$self->{ignore_list}}) { foreach my $channel ($self->{ignorelist}->get_keys) {
foreach my $channel (keys %{$self->{ignore_list}->{$hostmask}}) { foreach my $hostmask ($self->{ignorelist}->get_keys($channel)) {
next if ($self->{ignore_list}->{$hostmask}->{$channel} == -1); #permanent ignore my $timeout = $self->{ignorelist}->get_data($channel, $hostmask, 'timeout');
next if $timeout == -1; # permanent ignore
if ($self->{ignore_list}->{$hostmask}->{$channel} < $now) { if ($now >= $timeout) {
$self->{pbot}->{logger}->log("Unignoring $hostmask in channel $channel.\n"); $self->{pbot}->{logger}->log("Unignoring $hostmask in channel $channel.\n");
$self->remove($hostmask, $channel); $self->remove($channel, $hostmask);
if ($hostmask eq ".*") { $self->{pbot}->{conn}->me($channel, "awakens."); }
} }
} }
} }
} }
sub ignore_cmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
return "Usage: ignore <hostmask> [channel [timeout]] | ignore list" if not defined $target;
if ($target =~ /^list$/i) {
my $text = "Ignored: ";
my $now = time;
my $ignored = 0;
foreach my $channel (sort $self->{ignorelist}->get_keys) {
$text .= $channel eq '.*' ? "global:\n" : "$channel:\n";
my @list = ();
foreach my $hostmask (sort $self->{ignorelist}->get_keys($channel)) {
my $timeout = $self->{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;
}
return "Ignore list is empty." if not $ignored;
return "/msg $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, "$nick!$user\@$host");
}
sub unignore_cmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
if (not defined $target) { return "Usage: unignore <hostmask> [channel]"; }
if (not defined $channel) { $channel = '.*'; }
return $self->remove($channel, $target);
}
1; 1;

View File

@ -1,98 +0,0 @@
# File: IgnoreListCommands.pm
# Author: pragma_
#
# Purpose: Bot commands for interfacing with ignore list.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::IgnoreListCommands;
use parent 'PBot::Class';
use warnings; use strict;
use feature 'unicode_strings';
use Time::HiRes qw(gettimeofday);
use Time::Duration;
sub initialize {
my ($self, %conf) = @_;
$self->{pbot}->{commands}->register(sub { $self->ignore_user(@_) }, "ignore", 1);
$self->{pbot}->{commands}->register(sub { $self->unignore_user(@_) }, "unignore", 1);
$self->{pbot}->{capabilities}->add('admin', 'can-ignore', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-unignore', 1);
}
sub ignore_user {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
return "Usage: ignore <hostmask> [channel [timeout]]" if not defined $arguments;
my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
if (not defined $target) { return "Usage: ignore <hostmask> [channel [timeout]]"; }
if ($target =~ /^list$/i) {
my $text = "Ignored: ";
my $sep = "";
foreach my $ignored (sort keys %{$self->{pbot}->{ignorelist}->{ignore_list}}) {
foreach my $channel (sort keys %{${$self->{pbot}->{ignorelist}->{ignore_list}}{$ignored}}) {
$text .=
$sep
. "$ignored [$channel] "
. (
$self->{pbot}->{ignorelist}->{ignore_list}->{$ignored}->{$channel} < 0
? "perm"
: duration($self->{pbot}->{ignorelist}->{ignore_list}->{$ignored}->{$channel} - gettimeofday)
);
$sep = ";\n";
}
}
return "/msg $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;
}
$self->{pbot}->{ignorelist}->add($target, $channel, $length);
if ($length >= 0) { $length = "for " . duration($length); }
else { $length = "permanently"; }
$self->{pbot}->{logger}->log("$nick added [$target][$channel] to ignore list $length\n");
return "/msg $nick [$target][$channel] added to ignore list $length";
}
sub unignore_user {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
if (not defined $target) { return "Usage: unignore <hostmask> [channel]"; }
if (not defined $channel) { $channel = ".*"; }
if (exists $self->{pbot}->{ignorelist}->{ignore_list}->{$target} and not exists $self->{pbot}->{ignorelist}->{ignore_list}->{$target}->{$channel}) {
$self->{pbot}->{logger}->log("$nick attempt to remove nonexistent [$target][$channel] from ignore list\n");
return "/msg $nick [$target][$channel] not found in ignore list (use `ignore list` to list ignores)";
}
$self->{pbot}->{ignorelist}->remove($target, $channel);
$self->{pbot}->{logger}->log("$nick removed [$target][$channel] from ignore list\n");
return "/msg $nick [$target][$channel] unignored";
}
1;

View File

@ -146,10 +146,9 @@ sub process_line {
foreach $command (@commands) { foreach $command (@commands) {
# check if user is ignored (and command isn't `login`) # check if user is ignored (and command isn't `login`)
if ($command !~ /^login / && defined $from && $pbot->{ignorelist}->check_ignore($nick, $user, $host, $from)) { if ($command !~ /^login / && defined $from && $pbot->{ignorelist}->is_ignored($from, "$nick!$user\@$host")) {
if (not $pbot->{users}->loggedin_admin($from, "$nick!$user\@$host")) { $self->{pbot}->{logger}->log("Disregarding command from ignored user $nick!$user\@$host in $from.\n");
return 1; # user is ignored return 1;
}
} }
$stuff->{text} = $text; $stuff->{text} = $text;

View File

@ -447,8 +447,7 @@ sub reload {
}, },
'ignores' => sub { 'ignores' => sub {
$self->{ignorelist}->clear_ignores; $self->{ignorelist}->{ignorelist}->load;
$self->{ignorelist}->load_ignores;
return "Ignore list reloaded."; return "Ignore list reloaded.";
}, },

View File

@ -492,9 +492,8 @@ sub on_public {
return 0 if $event->{interpreted}; return 0 if $event->{interpreted};
if ($self->{pbot}->{ignorelist}->check_ignore($nick, $user, $host, $channel, 1)) { if ($self->{pbot}->{ignorelist}->is_ignored($channel, "$nick!$user\@$host")) {
my $admin = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host"); return 0;
if (!defined $admin || $admin->{level} < 10) { return 0; }
} }
if (not $self->dbi_begin) { return 0; } if (not $self->dbi_begin) { return 0; }

View File

@ -38,9 +38,8 @@ sub show_url_titles {
return 0 if not $msg =~ m/https?:\/\/[^\s]/; return 0 if not $msg =~ m/https?:\/\/[^\s]/;
return 0 if $event->{interpreted}; return 0 if $event->{interpreted};
if ($self->{pbot}->{ignorelist}->check_ignore($nick, $user, $host, $channel)) { if ($self->{pbot}->{ignorelist}->is_ignore($channel, "$nick!$user\@$host")) {
my $admin = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host"); return 0;
if (!defined $admin || $admin->{level} < 10) { return 0; }
} }
# no titles for unidentified users in +z channels # no titles for unidentified users in +z channels