mirror of
https://github.com/pragma-/pbot.git
synced 2024-11-20 02:49:49 +01:00
Move some handlers to relevant Handlers modules
- dispatch irc.onemode event for each mode flag - misc clean-ups
This commit is contained in:
parent
1c5dc69047
commit
108c2924cc
@ -697,7 +697,8 @@ sub check_bans {
|
||||
|
||||
my $u = $self->{pbot}->{users}->loggedin($baninfo->{channel}, $mask);
|
||||
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
|
||||
if ($self->{pbot}->{banlist}->ban_exempted($baninfo->{channel}, $baninfo->{mask}) || $whitelisted) {
|
||||
|
||||
if ($self->{pbot}->{banlist}->is_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;
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
# File: BanList.pm
|
||||
#
|
||||
# Purpose: Populates and maintains channel banlists by checking mode +b/+q on
|
||||
# joining channels and by tracking modes +b/+q and -b/-q in channels. Keeps
|
||||
# track of remaining duration for timed bans/quiets. Handles ban/unban queue.
|
||||
# Purpose: Implements functions related to maintaining and tracking channel
|
||||
# bans/mutes. Maintains ban/mute queues and timeouts.
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -25,12 +24,6 @@ sub initialize {
|
||||
$self->{pbot}->{registry}->add_default('text', 'banlist', 'debug', '0');
|
||||
$self->{pbot}->{registry}->add_default('text', 'banlist', 'mute_mode_char', 'q');
|
||||
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofnames', sub { $self->get_banlist(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.banlist', sub { $self->on_banlist_entry(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.quietlist', sub { $self->on_quietlist_entry(@_) });
|
||||
$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::Core::Storage::DualIndexHashObject->new(
|
||||
@ -66,226 +59,43 @@ sub initialize {
|
||||
$self->{pbot}->{event_queue}->enqueue(sub { $self->flush_unban_queue }, 30, 'Flush unban queue');
|
||||
}
|
||||
|
||||
sub get_banlist {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
$self->{pbot}->{logger}->log("Retrieving banlist for $channel.\n");
|
||||
delete $self->{temp_banlist};
|
||||
sub checkban {
|
||||
my ($self, $channel, $mode, $mask) = @_;
|
||||
|
||||
my $mute_char = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
|
||||
$mask = $self->nick_to_banmask($mask);
|
||||
|
||||
if ($mute_char eq 'b') {
|
||||
$event->{conn}->sl("mode $channel +b");
|
||||
} else {
|
||||
$event->{conn}->sl("mode $channel +b$mute_char");
|
||||
my $data;
|
||||
|
||||
if ($mode eq 'b') {
|
||||
$data = $self->{banlist}->get_data($channel, $mask);
|
||||
} elsif ($mode eq $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char')) {
|
||||
$data = $self->{quietlist}->get_data($channel, $mask);
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (not defined $data) {
|
||||
return "$mask is not " . ($mode eq 'b' ? 'banned' : 'muted') . ".";
|
||||
}
|
||||
|
||||
my $result = "$mask " . ($mode eq 'b' ? 'banned' : 'quieted') . " in $channel ";
|
||||
|
||||
if (defined $data->{timestamp}) {
|
||||
my $date = strftime "%a %b %e %H:%M:%S %Y %Z", localtime $data->{timestamp};
|
||||
my $ago = concise ago (time - $data->{timestamp});
|
||||
$result .= "on $date ($ago) ";
|
||||
}
|
||||
|
||||
$result .= "by $data->{owner} " if defined $data->{owner};
|
||||
$result .= "for $data->{reason} " if defined $data->{reason};
|
||||
|
||||
if (exists $data->{timeout} and $data->{timeout} > 0) {
|
||||
my $duration = concise duration($data->{timeout} - gettimeofday);
|
||||
$result .= "($duration remaining)";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub on_banlist_entry {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
my $target = lc $event->{event}->{args}[2];
|
||||
my $source = lc $event->{event}->{args}[3];
|
||||
my $timestamp = $event->{event}->{args}[4];
|
||||
|
||||
my $ago = concise ago(gettimeofday - $timestamp);
|
||||
$self->{pbot}->{logger}->log("Ban List: [banlist entry] $channel: $target banned by $source $ago.\n");
|
||||
$self->{temp_banlist}->{$channel}->{'+b'}->{$target} = [$source, $timestamp];
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub on_quietlist_entry {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
my $target = lc $event->{event}->{args}[3];
|
||||
my $source = lc $event->{event}->{args}[4];
|
||||
my $timestamp = $event->{event}->{args}[5];
|
||||
|
||||
my $ago = concise ago(gettimeofday - $timestamp);
|
||||
$self->{pbot}->{logger}->log("Ban List: [quietlist entry] $channel: $target quieted by $source $ago.\n");
|
||||
my $mute_char = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
|
||||
$self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$target} = [$source, $timestamp];
|
||||
return 0;
|
||||
}
|
||||
|
||||
# irc.endofbanlist
|
||||
sub compare_banlist {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
|
||||
# first check for saved bans no longer in channel
|
||||
foreach my $mask ($self->{banlist}->get_keys($channel)) {
|
||||
if (not exists $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}) {
|
||||
$self->{pbot}->{logger}->log("BanList: Saved ban +b $mask no longer exists in $channel.\n");
|
||||
# TODO option to restore ban
|
||||
$self->{banlist}->remove($channel, $mask, undef, 1);
|
||||
$self->{pbot}->{event_queue}->dequeue_event("unban $channel $mask");
|
||||
}
|
||||
}
|
||||
|
||||
# add channel bans to saved bans
|
||||
foreach my $mask (keys %{$self->{temp_banlist}->{$channel}->{'+b'}}) {
|
||||
my $data = $self->{banlist}->get_data($channel, $mask);
|
||||
$data->{owner} = $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}->[0];
|
||||
$data->{timestamp} = $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}->[1];
|
||||
|
||||
# make some special-case bans temporary
|
||||
if (not defined $data->{timeout} and $self->{pbot}->{chanops}->can_gain_ops($channel)) {
|
||||
if ($mask =~ m/^\*!\*@/ or $mask =~ m/^\*!.*\@gateway\/web/i) {
|
||||
my $timeout = 60 * 60 * 24 * 7;
|
||||
|
||||
# permanent bans for cloaks that aren't gateway
|
||||
$timeout = 0 if $mask =~ m/\// and $mask !~ m/\@gateway/;
|
||||
|
||||
if ($timeout) {
|
||||
$self->{pbot}->{logger}->log("Temp ban for $mask in $channel.\n");
|
||||
$data->{timeout} = gettimeofday + $timeout;
|
||||
$self->enqueue_unban($channel, 'b', $mask, $timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->{banlist}->add($channel, $mask, $data, 1);
|
||||
}
|
||||
|
||||
$self->{banlist}->save if keys %{$self->{temp_banlist}->{$channel}->{'+b'}};
|
||||
delete $self->{temp_banlist}->{$channel}->{'+b'};
|
||||
}
|
||||
|
||||
# irc.endofquietlist
|
||||
sub compare_quietlist {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
|
||||
my $mute_char = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
|
||||
|
||||
# first check for saved quiets no longer in channel
|
||||
foreach my $mask ($self->{quietlist}->get_keys($channel)) {
|
||||
if (not exists $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}) {
|
||||
$self->{pbot}->{logger}->log("BanList: Saved quiet +q $mask no longer exists in $channel.\n");
|
||||
# TODO option to restore quiet
|
||||
$self->{quietlist}->remove($channel, $mask, undef, 1);
|
||||
$self->{pbot}->{event_queue}->dequeue_event("unmute $channel $mask");
|
||||
}
|
||||
}
|
||||
|
||||
# add channel bans to saved bans
|
||||
foreach my $mask (keys %{$self->{temp_banlist}->{$channel}->{"+$mute_char"}}) {
|
||||
my $data = $self->{quietlist}->get_data($channel, $mask);
|
||||
$data->{owner} = $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}->[0];
|
||||
$data->{timestamp} = $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}->[1];
|
||||
$self->{quietlist}->add($channel, $mask, $data, 1);
|
||||
}
|
||||
|
||||
$self->{quietlist}->save if keys %{$self->{temp_banlist}->{$channel}->{"+$mute_char"}};
|
||||
delete $self->{temp_banlist}->{$channel}->{"+$mute_char"};
|
||||
}
|
||||
|
||||
sub track_mode {
|
||||
my $self = shift;
|
||||
my ($source, $channel, $mode, $mask) = @_;
|
||||
|
||||
my ($nick) = $source =~ /(^[^!]+)/;
|
||||
$channel = defined $channel ? lc $channel : '';
|
||||
$mask = defined $mask ? lc $mask : '';
|
||||
|
||||
my $mute_char = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
|
||||
|
||||
if ($mode eq "+b" or $mode eq "+$mute_char") {
|
||||
$self->{pbot}->{logger}->log("Ban List: $mask " . ($mode eq '+b' ? 'banned' : 'muted') . " by $source in $channel.\n");
|
||||
|
||||
my $data = {
|
||||
owner => $source,
|
||||
timestamp => scalar gettimeofday,
|
||||
};
|
||||
|
||||
if ($mode eq "+b") {
|
||||
$self->{banlist}->add($channel, $mask, $data);
|
||||
} elsif ($mode eq "+$mute_char") {
|
||||
$self->{quietlist}->add($channel, $mask, $data);
|
||||
}
|
||||
|
||||
$self->{pbot}->{antiflood}->devalidate_accounts($mask, $channel);
|
||||
} elsif ($mode eq "-b" or $mode eq "-$mute_char") {
|
||||
$self->{pbot}->{logger}->log("Ban List: $mask " . ($mode eq '-b' ? 'unbanned' : 'unmuted') . " by $source in $channel.\n");
|
||||
|
||||
if ($mode eq "-b") {
|
||||
$self->{banlist}->remove($channel, $mask);
|
||||
$self->{pbot}->{event_queue}->dequeue_event("unban $channel $mask");
|
||||
|
||||
# freenode strips channel forwards from unban result if no ban exists with a channel forward
|
||||
my $join_flood_channel = $self->{pbot}->{registry}->get_value('antiflood', 'join_flood_channel') // '#stop-join-flood';
|
||||
$self->{banlist}->remove($channel, "$mask\$$join_flood_channel");
|
||||
$self->{pbot}->{event_queue}->dequeue_event(lc "unban $channel $mask\$$join_flood_channel");
|
||||
} elsif ($mode eq "-$mute_char") {
|
||||
$self->{quietlist}->remove($channel, $mask);
|
||||
$self->{pbot}->{event_queue}->dequeue_event("unmute $channel $mask");
|
||||
}
|
||||
}
|
||||
|
||||
return if not $self->{pbot}->{chanops}->can_gain_ops($channel);
|
||||
|
||||
if ($mode eq "+b") {
|
||||
if ($nick eq "ChanServ" or $mask =~ m/##fix_your_connection$/i) {
|
||||
if ($self->{banlist}->exists($channel, $mask)) {
|
||||
$self->{banlist}->set($channel, $mask, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
$self->{pbot}->{event_queue}->update_interval("unban $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
} else {
|
||||
my $data = {
|
||||
reason => 'Temp ban for banned-by-ChanServ or mask is *!*@*##fix_your_connection',
|
||||
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
|
||||
timeout => gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'),
|
||||
timestamp => gettimeofday,
|
||||
};
|
||||
$self->{banlist}->add($channel, $mask, $data);
|
||||
$self->enqueue_unban($channel, 'b', $mask, $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
}
|
||||
} elsif ($mask =~ m/^\*!\*@/ or $mask =~ m/^\*!.*\@gateway\/web/i) {
|
||||
my $timeout = 60 * 60 * 24 * 7;
|
||||
|
||||
if ($mask =~ m/\// and $mask !~ m/\@gateway/) {
|
||||
$timeout = 0; # permanent bans for cloaks that aren't gateway
|
||||
}
|
||||
|
||||
if ($timeout) {
|
||||
if (not $self->{banlist}->exists($channel, $mask)) {
|
||||
$self->{pbot}->{logger}->log("Temp ban for $mask in $channel.\n");
|
||||
my $data = {
|
||||
reason => 'Temp ban for *!*@host',
|
||||
timeout => gettimeofday + $timeout,
|
||||
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
|
||||
timestamp => gettimeofday,
|
||||
};
|
||||
$self->{banlist}->add($channel, $mask, $data);
|
||||
$self->enqueue_unban($channel, 'b', $mask, $timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elsif ($mode eq "+$mute_char") {
|
||||
if (lc $nick ne lc $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
|
||||
$self->{pbot}->{logger}->log("WEIRD MUTE THING $nick...\n");
|
||||
if ($self->{quietlist}->exists($channel, $mask)) {
|
||||
$self->{quietlist}->set($channel, $mask, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
$self->{pbot}->{event_queue}->update_interval("unmute $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
} else {
|
||||
my $data = {
|
||||
reason => 'Temp mute',
|
||||
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
|
||||
timeout => gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'mute_timeout'),
|
||||
timestamp => gettimeofday,
|
||||
};
|
||||
$self->{quietlist}->add($channel, $mask, $data);
|
||||
$self->enqueue_unban($channel, $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char'), $mask, $self->{pbot}->{registry}->get_value('banlist', 'mute_timeout'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub ban_exempted {
|
||||
sub is_ban_exempted {
|
||||
my ($self, $channel, $hostmask) = @_;
|
||||
$channel = lc $channel;
|
||||
$hostmask = lc $hostmask;
|
||||
@ -293,6 +103,88 @@ sub ban_exempted {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub is_banned {
|
||||
my ($self, $channel, $nick, $user, $host) = @_;
|
||||
|
||||
my $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
|
||||
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
|
||||
push @nickserv_accounts, undef;
|
||||
|
||||
my $banned = undef;
|
||||
|
||||
foreach my $nickserv_account (@nickserv_accounts) {
|
||||
my $baninfos = $self->get_baninfo($channel, "$nick!$user\@$host", $nickserv_account);
|
||||
|
||||
if (defined $baninfos) {
|
||||
foreach my $baninfo (@$baninfos) {
|
||||
my $u = $self->{pbot}->{users}->loggedin($baninfo->{channel}, "$nick!$user\@$host");
|
||||
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
|
||||
|
||||
if ($self->is_ban_exempted($baninfo->{channel}, $baninfo->{mask}) || $whitelisted) {
|
||||
$self->{pbot}->{logger}->log("[BanList] is_banned: $nick!$user\@$host 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("[BanList] is_banned: $nick!$user\@$host $mode as $baninfo->{mask} in $baninfo->{channel} by $baninfo->{owner}\n");
|
||||
$banned = $baninfo;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $banned;
|
||||
}
|
||||
|
||||
sub has_ban_timeout {
|
||||
my ($self, $channel, $mask, $mode) = @_;
|
||||
|
||||
$mode ||= 'b';
|
||||
|
||||
my $list = $mode eq 'b' ? $self->{banlist} : $self->{quietlist};
|
||||
|
||||
my $data = $list->get_data($channel, $mask);
|
||||
|
||||
if (defined $data && $data->{timeout} > 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub ban_user_timed {
|
||||
my ($self, $channel, $mode, $mask, $length, $owner, $reason, $immediately) = @_;
|
||||
|
||||
$channel = lc $channel;
|
||||
$mask = lc $mask;
|
||||
|
||||
$mask = $self->nick_to_banmask($mask);
|
||||
$self->ban_user($channel, $mode, $mask, $immediately);
|
||||
|
||||
my $data = {
|
||||
timeout => $length > 0 ? gettimeofday + $length : -1,
|
||||
owner => $owner,
|
||||
reason => $reason,
|
||||
timestamp => time,
|
||||
};
|
||||
|
||||
if ($mode eq 'b') {
|
||||
$self->{banlist}->remove($channel, $mask, 'timeout');
|
||||
$self->{banlist}->add($channel, $mask, $data);
|
||||
} elsif ($mode eq $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char')) {
|
||||
$self->{quietlist}->remove($channel, $mask, 'timeout');
|
||||
$self->{quietlist}->add($channel, $mask, $data);
|
||||
}
|
||||
|
||||
my $method = $mode eq 'b' ? 'unban' : 'unmute';
|
||||
$self->{pbot}->{event_queue}->dequeue_event("$method $channel $mask");
|
||||
|
||||
if ($length > 0) {
|
||||
$self->enqueue_unban($channel, $mode, $mask, $length);
|
||||
}
|
||||
}
|
||||
|
||||
sub ban_user {
|
||||
my ($self, $channel, $mode, $mask, $immediately) = @_;
|
||||
$mode ||= 'b';
|
||||
@ -461,71 +353,6 @@ sub nick_to_banmask {
|
||||
return $mask;
|
||||
}
|
||||
|
||||
sub ban_user_timed {
|
||||
my ($self, $channel, $mode, $mask, $length, $owner, $reason, $immediately) = @_;
|
||||
|
||||
$channel = lc $channel;
|
||||
$mask = lc $mask;
|
||||
|
||||
$mask = $self->nick_to_banmask($mask);
|
||||
$self->ban_user($channel, $mode, $mask, $immediately);
|
||||
|
||||
my $data = {
|
||||
timeout => $length > 0 ? gettimeofday + $length : -1,
|
||||
owner => $owner,
|
||||
reason => $reason,
|
||||
timestamp => time,
|
||||
};
|
||||
|
||||
if ($mode eq 'b') {
|
||||
$self->{banlist}->remove($channel, $mask, 'timeout');
|
||||
$self->{banlist}->add($channel, $mask, $data);
|
||||
} elsif ($mode eq $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char')) {
|
||||
$self->{quietlist}->remove($channel, $mask, 'timeout');
|
||||
$self->{quietlist}->add($channel, $mask, $data);
|
||||
}
|
||||
|
||||
my $method = $mode eq 'b' ? 'unban' : 'unmute';
|
||||
$self->{pbot}->{event_queue}->dequeue_event("$method $channel $mask");
|
||||
|
||||
if ($length > 0) {
|
||||
$self->enqueue_unban($channel, $mode, $mask, $length);
|
||||
}
|
||||
}
|
||||
|
||||
sub checkban {
|
||||
my ($self, $channel, $mode, $mask) = @_;
|
||||
$mask = $self->nick_to_banmask($mask);
|
||||
|
||||
my $data;
|
||||
|
||||
if ($mode eq 'b') {
|
||||
$data = $self->{banlist}->get_data($channel, $mask);
|
||||
} elsif ($mode eq $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char')) {
|
||||
$data = $self->{quietlist}->get_data($channel, $mask);
|
||||
}
|
||||
|
||||
if (not defined $data) {
|
||||
return "$mask is not " . ($mode eq 'b' ? 'banned' : 'muted') . ".";
|
||||
}
|
||||
|
||||
my $result = "$mask " . ($mode eq 'b' ? 'banned' : 'quieted') . " in $channel ";
|
||||
|
||||
if (defined $data->{timestamp}) {
|
||||
my $date = strftime "%a %b %e %H:%M:%S %Y %Z", localtime $data->{timestamp};
|
||||
my $ago = concise ago (time - $data->{timestamp});
|
||||
$result .= "on $date ($ago) ";
|
||||
}
|
||||
|
||||
$result .= "by $data->{owner} " if defined $data->{owner};
|
||||
$result .= "for $data->{reason} " if defined $data->{reason};
|
||||
if (exists $data->{timeout} and $data->{timeout} > 0) {
|
||||
my $duration = concise duration($data->{timeout} - gettimeofday);
|
||||
$result .= "($duration remaining)";
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub add_to_ban_queue {
|
||||
my ($self, $channel, $mode, $mask) = @_;
|
||||
if (not grep { $_ eq $mask } @{$self->{ban_queue}->{$channel}->{$mode}}) {
|
||||
@ -534,6 +361,14 @@ sub add_to_ban_queue {
|
||||
}
|
||||
}
|
||||
|
||||
sub add_to_unban_queue {
|
||||
my ($self, $channel, $mode, $mask) = @_;
|
||||
if (not grep { $_ eq $mask } @{$self->{unban_queue}->{$channel}->{$mode}}) {
|
||||
push @{$self->{unban_queue}->{$channel}->{$mode}}, $mask;
|
||||
$self->{pbot}->{logger}->log("Added -$mode $mask for $channel to unban queue.\n");
|
||||
}
|
||||
}
|
||||
|
||||
sub flush_ban_queue {
|
||||
my $self = shift;
|
||||
|
||||
@ -577,14 +412,6 @@ sub flush_ban_queue {
|
||||
}
|
||||
}
|
||||
|
||||
sub add_to_unban_queue {
|
||||
my ($self, $channel, $mode, $mask) = @_;
|
||||
if (not grep { $_ eq $mask } @{$self->{unban_queue}->{$channel}->{$mode}}) {
|
||||
push @{$self->{unban_queue}->{$channel}->{$mode}}, $mask;
|
||||
$self->{pbot}->{logger}->log("Added -$mode $mask for $channel to unban queue.\n");
|
||||
}
|
||||
}
|
||||
|
||||
sub flush_unban_queue {
|
||||
my $self = shift;
|
||||
|
||||
@ -635,7 +462,7 @@ sub enqueue_unban {
|
||||
|
||||
$self->{pbot}->{event_queue}->enqueue_event(
|
||||
sub {
|
||||
$self->{pbot}->{event_queue}->update_interval("$method $channel $hostmask", 60 * 15, 1); # try again in 15 minutes
|
||||
$self->{pbot}->{event_queue}->update_interval("$method $channel $hostmask", 60 * 5, 1); # try again in 5 minutes
|
||||
return if not $self->{pbot}->{joined_channels};
|
||||
$self->unban_user($channel, $mode, $hostmask);
|
||||
}, $interval, "$method $channel $hostmask", 1
|
||||
@ -658,52 +485,4 @@ sub enqueue_timeouts {
|
||||
}
|
||||
}
|
||||
|
||||
sub has_ban_timeout {
|
||||
my ($self, $channel, $mask, $mode) = @_;
|
||||
$mode ||= 'b';
|
||||
|
||||
my $list = $mode eq 'b' ? $self->{banlist} : $self->{quietlist};
|
||||
|
||||
my $data = $list->get_data($channel, $mask);
|
||||
|
||||
if (defined $data && $data->{timeout} > 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub is_banned {
|
||||
my ($self, $channel, $nick, $user, $host) = @_;
|
||||
|
||||
my $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
|
||||
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
|
||||
push @nickserv_accounts, undef;
|
||||
|
||||
my $banned = undef;
|
||||
|
||||
foreach my $nickserv_account (@nickserv_accounts) {
|
||||
my $baninfos = $self->get_baninfo($channel, "$nick!$user\@$host", $nickserv_account);
|
||||
|
||||
if (defined $baninfos) {
|
||||
foreach my $baninfo (@$baninfos) {
|
||||
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
|
||||
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
|
||||
if ($self->{pbot}->{antiflood}->ban_exempted($baninfo->{channel}, $baninfo->{mask}) || $whitelisted) {
|
||||
$self->{pbot}->{logger}->log("[BanList] is_banned: $nick!$user\@$host 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("[BanList] is_banned: $nick!$user\@$host $mode as $baninfo->{mask} in $baninfo->{channel} by $baninfo->{owner}\n");
|
||||
$banned = $baninfo;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $banned;
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -1,6 +1,12 @@
|
||||
# File: ChanOps.pm
|
||||
#
|
||||
# Purpose: Provides channel operator status tracking and commands.
|
||||
# Purpose: Manages channel operator status and command queues.
|
||||
#
|
||||
# Unless the `permop` metadata is set, PBot remains unopped until an OP-related
|
||||
# command is queued. When a command is queued, PBot will request OP status.
|
||||
# Until PBot gains Op status, new OP commands will be added to the queue. Once
|
||||
# PBot gains OP status, all queued commands are invoked and then after a
|
||||
# timeout PBot will remove its OP status.
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -16,105 +22,96 @@ use Time::Duration qw(concise duration);
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
||||
$self->{op_commands} = {};
|
||||
$self->{is_opped} = {};
|
||||
$self->{op_requested} = {};
|
||||
$self->{op_commands} = {}; # OP command queue
|
||||
$self->{op_requested} = {}; # channels PBot has requested OP
|
||||
$self->{is_opped} = {}; # channels PBot is currently OP
|
||||
|
||||
# default de-OP timeout
|
||||
$self->{pbot}->{registry}->add_default('text', 'general', 'deop_timeout', 300);
|
||||
|
||||
# TODO: enqueue OP events as needed instead of naively checking every 10 seconds
|
||||
$self->{pbot}->{event_queue}->enqueue(sub { $self->check_opped_timeouts }, 10, 'Check opped timeouts');
|
||||
}
|
||||
|
||||
sub track_mode {
|
||||
my ($self, $source, $channel, $mode, $target) = @_;
|
||||
|
||||
$channel = defined $channel ? lc $channel : '';
|
||||
$target = defined $target ? lc $target : '';
|
||||
|
||||
if ($target eq lc $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
|
||||
if ($mode eq '+o') {
|
||||
$self->{pbot}->{logger}->log("$source opped me in $channel\n");
|
||||
my $timeout = $self->{pbot}->{registry}->get_value($channel, 'deop_timeout') // $self->{pbot}->{registry}->get_value('general', 'deop_timeout');
|
||||
$self->{is_opped}->{$channel}{timeout} = gettimeofday + $timeout;
|
||||
delete $self->{op_requested}->{$channel};
|
||||
$self->perform_op_commands($channel);
|
||||
} elsif ($mode eq '-o') {
|
||||
$self->{pbot}->{logger}->log("$source removed my ops in $channel\n");
|
||||
delete $self->{is_opped}->{$channel};
|
||||
} else {
|
||||
$self->{pbot}->{logger}->log("ChanOps: $source performed unhandled mode '$mode' on me\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# returns true if PBot can gain OP status in $channel
|
||||
sub can_gain_ops {
|
||||
my ($self, $channel) = @_;
|
||||
$channel = lc $channel;
|
||||
return
|
||||
$self->{pbot}->{channels}->{storage}->exists($channel)
|
||||
&& $self->{pbot}->{channels}->{storage}->get_data($channel, 'chanop')
|
||||
&& $self->{pbot}->{channels}->{storage}->get_data($channel, 'enabled');
|
||||
}
|
||||
|
||||
# sends request to gain OP status in $channel
|
||||
sub gain_ops {
|
||||
my $self = shift;
|
||||
my $channel = shift;
|
||||
my ($self, $channel) = @_;
|
||||
$channel = lc $channel;
|
||||
|
||||
return if exists $self->{op_requested}->{$channel};
|
||||
return if not $self->can_gain_ops($channel);
|
||||
|
||||
my $op_nick = $self->{pbot}->{registry}->get_value($channel, 'op_nick') // $self->{pbot}->{registry}->get_value('general', 'op_nick') // 'chanserv';
|
||||
if (not exists $self->{is_opped}->{$channel}) {
|
||||
# not opped in channel, send request for ops
|
||||
my $op_nick = $self->{pbot}->{registry}->get_value($channel, 'op_nick')
|
||||
// $self->{pbot}->{registry}->get_value('general', 'op_nick')
|
||||
// 'chanserv';
|
||||
|
||||
my $op_command = $self->{pbot}->{registry}->get_value($channel, 'op_command') // $self->{pbot}->{registry}->get_value('general', 'op_command') // "op $channel";
|
||||
my $op_command = $self->{pbot}->{registry}->get_value($channel, 'op_command')
|
||||
// $self->{pbot}->{registry}->get_value('general', 'op_command')
|
||||
// "op $channel";
|
||||
|
||||
$op_command =~ s/\$channel\b/$channel/g;
|
||||
|
||||
if (not exists $self->{is_opped}->{$channel}) {
|
||||
$self->{pbot}->{conn}->privmsg($op_nick, $op_command);
|
||||
$self->{op_requested}->{$channel} = scalar gettimeofday;
|
||||
} else {
|
||||
# already opped, invoke op commands
|
||||
$self->perform_op_commands($channel);
|
||||
}
|
||||
}
|
||||
|
||||
# removes OP status in $channel
|
||||
sub lose_ops {
|
||||
my $self = shift;
|
||||
my $channel = shift;
|
||||
my ($self, $channel) = @_;
|
||||
$channel = lc $channel;
|
||||
$self->{pbot}->{conn}->mode($channel, '-o ' . $self->{pbot}->{registry}->get_value('irc', 'botnick'));
|
||||
}
|
||||
|
||||
# adds a command to the OP command queue
|
||||
sub add_op_command {
|
||||
my ($self, $channel, $command) = @_;
|
||||
$channel = lc $channel;
|
||||
return if not $self->can_gain_ops($channel);
|
||||
push @{$self->{op_commands}->{$channel}}, $command;
|
||||
push @{$self->{op_commands}->{lc $channel}}, $command;
|
||||
}
|
||||
|
||||
# invokes commands in OP command queue
|
||||
sub perform_op_commands {
|
||||
my $self = shift;
|
||||
my $channel = shift;
|
||||
my ($self, $channel) = @_;
|
||||
$channel = lc $channel;
|
||||
|
||||
$self->{pbot}->{logger}->log("Performing op commands in $channel:\n");
|
||||
|
||||
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
|
||||
|
||||
$self->{pbot}->{logger}->log("Performing op commands...\n");
|
||||
while (my $command = shift @{$self->{op_commands}->{$channel}}) {
|
||||
if ($command =~ /^mode (.*?) (.*)/i) {
|
||||
$self->{pbot}->{conn}->mode($1, $2);
|
||||
$self->{pbot}->{logger}->log(" executing mode $1 $2\n");
|
||||
} elsif ($command =~ /^kick (.*?) (.*?) (.*)/i) {
|
||||
$self->{pbot}->{conn}->kick($1, $2, $3) unless $1 =~ /^\Q$botnick\E$/i;
|
||||
$self->{pbot}->{conn}->mode($1, $2);
|
||||
}
|
||||
elsif ($command =~ /^kick (.*?) (.*?) (.*)/i) {
|
||||
$self->{pbot}->{logger}->log(" executing kick on $1 $2 $3\n");
|
||||
} elsif ($command =~ /^sl (.*)/i) {
|
||||
$self->{pbot}->{conn}->sl($1);
|
||||
$self->{pbot}->{conn}->kick($1, $2, $3) unless $1 =~ /^\Q$botnick\E$/i;
|
||||
}
|
||||
elsif ($command =~ /^sl (.*)/i) {
|
||||
$self->{pbot}->{logger}->log(" executing sl $1\n");
|
||||
$self->{pbot}->{conn}->sl($1);
|
||||
}
|
||||
}
|
||||
|
||||
$self->{pbot}->{logger}->log("Done.\n");
|
||||
}
|
||||
|
||||
# manages OP-related timeouts
|
||||
sub check_opped_timeouts {
|
||||
my $self = shift;
|
||||
my $now = gettimeofday();
|
||||
|
@ -31,16 +31,7 @@ sub join {
|
||||
|
||||
foreach my $channel (split /,/, $channels) {
|
||||
$channel = lc $channel;
|
||||
|
||||
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.join', {channel => $channel});
|
||||
|
||||
delete $self->{pbot}->{chanops}->{is_opped}->{$channel};
|
||||
delete $self->{pbot}->{chanops}->{op_requested}->{$channel};
|
||||
|
||||
if ($self->{storage}->exists($channel) and $self->{storage}->get_data($channel, 'permop')) {
|
||||
$self->{pbot}->{chanops}->gain_ops($channel);
|
||||
}
|
||||
|
||||
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.join', { channel => $channel });
|
||||
$self->{pbot}->{conn}->mode($channel);
|
||||
}
|
||||
}
|
||||
@ -48,10 +39,8 @@ sub join {
|
||||
sub part {
|
||||
my ($self, $channel) = @_;
|
||||
$channel = lc $channel;
|
||||
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.part', {channel => $channel});
|
||||
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.part', { channel => $channel });
|
||||
$self->{pbot}->{conn}->part($channel);
|
||||
delete $self->{pbot}->{chanops}->{is_opped}->{$channel};
|
||||
delete $self->{pbot}->{chanops}->{op_requested}->{$channel};
|
||||
}
|
||||
|
||||
sub autojoin {
|
||||
|
259
lib/PBot/Core/Handlers/BanList.pm
Normal file
259
lib/PBot/Core/Handlers/BanList.pm
Normal file
@ -0,0 +1,259 @@
|
||||
# File: BanList.pm
|
||||
#
|
||||
# Purpose: Populates and maintains channel banlists by checking mode +b/+q
|
||||
# when joining channels and by tracking modes +b/+q and -b/-q in channels.
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
package PBot::Core::Handlers::BanList;
|
||||
use parent 'PBot::Core::Class';
|
||||
|
||||
use PBot::Imports;
|
||||
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
use Time::Duration;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofnames', sub { $self->get_banlist(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.banlist', sub { $self->on_banlist_entry(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.quietlist', sub { $self->on_quietlist_entry(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofbanlist', sub { $self->compare_banlist(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofquietlist', sub { $self->compare_quietlist(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.onemode', sub { $self->track_mode(@_) });
|
||||
|
||||
$self->{mute_char} = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
|
||||
}
|
||||
|
||||
# irc.endofnames
|
||||
sub get_banlist {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
|
||||
$self->{pbot}->{logger}->log("Retrieving banlist for $channel.\n");
|
||||
|
||||
delete $self->{temp_banlist};
|
||||
|
||||
my $mute_char = $self->{mute_char};
|
||||
|
||||
if ($mute_char eq 'b') {
|
||||
$event->{conn}->sl("mode $channel +b");
|
||||
} else {
|
||||
$event->{conn}->sl("mode $channel +b$mute_char");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub on_banlist_entry {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
my $target = lc $event->{event}->{args}[2];
|
||||
my $source = lc $event->{event}->{args}[3];
|
||||
my $timestamp = $event->{event}->{args}[4];
|
||||
|
||||
my $ago = concise ago(gettimeofday - $timestamp);
|
||||
$self->{pbot}->{logger}->log("Ban List: [banlist entry] $channel: $target banned by $source $ago.\n");
|
||||
$self->{temp_banlist}->{$channel}->{'+b'}->{$target} = [$source, $timestamp];
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub on_quietlist_entry {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
my $target = lc $event->{event}->{args}[3];
|
||||
my $source = lc $event->{event}->{args}[4];
|
||||
my $timestamp = $event->{event}->{args}[5];
|
||||
|
||||
my $ago = concise ago(gettimeofday - $timestamp);
|
||||
$self->{pbot}->{logger}->log("Ban List: [quietlist entry] $channel: $target quieted by $source $ago.\n");
|
||||
my $mute_char = $self->{mute_char};
|
||||
$self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$target} = [$source, $timestamp];
|
||||
return 0;
|
||||
}
|
||||
|
||||
# irc.endofbanlist
|
||||
sub compare_banlist {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
|
||||
# first check for saved bans no longer in channel
|
||||
foreach my $mask ($self->{pbot}->{banlist}->{banlist}->get_keys($channel)) {
|
||||
if (not exists $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}) {
|
||||
$self->{pbot}->{logger}->log("BanList: Saved ban +b $mask no longer exists in $channel.\n");
|
||||
# TODO option to restore ban
|
||||
$self->{pbot}->{banlist}->{banlist}->remove($channel, $mask, undef, 1);
|
||||
$self->{pbot}->{event_queue}->dequeue_event("unban $channel $mask");
|
||||
}
|
||||
}
|
||||
|
||||
# add channel bans to saved bans
|
||||
foreach my $mask (keys %{$self->{temp_banlist}->{$channel}->{'+b'}}) {
|
||||
my $data = $self->{pbot}->{banlist}->{banlist}->get_data($channel, $mask);
|
||||
$data->{owner} = $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}->[0];
|
||||
$data->{timestamp} = $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}->[1];
|
||||
|
||||
# make some special-case bans temporary
|
||||
if (not defined $data->{timeout} and $self->{pbot}->{chanops}->can_gain_ops($channel)) {
|
||||
if ($mask =~ m/^\*!\*@/ or $mask =~ m/^\*!.*\@gateway\/web/i) {
|
||||
my $timeout = 60 * 60 * 24 * 7;
|
||||
|
||||
# permanent bans for cloaks that aren't gateway
|
||||
$timeout = 0 if $mask =~ m/\// and $mask !~ m/\@gateway/;
|
||||
|
||||
if ($timeout) {
|
||||
$self->{pbot}->{logger}->log("Temp ban for $mask in $channel.\n");
|
||||
$data->{timeout} = gettimeofday + $timeout;
|
||||
$self->{pbot}->{banlist}->enqueue_unban($channel, 'b', $mask, $timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->{pbot}->{banlist}->{banlist}->add($channel, $mask, $data, 1);
|
||||
}
|
||||
|
||||
$self->{pbot}->{banlist}->{banlist}->save if keys %{$self->{temp_banlist}->{$channel}->{'+b'}};
|
||||
delete $self->{temp_banlist}->{$channel}->{'+b'};
|
||||
}
|
||||
|
||||
# irc.endofquietlist
|
||||
sub compare_quietlist {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
my $channel = lc $event->{event}->{args}[1];
|
||||
|
||||
my $mute_char = $self->{mute_char};
|
||||
|
||||
# first check for saved quiets no longer in channel
|
||||
foreach my $mask ($self->{pbot}->{banlist}->{quietlist}->get_keys($channel)) {
|
||||
if (not exists $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}) {
|
||||
$self->{pbot}->{logger}->log("BanList: Saved quiet +q $mask no longer exists in $channel.\n");
|
||||
# TODO option to restore quiet
|
||||
$self->{pbot}->{banlist}->{quietlist}->remove($channel, $mask, undef, 1);
|
||||
$self->{pbot}->{event_queue}->dequeue_event("unmute $channel $mask");
|
||||
}
|
||||
}
|
||||
|
||||
# add channel bans to saved bans
|
||||
foreach my $mask (keys %{$self->{temp_banlist}->{$channel}->{"+$mute_char"}}) {
|
||||
my $data = $self->{pbot}->{banlist}->{quietlist}->get_data($channel, $mask);
|
||||
$data->{owner} = $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}->[0];
|
||||
$data->{timestamp} = $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}->[1];
|
||||
$self->{pbot}->{banlist}->{quietlist}->add($channel, $mask, $data, 1);
|
||||
}
|
||||
|
||||
$self->{pbot}->{banlist}->{quietlist}->save if keys %{$self->{temp_banlist}->{$channel}->{"+$mute_char"}};
|
||||
delete $self->{temp_banlist}->{$channel}->{"+$mute_char"};
|
||||
}
|
||||
|
||||
# irc.onemode
|
||||
sub track_mode {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my ($source, $channel, $mode, $mask) = (
|
||||
$event->{source},
|
||||
$event->{channel},
|
||||
$event->{mode},
|
||||
$event->{target},
|
||||
);
|
||||
|
||||
my ($nick) = $source =~ /(^[^!]+)/;
|
||||
$channel = defined $channel ? lc $channel : '';
|
||||
$mask = defined $mask ? lc $mask : '';
|
||||
|
||||
my $mute_char = $self->{mute_char};
|
||||
|
||||
if ($mode eq "+b" or $mode eq "+$mute_char") {
|
||||
$self->{pbot}->{logger}->log("Ban List: $mask " . ($mode eq '+b' ? 'banned' : 'muted') . " by $source in $channel.\n");
|
||||
|
||||
my $data = {
|
||||
owner => $source,
|
||||
timestamp => scalar gettimeofday,
|
||||
};
|
||||
|
||||
if ($mode eq "+b") {
|
||||
$self->{pbot}->{banlist}->{banlist}->add($channel, $mask, $data);
|
||||
} elsif ($mode eq "+$mute_char") {
|
||||
$self->{pbot}->{banlist}->{quietlist}->add($channel, $mask, $data);
|
||||
}
|
||||
|
||||
$self->{pbot}->{antiflood}->devalidate_accounts($mask, $channel);
|
||||
} elsif ($mode eq "-b" or $mode eq "-$mute_char") {
|
||||
$self->{pbot}->{logger}->log("Ban List: $mask " . ($mode eq '-b' ? 'unbanned' : 'unmuted') . " by $source in $channel.\n");
|
||||
|
||||
if ($mode eq "-b") {
|
||||
$self->{pbot}->{banlist}->{banlist}->remove($channel, $mask);
|
||||
$self->{pbot}->{event_queue}->dequeue_event("unban $channel $mask");
|
||||
|
||||
# freenode strips channel forwards from unban result if no ban exists with a channel forward
|
||||
my $join_flood_channel = $self->{pbot}->{registry}->get_value('antiflood', 'join_flood_channel') // '#stop-join-flood';
|
||||
$self->{pbot}->{banlist}->{banlist}->remove($channel, "$mask\$$join_flood_channel");
|
||||
$self->{pbot}->{event_queue}->dequeue_event(lc "unban $channel $mask\$$join_flood_channel");
|
||||
} elsif ($mode eq "-$mute_char") {
|
||||
$self->{pbot}->{banlist}->{quietlist}->remove($channel, $mask);
|
||||
$self->{pbot}->{event_queue}->dequeue_event("unmute $channel $mask");
|
||||
}
|
||||
}
|
||||
|
||||
return if not $self->{pbot}->{chanops}->can_gain_ops($channel);
|
||||
|
||||
if ($mode eq "+b") {
|
||||
if ($nick eq "ChanServ" or $mask =~ m/##fix_your_connection$/i) {
|
||||
if ($self->{pbot}->{banlist}->{banlist}->exists($channel, $mask)) {
|
||||
$self->{pbot}->{banlist}->{banlist}->set($channel, $mask, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
$self->{pbot}->{event_queue}->update_interval("unban $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
} else {
|
||||
my $data = {
|
||||
reason => 'Temp ban for banned-by-ChanServ or mask is *!*@*##fix_your_connection',
|
||||
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
|
||||
timeout => gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'),
|
||||
timestamp => gettimeofday,
|
||||
};
|
||||
$self->{pbot}->{banlist}->{banlist}->add($channel, $mask, $data);
|
||||
$self->{pbot}->{banlist}->enqueue_unban($channel, 'b', $mask, $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
}
|
||||
} elsif ($mask =~ m/^\*!\*@/ or $mask =~ m/^\*!.*\@gateway\/web/i) {
|
||||
my $timeout = 60 * 60 * 24 * 7;
|
||||
|
||||
if ($mask =~ m/\// and $mask !~ m/\@gateway/) {
|
||||
$timeout = 0; # permanent bans for cloaks that aren't gateway
|
||||
}
|
||||
|
||||
if ($timeout) {
|
||||
if (not $self->{pbot}->{banlist}->{banlist}->exists($channel, $mask)) {
|
||||
$self->{pbot}->{logger}->log("Temp ban for $mask in $channel.\n");
|
||||
my $data = {
|
||||
reason => 'Temp ban for *!*@host',
|
||||
timeout => gettimeofday + $timeout,
|
||||
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
|
||||
timestamp => gettimeofday,
|
||||
};
|
||||
$self->{pbot}->{banlist}->{banlist}->add($channel, $mask, $data);
|
||||
$self->{pbot}->{banlist}->enqueue_unban($channel, 'b', $mask, $timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elsif ($mode eq "+$mute_char") {
|
||||
if (lc $nick ne lc $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
|
||||
$self->{pbot}->{logger}->log("WEIRD MUTE THING $nick...\n");
|
||||
if ($self->{pbot}->{banlist}->{quietlist}->exists($channel, $mask)) {
|
||||
$self->{pbot}->{banlist}->{quietlist}->set($channel, $mask, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
$self->{pbot}->{event_queue}->update_interval("unmute $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
|
||||
} else {
|
||||
my $data = {
|
||||
reason => 'Temp mute',
|
||||
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
|
||||
timeout => gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'mute_timeout'),
|
||||
timestamp => gettimeofday,
|
||||
};
|
||||
$self->{pbot}->{banlist}->{quietlist}->add($channel, $mask, $data);
|
||||
$self->{pbot}->{banlist}->enqueue_unban($channel, $self->{mute_char}, $mask, $self->{pbot}->{registry}->get_value('banlist', 'mute_timeout'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
78
lib/PBot/Core/Handlers/ChanOps.pm
Normal file
78
lib/PBot/Core/Handlers/ChanOps.pm
Normal file
@ -0,0 +1,78 @@
|
||||
# File: ChanOps.pm
|
||||
#
|
||||
# Purpose: Tracks when PBot gains or loses OPs in a channel and invokes
|
||||
# relevant actions. Handles OP-related actions when PBot joins or parts.
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
package PBot::Core::Handlers::ChanOps;
|
||||
use parent 'PBot::Core::Class';
|
||||
|
||||
use PBot::Imports;
|
||||
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('pbot.join', sub { $self->on_self_join(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('pbot.part', sub { $self->on_self_part(@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.onemode', sub { $self->track_mode(@_) });
|
||||
}
|
||||
|
||||
sub on_self_join {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
my $channel = $event->{channel};
|
||||
|
||||
delete $self->{pbot}->{chanops}->{is_opped}->{$channel};
|
||||
delete $self->{pbot}->{chanops}->{op_requested}->{$channel};
|
||||
|
||||
if ($self->{pbot}->{channels}->{storage}->get_data($channel, 'permop')) {
|
||||
$self->{pbot}->{chanops}->gain_ops($channel);
|
||||
}
|
||||
}
|
||||
|
||||
sub on_self_part {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
my $channel = $event->{channel};
|
||||
delete $self->{pbot}->{chanops}->{is_opped}->{$channel};
|
||||
delete $self->{pbot}->{chanops}->{op_requested}->{$channel};
|
||||
}
|
||||
|
||||
sub track_mode {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my ($source, $channel, $mode, $target) = (
|
||||
$event->{source},
|
||||
$event->{channel},
|
||||
$event->{mode},
|
||||
$event->{target},
|
||||
);
|
||||
|
||||
$channel = defined $channel ? lc $channel : '';
|
||||
$target = defined $target ? lc $target : '';
|
||||
|
||||
if ($target eq lc $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
|
||||
if ($mode eq '+o') {
|
||||
$self->{pbot}->{logger}->log("$source opped me in $channel\n");
|
||||
|
||||
my $timeout = $self->{pbot}->{registry}->get_value($channel, 'deop_timeout')
|
||||
// $self->{pbot}->{registry}->get_value('general', 'deop_timeout');
|
||||
|
||||
$self->{pbot}->{chanops}->{is_opped}->{$channel}{timeout} = gettimeofday + $timeout;
|
||||
|
||||
delete $self->{pbot}->{chanops}->{op_requested}->{$channel};
|
||||
|
||||
$self->{pbot}->{chanops}->perform_op_commands($channel);
|
||||
}
|
||||
elsif ($mode eq '-o') {
|
||||
$self->{pbot}->{logger}->log("$source removed my ops in $channel\n");
|
||||
delete $self->{pbot}->{chanops}->{is_opped}->{$channel};
|
||||
}
|
||||
else {
|
||||
$self->{pbot}->{logger}->log("ChanOps: $source performed unhandled mode '$mode' on me\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
@ -1,6 +1,7 @@
|
||||
# File: Channel.pm
|
||||
#
|
||||
# Purpose: Handlers for channel-related IRC events.
|
||||
# Purpose: Handlers for general channel-related IRC events that aren't handled
|
||||
# by any specialized Handler modules.
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -9,15 +10,12 @@ package PBot::Core::Handlers::Channel;
|
||||
use parent 'PBot::Core::Class';
|
||||
|
||||
use PBot::Imports;
|
||||
use parent 'PBot::Core::Class';
|
||||
|
||||
use PBot::Core::MessageHistory::Constants ':all';
|
||||
|
||||
use Time::HiRes qw/time/;
|
||||
use Data::Dumper;
|
||||
|
||||
use MIME::Base64;
|
||||
use Encode;
|
||||
use MIME::Base64;
|
||||
use Time::HiRes qw/time/;
|
||||
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
@ -31,9 +29,9 @@ sub initialize {
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.topic', sub { $self->on_topic (@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.topicinfo', sub { $self->on_topicinfo (@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.channelcreate', sub { $self->on_channelcreate (@_) });
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.onemode', sub { $self->track_mode (@_) });
|
||||
}
|
||||
|
||||
# FIXME: on_mode doesn't handle chanmodes that have parameters, e.g. +l
|
||||
sub on_mode {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
@ -50,6 +48,9 @@ sub on_mode {
|
||||
my $i = 0;
|
||||
my ($modifier, $char, $mode, $target);
|
||||
|
||||
my $source = "$nick!$user\@$host";
|
||||
|
||||
# split combined modes
|
||||
while ($mode_string =~ m/(.)/g) {
|
||||
$char = $1;
|
||||
|
||||
@ -61,44 +62,49 @@ sub on_mode {
|
||||
$mode = $modifier . $char;
|
||||
$target = $event->{event}->{args}->[++$i];
|
||||
|
||||
$self->{pbot}->{logger}->log("Mode $channel [$mode" . (length $target ? " $target" : '') . "] by $nick!$user\@$host\n");
|
||||
$self->{pbot}->{logger}->log("Mode $channel [$mode" . (length $target ? " $target" : '') . "] by $source\n");
|
||||
|
||||
# TODO: figure out a good way to allow other packages to receive "track_mode" events
|
||||
# i.e., perhaps by emitting a modechange event or some such and registering handlers
|
||||
$self->{pbot}->{banlist}->track_mode("$nick!$user\@$host", $channel, $mode, $target);
|
||||
$self->{pbot}->{chanops}->track_mode("$nick!$user\@$host", $channel, $mode, $target);
|
||||
|
||||
if (defined $target and length $target) {
|
||||
# mode set on user
|
||||
my $message_account = $self->{pbot}->{messagehistory}->get_message_account($nick, $user, $host);
|
||||
|
||||
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, "MODE $mode $target", MSG_CHAT);
|
||||
|
||||
# TODO: here as well
|
||||
if ($modifier eq '-') {
|
||||
$self->{pbot}->{nicklist}->delete_meta($channel, $target, "+$char");
|
||||
} else {
|
||||
$self->{pbot}->{nicklist}->set_meta($channel, $target, $mode, 1);
|
||||
# dispatch a single mode event
|
||||
$self->{pbot}->{event_dispatcher}->dispatch_event(
|
||||
'irc.onemode',
|
||||
{
|
||||
source => $source,
|
||||
channel => $channel,
|
||||
mode => $mode,
|
||||
target => $target,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
# mode set on channel
|
||||
my $modes = $self->{pbot}->{channels}->get_meta($channel, 'MODE');
|
||||
|
||||
if (defined $modes) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub track_mode {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my ($source, $channel, $mode, $target) = (
|
||||
$event->{source},
|
||||
$event->{channel},
|
||||
$event->{mode},
|
||||
$event->{target},
|
||||
);
|
||||
|
||||
# disregard mode set on user instead of channel
|
||||
return if defined $target and length $target;
|
||||
|
||||
my ($modifier, $char) = split //, $mode;
|
||||
|
||||
my $modes = $self->{pbot}->{channels}->get_meta($channel, 'MODE') // '';
|
||||
|
||||
if ($modifier eq '+') {
|
||||
$modes = '+' if not length $modes;
|
||||
$modes .= $char;
|
||||
$modes .= $char if $modes !~ /\Q$char/;
|
||||
} else {
|
||||
$modes =~ s/\Q$char//g;
|
||||
$modes = '' if $modes eq '+';
|
||||
}
|
||||
|
||||
# TODO: here as well
|
||||
$self->{pbot}->{channels}->{storage}->set($channel, 'MODE', $modes, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub on_join {
|
||||
@ -148,7 +154,7 @@ sub on_join {
|
||||
MSG_JOIN,
|
||||
);
|
||||
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub on_invite {
|
||||
@ -173,7 +179,7 @@ sub on_invite {
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub on_kick {
|
||||
@ -232,7 +238,7 @@ sub on_kick {
|
||||
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, $text, MSG_CHAT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub on_departure {
|
||||
@ -280,7 +286,7 @@ sub on_departure {
|
||||
$self->{pbot}->{users}->save;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub on_channelmodeis {
|
||||
@ -291,6 +297,7 @@ sub on_channelmodeis {
|
||||
$self->{pbot}->{logger}->log("Channel $channel modes: $modes\n");
|
||||
|
||||
$self->{pbot}->{channels}->{storage}->set($channel, 'MODE', $modes, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub on_channelcreate {
|
||||
@ -302,6 +309,7 @@ sub on_channelcreate {
|
||||
|
||||
$self->{pbot}->{channels}->{storage}->set($channel, 'CREATED_BY', $owner, 1);
|
||||
$self->{pbot}->{channels}->{storage}->set($channel, 'CREATED_ON', $timestamp, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub on_topic {
|
||||
@ -324,7 +332,7 @@ sub on_topic {
|
||||
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC_SET_ON', time);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub on_topicinfo {
|
||||
@ -333,7 +341,7 @@ sub on_topicinfo {
|
||||
$self->{pbot}->{logger}->log("Topic for $channel set by $by on " . localtime($timestamp) . "\n");
|
||||
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC_SET_BY', $by, 1);
|
||||
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC_SET_ON', $timestamp, 1);
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -23,6 +23,7 @@ sub initialize {
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) }, 0);
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_activity(@_) }, 0);
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_activity(@_) }, 0);
|
||||
$self->{pbot}->{event_dispatcher}->register_handler('irc.onemode', sub { $self->track_mode(@_) }, 0);
|
||||
|
||||
# lowest priority so these get handled by NickList after all other handlers
|
||||
# (all other handlers should be given a priority < 100)
|
||||
@ -143,6 +144,27 @@ sub on_nickchange {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub track_mode {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
|
||||
my ($source, $channel, $mode, $target) = (
|
||||
$event->{source},
|
||||
$event->{channel},
|
||||
$event->{mode},
|
||||
$event->{target},
|
||||
);
|
||||
|
||||
return if not defined $target or not length $target;
|
||||
|
||||
my ($modifier, $char) = split //, $mode;
|
||||
|
||||
if ($modifier eq '-') {
|
||||
$self->{pbot}->{nicklist}->delete_meta($channel, $target, "+$char");
|
||||
} else {
|
||||
$self->{pbot}->{nicklist}->set_meta($channel, $target, $mode, 1);
|
||||
}
|
||||
}
|
||||
|
||||
sub on_self_join {
|
||||
my ($self, $event_type, $event) = @_;
|
||||
# clear nicklist to remove any stale nicks before repopulating with namreplies
|
||||
|
@ -18,7 +18,7 @@ use Time::HiRes qw/gettimeofday/;
|
||||
sub initialize {
|
||||
my ($self, %conf) = @_;
|
||||
|
||||
# nicklist hash
|
||||
# nicklist hashtable
|
||||
$self->{nicklist} = {};
|
||||
|
||||
# nicklist debug registry entry
|
||||
@ -33,11 +33,11 @@ sub update_timestamp {
|
||||
$channel = lc $channel;
|
||||
$nick = lc $nick;
|
||||
|
||||
if (exists $self->{nicklist}->{$channel} and exists $self->{nicklist}->{$channel}->{$nick}) { $self->{nicklist}->{$channel}->{$nick}->{timestamp} = gettimeofday; }
|
||||
else {
|
||||
$self->{pbot}->{logger}->log("Adding nick '$orig_nick' to channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
|
||||
$self->{nicklist}->{$channel}->{$nick} = {nick => $orig_nick, timestamp => scalar gettimeofday};
|
||||
if (not exists $self->{nicklist}->{$channel} or not exists $self->{nicklist}->{$channel}->{$nick}) {
|
||||
$self->add_nick($channel, $orig_nick);
|
||||
}
|
||||
|
||||
$self->{nicklist}->{$channel}->{$nick}->{timestamp} = gettimeofday;
|
||||
}
|
||||
|
||||
sub remove_channel {
|
||||
@ -49,14 +49,19 @@ sub add_nick {
|
||||
my ($self, $channel, $nick) = @_;
|
||||
|
||||
if (not exists $self->{nicklist}->{lc $channel}->{lc $nick}) {
|
||||
$self->{pbot}->{logger}->log("Adding nick '$nick' to channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
|
||||
$self->{nicklist}->{lc $channel}->{lc $nick} = {nick => $nick, timestamp => 0};
|
||||
if ($self->{pbot}->{registry}->get_value('nicklist', 'debug')) {
|
||||
$self->{pbot}->{logger}->log("Adding nick '$nick' to channel '$channel'\n");
|
||||
}
|
||||
$self->{nicklist}->{lc $channel}->{lc $nick} = { nick => $nick, timestamp => 0, join => 0 };
|
||||
}
|
||||
}
|
||||
|
||||
sub remove_nick {
|
||||
my ($self, $channel, $nick) = @_;
|
||||
$self->{pbot}->{logger}->log("Removing nick '$nick' from channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
|
||||
|
||||
if ($self->{pbot}->{registry}->get_value('nicklist', 'debug')) {
|
||||
$self->{pbot}->{logger}->log("Removing nick '$nick' from channel '$channel'\n");
|
||||
}
|
||||
delete $self->{nicklist}->{lc $channel}->{lc $nick};
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ sub autoload {
|
||||
$self->{pbot}->{logger}->log(" $plugin\n");
|
||||
|
||||
if ($self->load($plugin, %conf)) {
|
||||
$plugin_count++
|
||||
$plugin_count++;
|
||||
} else {
|
||||
die "Plugin $plugin failed to autoload. You may remove it from $data_dir/plugin_autoload.\n";
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ use PBot::Imports;
|
||||
# These are set by the /misc/update_version script
|
||||
use constant {
|
||||
BUILD_NAME => "PBot",
|
||||
BUILD_REVISION => 4332,
|
||||
BUILD_DATE => "2021-07-31",
|
||||
BUILD_REVISION => 4333,
|
||||
BUILD_DATE => "2021-08-01",
|
||||
};
|
||||
|
||||
sub initialize {}
|
||||
|
Loading…
Reference in New Issue
Block a user