diff --git a/PBot/AntiFlood.pm b/PBot/AntiFlood.pm index c94b6353..ff6df298 100644 --- a/PBot/AntiFlood.pm +++ b/PBot/AntiFlood.pm @@ -171,7 +171,7 @@ sub check_flood { my $account; if ($mode == $self->{pbot}->{messagehistory}->{MSG_JOIN} and exists $self->{changinghost}->{$nick}) { - $self->{pbot}->{logger}->log("Finalizing changinghost for $nick!\n"); + $self->{pbot}->{logger}->log("Finalizing host change for $nick.\n"); $account = delete $self->{changinghost}->{$nick}; my $id = $self->{pbot}->{messagehistory}->{database}->get_message_account_id($mask); @@ -373,8 +373,15 @@ sub check_flood { my $banmask = $self->address_to_mask($host); if ($self->{pbot}->{channels}->is_active_op("${channel}-floodbans")) { - $self->{pbot}->{chanops} - ->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'join flooding', "*!$user\@$banmask\$##stop_join_flood", $chan . '-floodbans', $timeout); + $self->{pbot}->{banlist}->ban_user_timed( + $chan . '-floodbans', + 'b', + "*!$user\@$banmask\$##stop_join_flood", + $timeout, + $self->{pbot}->{registry}->get_value('irc', 'botnick'), + 'join flooding', + ); + $self->{pbot}->{logger}->log("$nick!$user\@$banmask banned for $duration due to join flooding (offense #" . $chan_data->{offenses} . ").\n"); $self->{pbot}->{conn}->privmsg( $nick, @@ -403,8 +410,15 @@ sub check_flood { if ($self->{pbot}->{registry}->get_value('antiflood', 'enforce')) { my $length = $self->{pbot}->{registry}->get_array_value('antiflood', 'chat_flood_punishment', $chan_data->{offenses} - 1); - $self->{pbot}->{chanops} - ->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'chat flooding', "*!$user\@" . $self->address_to_mask($host), $chan, $length); + $self->{pbot}->{banlist}->ban_user_timed( + $chan, + 'b', + "*!$user\@" . $self->address_to_mask($host), + $length, + $self->{pbot}->{registry}->get_value('irc', 'botnick'), + 'chat flooding', + ); + $length = duration($length); $self->{pbot}->{logger}->log("$nick $chan flood offense " . $chan_data->{offenses} . " earned $length ban\n"); $self->{pbot}->{conn}->privmsg( @@ -441,8 +455,16 @@ sub check_flood { if ($self->{pbot}->{registry}->get_value('antiflood', 'enforce')) { my $length = $self->{pbot}->{registry}->get_array_value('antiflood', 'nick_flood_punishment', $self->{nickflood}->{$ancestor}->{offenses} - 1); - $self->{pbot}->{chanops} - ->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'nick flooding', "*!$user\@" . $self->address_to_mask($host), $chan, $length); + + $self->{pbot}->{banlist}->ban_user_timed( + $chan, + 'b', + "*!$user\@" . $self->address_to_mask($host), + $length, + $self->{pbot}->{registry}->get_value('irc', 'botnick'), + 'nick flooding', + ); + $length = duration($length); $self->{pbot}->{logger}->log("$nick nickchange flood offense " . $self->{nickflood}->{$ancestor}->{offenses} . " earned $length ban\n"); $self->{pbot}->{conn}->privmsg($nick, "You have been temporarily banned due to nick-change flooding. You will be unbanned in $length."); @@ -481,14 +503,24 @@ sub check_flood { my $offenses = $chan_data->{enter_abuses} - $enter_abuse_max_offenses + 1 + $other_offenses; my $ban_length = $self->{pbot}->{registry}->get_array_value('antiflood', 'enter_abuse_punishment', $offenses - 1); - $self->{pbot}->{chanops} - ->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'enter abuse', "*!$user\@" . $self->address_to_mask($host), $chan, $ban_length); + + $self->{pbot}->{banlist}->ban_user_timed( + $chan, + 'b', + "*!$user\@" . $self->address_to_mask($host), + $ban_length, + $self->{pbot}->{registry}->get_value('irc', 'botnick'), + 'enter abuse', + ); + $ban_length = duration($ban_length); $self->{pbot}->{logger}->log("$nick $chan enter abuse offense " . $chan_data->{enter_abuses} . " earned $ban_length ban\n"); + $self->{pbot}->{conn}->privmsg( $nick, "You have been muted due to abusing the enter key. Please do not split your sentences over multiple messages. You will be allowed to speak again in approximately $ban_length." ); + $chan_data->{last_offense} = gettimeofday; $self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $chan, $chan_data); next; @@ -548,7 +580,7 @@ sub unbanme { foreach my $channel (@channels) { next if exists $unbanned->{$channel} and exists $unbanned->{$channel}->{$mask}; - next if not $self->{pbot}->{chanops}->{unban_timeout}->exists($channel . '-floodbans', $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); @@ -556,7 +588,7 @@ sub unbanme { push @nickserv_accounts, undef; foreach my $nickserv_account (@nickserv_accounts) { - my $baninfos = $self->{pbot}->{bantracker}->get_baninfo("$anick!$auser\@$ahost", $channel, $nickserv_account); + my $baninfos = $self->{pbot}->{banlist}->get_baninfo($channel, "$anick!$auser\@$ahost", $nickserv_account); if (defined $baninfos) { foreach my $baninfo (@$baninfos) { @@ -598,7 +630,7 @@ sub unbanme { foreach my $mask (keys %{$unbanned->{$channel}}) { if ($self->{pbot}->{channels}->is_active_op("${channel}-floodbans")) { if ($unbanned->{$channel}->{$mask} <= 2) { - $self->{pbot}->{chanops}->unban_user($mask, $channel . '-floodbans'); + $self->{pbot}->{banlist}->unban_user($channel . '-floodbans', 'b', $mask); $channels .= "$sep$channel"; $sep = ", "; } @@ -614,7 +646,7 @@ sub unbanme { } } - $self->{pbot}->{chanops}->check_unban_queue(); + $self->{pbot}->{banlist}->flush_unban_queue(); $channels =~ s/(.*), /$1 and /; $channels_warning =~ s/(.*), /$1 and /; @@ -624,16 +656,16 @@ sub unbanme { 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 before using your final `unbanme` of the day."; + " 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; ensure that your client or connection issues are resolved, otherwise leave the channel until they are or you will be temporarily banned for several hours if you join-flood again during this period."; + " You may not use `unbanme` again for several hours for $channels_disabled."; } if (length $channels) { return "/msg $nick You have been unbanned from $channels.$warning"; } - else { return "/msg $nick You were not unbanned at this time.$warning"; } + else { return "/msg $nick $warning"; } } else { return "/msg $nick There is no join-flooding ban set for you."; } @@ -783,7 +815,7 @@ sub check_bans { $self->{pbot}->{logger}->log("anti-flood: [check-bans] checking for bans in $channel on $alias using nickserv " . (defined $nickserv ? $nickserv : "[undefined]") . "\n") if $debug_checkban >= 2; - my $baninfos = $self->{pbot}->{bantracker}->get_baninfo($alias, $channel, $nickserv); + my $baninfos = $self->{pbot}->{banlist}->get_baninfo($channel, $alias, $nickserv); if (defined $baninfos) { foreach my $baninfo (@$baninfos) { @@ -846,7 +878,7 @@ sub check_bans { if ($host =~ m{^([^/]+)/.+} and $1 ne 'gateway' and $1 ne 'nat') { $banmask = "*!*\@$host"; } elsif ( $current_nickserv_account and $baninfo->{banmask} !~ m/^\$a:/i - and not exists $self->{pbot}->{bantracker}->{banlist}->{$baninfo->{channel}}->{'+b'}->{"\$a:$current_nickserv_account"}) + and not $self->{pbot}->{banlist}->{banlist}->exists($baninfo->{channel}, "\$a:$current_nickserv_account")) { $banmask = "\$a:$current_nickserv_account"; } else { @@ -854,7 +886,6 @@ sub check_bans { elsif ($host =~ m{^nat/([^/]+)/}) { $banmask = "*!$user\@nat/$1/*"; } else { $banmask = "*!*\@$host"; - #$banmask = "*!$user@" . $self->address_to_mask($host); } } @@ -886,7 +917,13 @@ sub check_bans { $owner =~ s/!.*$//; $self->{pbot}->{chanops}->add_op_command($baninfo->{channel}, "kick $baninfo->{channel} $bannick Evaded $baninfo->{banmask} set by $owner"); } - $self->{pbot}->{chanops}->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'ban evasion', $banmask, $baninfo->{channel}, 60 * 60 * 24 * 14); + $self->{pbot}->{banlist}->ban_user_timed( + $banmask, $baninfo->{channel}, + 'b', + 60 * 60 * 24 * 14, + $self->{pbot}->{registry}->get_value('irc', 'botnick'), + 'ban evasion', + ); } my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated'); if ($channel_data->{validated} & $self->{NICKSERV_VALIDATED}) { diff --git a/PBot/BanList.pm b/PBot/BanList.pm index b722d456..b63bba7a 100644 --- a/PBot/BanList.pm +++ b/PBot/BanList.pm @@ -1,16 +1,15 @@ -# File: BanTracker.pm +# File: BanList.pm # Author: pragma_ # -# Purpose: Populates and maintains channel banlists by checking mode +b on -# joining channels and by tracking modes +b and -b in channels. -# -# Does NOT do banning or unbanning. +# 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. # 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::BanTracker; +package PBot::BanList; use parent 'PBot::Class'; @@ -20,36 +19,140 @@ use feature 'unicode_strings'; use Time::HiRes qw/gettimeofday/; use Time::Duration; use Data::Dumper; +use POSIX qw/strftime/; $Data::Dumper::Sortkeys = 1; sub initialize { my ($self, %conf) = @_; - $self->{pbot}->{registry}->add_default('text', 'bantracker', 'chanserv_ban_timeout', '604800'); - $self->{pbot}->{registry}->add_default('text', 'bantracker', 'mute_timeout', '604800'); - $self->{pbot}->{registry}->add_default('text', 'bantracker', 'debug', '0'); - $self->{pbot}->{commands}->register(sub { $self->dumpbans(@_) }, "dumpbans", 1); + $self->{pbot}->{registry}->add_default('text', 'banlist', 'chanserv_ban_timeout', '604800'); + $self->{pbot}->{registry}->add_default('text', 'banlist', 'mute_timeout', '604800'); + $self->{pbot}->{registry}->add_default('text', 'banlist', 'debug', '0'); - $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}->{commands}->register(sub { $self->banlist_cmd(@_) }, "banlist", 0); + $self->{pbot}->{commands}->register(sub { $self->checkban_cmd(@_) }, "checkban", 0); + $self->{pbot}->{commands}->register(sub { $self->checkmute_cmd(@_) }, "checkmute", 0); - $self->{banlist} = {}; + $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->{banlist} = PBot::DualIndexHashObject->new( + pbot => $self->{pbot}, + name => 'Ban List', + filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/banlist' + ); + + $self->{quietlist} = PBot::DualIndexHashObject->new( + pbot => $self->{pbot}, + name => 'Quiet List', + filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quietlist' + ); + + $self->{banlist}->load; + $self->{quietlist}->load; + + $self->enqueue_timeouts($self->{banlist}, 'b'); + $self->enqueue_timeouts($self->{quietlist}, 'q'); + + $self->{ban_queue} = {}; + $self->{unban_queue} = {}; + + $self->{pbot}->{timer}->register(sub { $self->flush_unban_queue }, 30, 'Unban Queue'); } -sub dumpbans { +sub banlist_cmd { my ($self, $from, $nick, $user, $host, $arguments) = @_; - my $bans = Dumper($self->{banlist}); - return $bans; + + if (not length $arguments) { + return "Usage: banlist "; + } + + my $result = "Ban list for $arguments:\n"; + + if ($self->{banlist}->exists($arguments)) { + my $count = $self->{banlist}->get_keys($arguments); + $result .= "$count bans:\n"; + foreach my $mask ($self->{banlist}->get_keys($arguments)) { + my $data = $self->{banlist}->get_data($arguments, $mask); + $result .= " $mask banned "; + + 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 (defined $data->{timeout} and $data->{timeout} > 0) { + my $duration = concise duration($data->{timeout} - gettimeofday); + $result .= "($duration remaining)"; + } + $result .= ";\n"; + } + } else { + $result .= "bans: none;\n"; + } + + if ($self->{quietlist}->exists($arguments)) { + my $count = $self->{quietlist}->get_keys($arguments); + $result .= "$count mutes:\n"; + foreach my $mask ($self->{quietlist}->get_keys($arguments)) { + my $data = $self->{quietlist}->get_data($arguments, $mask); + $result .= " $mask muted "; + + 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 ago) "; + } + + $result .= "by $data->{owner} " if defined $data->{owner}; + $result .= "for $data->{reason} " if defined $data->{reason}; + if (defined $data->{timeout} and $data->{timeout} > 0) { + my $duration = concise duration($data->{timeout} - gettimeofday); + $result .= "($duration remaining)"; + } + $result .= ";\n"; + } + } else { + $result .= "quiets: none;\n"; + } + + return $result; +} + +sub checkban_cmd { + my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_; + my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2); + + return "Usage: checkban [channel]" if not defined $target; + $channel = $from if not defined $channel; + + return "Please specify a channel." if $channel !~ /^#/; + return $self->checkban($channel, 'b', $target); +} + +sub checkmute_cmd { + my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_; + my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2); + + return "Usage: checkmute [channel]" if not defined $target; + $channel = $from if not defined $channel; + + return "Please specify a channel." if $channel !~ /^#/; + return $self->checkban($channel, 'q', $target); } sub get_banlist { my ($self, $event_type, $event) = @_; my $channel = lc $event->{event}->{args}[1]; - return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel); - delete $self->{banlist}->{$channel}; $self->{pbot}->{logger}->log("Retrieving banlist for $channel.\n"); + delete $self->{temp_banlist}; $event->{conn}->sl("mode $channel +bq"); return 0; } @@ -60,33 +163,11 @@ sub on_banlist_entry { 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 $timestamp = $event->{event}->{args}[4]; - my $ago = ago(gettimeofday - $timestamp); - - $self->{pbot}->{logger}->log("ban-tracker: [banlist entry] $channel: $target banned by $source $ago.\n"); - $self->{banlist}->{$channel}->{'+b'}->{$target} = [$source, $timestamp]; - - if ($target =~ m/^\*!\*@/ or $target =~ m/^\*!.*\@gateway\/web/i) { - my $timeout = 60 * 60 * 24 * 7; - - if ($target =~ m/\// and $target !~ m/\@gateway/) { - $timeout = 0; # permanent bans for cloaks that aren't gateway - } - - if ($timeout && $self->{pbot}->{chanops}->can_gain_ops($channel)) { - if (not $self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $target)) { - $self->{pbot}->{logger}->log("Temp ban for $target in $channel.\n"); - my $data = { - timeout => gettimeofday + $timeout, - owner => $source, - reason => 'Temp ban on *!*@... or *!...@gateway/web' - }; - $self->{pbot}->{chanops}->{unban_timeout}->add($channel, $target, $data); - $self->{pbot}->{chanops}->enqueue_unban_timeout($channel, $target, $timeout); - } - } - } + my $ago = concise ago(gettimeofday - $timestamp); + $self->{pbot}->{logger}->log("banlist: [banlist entry] $channel: $target banned by $source $ago.\n"); + $self->{temp_banlist}->{$channel}->{'+b'}->{$target} = [$source, $timestamp]; return 0; } @@ -96,58 +177,310 @@ sub on_quietlist_entry { 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 $timestamp = $event->{event}->{args}[5]; - my $ago = ago(gettimeofday - $timestamp); - - $self->{pbot}->{logger}->log("ban-tracker: [quietlist entry] $channel: $target quieted by $source $ago.\n"); - $self->{banlist}->{$channel}->{'+q'}->{$target} = [$source, $timestamp]; + my $ago = concise ago(gettimeofday - $timestamp); + $self->{pbot}->{logger}->log("banlist: [quietlist entry] $channel: $target quieted by $source $ago.\n"); + $self->{temp_banlist}->{$channel}->{'+q'}->{$target} = [$source, $timestamp]; return 0; } +sub compare_banlist { + my ($self, $event_type, $event) = @_; + my $channel = lc $event->{event}->{args}[1]; + + $self->{pbot}->{logger}->log("Finalizing ban list for $channel\n"); + + # 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}->{timer}->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->{pbot}->{chanops}->enqueue_unban($channel, 'b', $mask, $timeout); + } + } + } + + $self->{banlist}->add($channel, $mask, $data, 1); + } + + $self->{banlist}->save; +} + +sub compare_quietlist { + my ($self, $event_type, $event) = @_; + my $channel = lc $event->{event}->{args}[1]; + + $self->{pbot}->{logger}->log("Finalizing quiet list for $channel\n"); + + # first check for saved quiets no longer in channel + foreach my $mask ($self->{quietlist}->get_keys($channel)) { + if (not exists $self->{temp_banlist}->{$channel}->{'+q'}->{$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}->{timer}->dequeue_event("unmute $channel $mask"); + } + } + + # add channel bans to saved bans + foreach my $mask (keys %{$self->{temp_banlist}->{$channel}->{'+q'}}) { + my $data = $self->{quietlist}->get_data($channel, $mask); + $data->{owner} = $self->{temp_banlist}->{$channel}->{'+q'}->{$mask}->[0]; + $data->{timestamp} = $self->{temp_banlist}->{$channel}->{'+q'}->{$mask}->[1]; + $self->{quietlist}->add($channel, $mask, $data, 1); + } + + $self->{quietlist}->save; +} + +sub track_mode { + my $self = shift; + my ($source, $channel, $mode, $mask) = @_; + + my ($nick) = $source =~ /(^[^!]+)/; + $channel = lc $channel; + $mask = lc $mask; + + if ($mode eq "+b" or $mode eq "+q") { + $self->{pbot}->{logger}->log("banlist: $mask " . ($mode eq '+b' ? 'banned' : 'quieted') . " by $source in $channel.\n"); + + my $data = { + owner => $source, + timestamp => gettimeofday, + }; + + if ($mode eq "+b") { + $self->{banlist}->add($channel, $mask, $data); + } elsif ($mode eq "+q") { + $self->{quietlist}->add($channel, $mask, $data); + } + + $self->{pbot}->{antiflood}->devalidate_accounts($mask, $channel); + } elsif ($mode eq "-b" or $mode eq "-q") { + $self->{pbot}->{logger}->log("banlist: $mask " . ($mode eq '-b' ? 'unbanned' : 'unquieted') . " by $source in $channel.\n"); + + if ($mode eq "-b") { + $self->{banlist}->remove($channel, $mask); + $self->{pbot}->{timer}->dequeue_event("unban $channel $mask"); + + # freenode strips channel forwards from unban result if no ban exists with a channel forward + $self->{banlist}->remove($channel, "$mask\$##stop_join_flood"); + $self->{pbot}->{timer}->dequeue_event(lc "unban $channel $mask\$##stop_join_flood"); + } elsif ($mode eq "-q") { + $self->{quietlist}->remove($channel, $mask); + $self->{pbot}->{timer}->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}->{timer}->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 *!*@... banmask', + 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 "+q") { + 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}->{timer}->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, 'q', $mask, $self->{pbot}->{registry}->get_value('banlist', 'mute_timeout')); + } + } + } +} + +sub ban_user { + my ($self, $channel, $mode, $mask, $immediately) = @_; + $mode ||= 'b'; + $self->{pbot}->{logger}->log("Banning $channel +$mode $mask\n"); + $self->add_to_ban_queue($channel, $mode, $mask); + if (not defined $immediately or $immediately != 0) { + $self->flush_ban_queue; + } +} + +sub unban_user { + my ($self, $channel, $mode, $mask, $immediately) = @_; + $mask = lc $mask; + $channel = lc $channel; + $mode ||= 'b'; + $self->{pbot}->{logger}->log("Unbanning $channel -$mode $mask\n"); + $self->unmode_user($channel, $mode, $mask, $immediately); +} + +sub unmode_user { + my ($self, $channel, $mode, $mask, $immediately) = @_; + + $mask = lc $mask; + $channel = lc $channel; + $self->{pbot}->{logger}->log("Removing mode $mode from $mask in $channel\n"); + + my $bans = $self->get_bans($channel, $mask); + my %unbanned; + + if (not defined $bans) { + push @$bans, { banmask => $mask, type => "+$mode" }; + } + + foreach my $ban (@$bans) { + next if $ban->{type} ne "+$mode"; + next if exists $unbanned{$ban->{banmask}}; + $unbanned{$ban->{banmask}} = 1; + $self->add_to_unban_queue($channel, $mode, $ban->{banmask}); + } + + $self->flush_unban_queue if $immediately; +} + +sub get_bans { + my ($self, $channel, $mask) = @_; + + my $masks; + my ($message_account, $hostmask); + + if ($mask !~ m/[!@]/) { + ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask); + $hostmask = $mask if not defined $message_account; + } else { + $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account_id($mask); + $hostmask = $mask; + } + + if (defined $message_account) { + my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account); + $masks = $self->get_baninfo($channel, $hostmask, $nickserv); + } + + my %akas = $self->{pbot}->{messagehistory}->{database}->get_also_known_as($hostmask); + + foreach my $aka (keys %akas) { + next if $akas{$aka}->{type} == $self->{pbot}->{messagehistory}->{database}->{alias_type}->{WEAK}; + next if $akas{$aka}->{nickchange} == 1; + + my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($akas{$aka}->{id}); + + my $b = $self->get_baninfo($channel, $aka, $nickserv); + if (defined $b) { + push @$masks, @$b; + } + } + + return $masks; +} + sub get_baninfo { - my ($self, $mask, $channel, $account) = @_; - my ($bans, $ban_account); + my ($self, $channel, $mask, $nickserv) = @_; + my ($bans, $ban_nickserv); - $account = undef if not length $account; - $account = lc $account if defined $account; + $nickserv = undef if not length $nickserv; + $nickserv = lc $nickserv if defined $nickserv; - if ($self->{pbot}->{registry}->get_value('bantracker', 'debug')) { - $self->{pbot}->{logger}->log("[get-baninfo] Getting baninfo for $mask in $channel using account " . (defined $account ? $account : "[undefined]") . "\n"); + if ($self->{pbot}->{registry}->get_value('banlist', 'debug')) { + my $ns = defined $nickserv ? $nickserv : "[undefined]"; + $self->{pbot}->{logger}->log("[get-baninfo] Getting baninfo for $mask in $channel using nickserv $ns\n"); } my ($nick, $user, $host) = $mask =~ m/([^!]+)!([^@]+)@(.*)/; - foreach my $mode (keys %{$self->{banlist}->{$channel}}) { - foreach my $banmask (keys %{$self->{banlist}->{$channel}->{$mode}}) { - if ($banmask =~ m/^\$a:(.*)/) { $ban_account = lc $1; } - else { $ban_account = ""; } + my @lists = ( + [ 'b', $self->{banlist} ], + [ 'q', $self->{quietlist} ], + ); - my $banmask_key = $banmask; - $banmask = quotemeta $banmask; - $banmask =~ s/\\\*/.*?/g; - $banmask =~ s/\\\?/./g; + foreach my $entry (@lists) { + my ($mode, $list) = @$entry; + foreach my $banmask ($list->get_keys($channel)) { + if ($banmask =~ m/^\$a:(.*)/) { $ban_nickserv = lc $1; } + else { $ban_nickserv = ""; } + + my $banmask_regex = quotemeta $banmask; + $banmask_regex =~ s/\\\*/.*?/g; + $banmask_regex =~ s/\\\?/./g; my $banned; + $banned = 1 if defined $nickserv and $nickserv eq $ban_nickserv; + $banned = 1 if $mask =~ m/^$banmask_regex$/i; - $banned = 1 if defined $account and $account eq $ban_account; - $banned = 1 if $mask =~ m/^$banmask$/i; - - if ($banmask_key =~ m{\@gateway/web/irccloud.com} and $host =~ m{^gateway/web/irccloud.com}) { - my ($bannick, $banuser, $banhost) = $banmask_key =~ m/([^!]+)!([^@]+)@(.*)/; - - if (lc $user eq lc $banuser) { $banned = 1; } + if ($banmask =~ m{\@gateway/web/irccloud.com} and $host =~ m{^gateway/web/irccloud.com}) { + my ($bannick, $banuser, $banhost) = $banmask =~ m/([^!]+)!([^@]+)@(.*)/; + $banned = $1 if lc $user eq lc $banuser; } if ($banned) { - if (not defined $bans) { $bans = []; } - - my $baninfo = {}; - $baninfo->{banmask} = $banmask_key; - $baninfo->{channel} = $channel; - $baninfo->{owner} = $self->{banlist}->{$channel}->{$mode}->{$banmask_key}->[0]; - $baninfo->{when} = $self->{banlist}->{$channel}->{$mode}->{$banmask_key}->[1]; - $baninfo->{type} = $mode; + my $data = $list->get_data($channel, $banmask); + my $baninfo = { + mask => $banmask, + channel => $channel, + owner => $data->{owner}, + when => $data->{timestamp}, + type => $mode, + reason => $data->{reason}, + timeout => $data->{timeout}, + }; push @$bans, $baninfo; } } @@ -156,8 +489,237 @@ sub get_baninfo { return $bans; } +sub nick_to_banmask { + my ($self, $mask) = @_; + + if ($mask !~ m/[!@\$]/) { + my ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask); + if (defined $hostmask) { + my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account); + if (defined $nickserv && length $nickserv) { $mask = '$a:' . $nickserv; } + else { + my ($nick, $user, $host) = $hostmask =~ m/([^!]+)!([^@]+)@(.*)/; + $mask = "*!$user\@" . $self->{pbot}->{antiflood}->address_to_mask($host); + } + } else { + $mask .= '!*@*'; + } + } + + 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}->add($channel, $mask, $data); + } elsif ($mode eq 'q') { + $self->{quietlist}->add($channel, $mask, $data); + } + + my $method = $mode eq 'b' ? 'unban' : 'unmute'; + $self->{pbot}->{timer}->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 'q') { + $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 ago) "; + } + + $result .= "by $data->{owner} " if defined $data->{owner}; + $result .= "for $data->{reason} " if defined $data->{reason}; + if ($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}}) { + push @{$self->{ban_queue}->{$channel}->{$mode}}, $mask; + $self->{pbot}->{logger}->log("Added +$mode $mask for $channel to ban queue.\n"); + } +} + +sub flush_ban_queue { + my $self = shift; + + my $MAX_COMMANDS = 4; + my $commands = 0; + + foreach my $channel (keys %{$self->{ban_queue}}) { + my $done = 0; + while (not $done) { + my ($list, $count, $modes); + $list = ''; + $modes = '+'; + $count = 0; + + foreach my $mode (keys %{$self->{ban_queue}->{$channel}}) { + while (@{$self->{ban_queue}->{$channel}->{$mode}}) { + my $target = pop @{$self->{ban_queue}->{$channel}->{$mode}}; + $list .= " $target"; + $modes .= $mode; + last if ++$count >= $self->{pbot}->{ircd}->{MODES}; + } + + if (not @{$self->{ban_queue}->{$channel}->{$mode}}) { + delete $self->{ban_queue}->{$channel}->{$mode}; + } + + last if $count >= $self->{pbot}->{ircd}->{MODES}; + } + + if (not keys %{$self->{ban_queue}->{$channel}}) { + delete $self->{ban_queue}->{$channel}; + $done = 1; + } + + if ($count) { + $self->{pbot}->{chanops}->add_op_command($channel, "mode $channel $modes $list"); + $self->{pbot}->{chanops}->gain_ops($channel); + return if ++$commands >= $MAX_COMMANDS; + } + } + } +} + +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; + + my $MAX_COMMANDS = 4; + my $commands = 0; + + foreach my $channel (keys %{$self->{unban_queue}}) { + my $done = 0; + while (not $done) { + my ($list, $count, $modes); + $list = ''; + $modes = '-'; + $count = 0; + + foreach my $mode (keys %{$self->{unban_queue}->{$channel}}) { + while (@{$self->{unban_queue}->{$channel}->{$mode}}) { + my $target = pop @{$self->{unban_queue}->{$channel}->{$mode}}; + $list .= " $target"; + $modes .= $mode; + last if ++$count >= $self->{pbot}->{ircd}->{MODES}; + } + + if (not @{$self->{unban_queue}->{$channel}->{$mode}}) { + delete $self->{unban_queue}->{$channel}->{$mode}; + } + + last if $count >= $self->{pbot}->{ircd}->{MODES}; + } + + if (not keys %{$self->{unban_queue}->{$channel}}) { + delete $self->{unban_queue}->{$channel}; + $done = 1; + } + + if ($count) { + $self->{pbot}->{chanops}->add_op_command($channel, "mode $channel $modes $list"); + $self->{pbot}->{chanops}->gain_ops($channel); + return if ++$commands >= $MAX_COMMANDS; + } + } + } +} + +sub enqueue_unban { + my ($self, $channel, $mode, $hostmask, $interval) = @_; + + my $method = $mode eq 'b' ? 'unban' : 'unmute'; + + $self->{pbot}->{timer}->enqueue_event( + sub { + $self->{pbot}->{timer}->update_interval("$method $channel $hostmask", 60 * 15, 1); # try again in 15 minutes + return if not $self->{pbot}->{joined_channels}; + $self->unban_user($channel, $mode, $hostmask); + }, $interval, "$method $channel $hostmask", 1 + ); +} + +sub enqueue_timeouts { + my ($self, $list, $mode) = @_; + my $now = time; + + foreach my $channel ($list->get_keys) { + foreach my $mask ($list->get_keys($channel)) { + my $timeout = $list->get_data($channel, $mask, 'timeout'); + next if $timeout <= 0; + my $interval = $timeout - $now; + $interval = 10 if $interval < 10; + $self->enqueue_unban($channel, $mode, $mask, $interval); + } + } +} + +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, $nick, $user, $host, $channel) = @_; + 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); @@ -166,18 +728,18 @@ sub is_banned { my $banned = undef; foreach my $nickserv_account (@nickserv_accounts) { - my $baninfos = $self->get_baninfo("$nick!$user\@$host", $channel, $nickserv_account); + 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->{banmask}) || $whitelisted) { - $self->{pbot}->{logger}->log("[BanTracker] is_banned: $nick!$user\@$host banned as $baninfo->{banmask} in $baninfo->{channel}, but allowed through whitelist\n"); + 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("[BanTracker] is_banned: $nick!$user\@$host $mode as $baninfo->{banmask} in $baninfo->{channel} by $baninfo->{owner}\n"); + $self->{pbot}->{logger}->log("[BanList] is_banned: $nick!$user\@$host $mode as $baninfo->{mask} in $baninfo->{channel} by $baninfo->{owner}\n"); $banned = $baninfo; last; } @@ -185,46 +747,8 @@ sub is_banned { } } } + return $banned; } -sub track_mode { - my $self = shift; - my ($source, $mode, $target, $channel) = @_; - - $mode = lc $mode; - $target = lc $target; - $channel = lc $channel; - - if ($mode eq "+b" or $mode eq "+q") { - $self->{pbot}->{logger}->log("ban-tracker: $target " . ($mode eq '+b' ? 'banned' : 'quieted') . " by $source in $channel.\n"); - $self->{banlist}->{$channel}->{$mode}->{$target} = [$source, gettimeofday]; - $self->{pbot}->{antiflood}->devalidate_accounts($target, $channel); - } elsif ($mode eq "-b" or $mode eq "-q") { - $self->{pbot}->{logger}->log("ban-tracker: $target " . ($mode eq '-b' ? 'unbanned' : 'unquieted') . " by $source in $channel.\n"); - delete $self->{banlist}->{$channel}->{$mode eq "-b" ? "+b" : "+q"}->{$target}; - - if ($mode eq "-b") { - if ($self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $target)) { - $self->{pbot}->{chanops}->{unban_timeout}->remove($channel, $target); - $self->{pbot}->{timer}->dequeue_event("unban_timeout $channel $target"); - } - elsif ($self->{pbot}->{chanops}->{unban_timeout}->exists($channel, "$target\$##stop_join_flood")) { - # freenode strips channel forwards from unban result if no ban exists with a channel forward - $self->{pbot}->{chanops}->{unban_timeout}->remove($channel, "$target\$##stop_join_flood"); - $self->{pbot}->{timer}->dequeue_event(lc "unban_timeout $channel $target\$##stop_join_flood"); - } - } elsif ($mode eq "-q") { - if ($self->{pbot}->{chanops}->{unmute_timeout}->exists($channel, $target)) { - $self->{pbot}->{chanops}->{unmute_timeout}->remove($channel, $target); - $self->{pbot}->{timer}->dequeue_event("unmute_timeout $channel $target"); - } else { - $self->{pbot}->{logger}->log("No unmute timeout for $channel $target\n"); - } - } - } else { - $self->{pbot}->{logger}->log("BanTracker: Unknown mode '$mode'\n"); - } -} - 1; diff --git a/PBot/ChanOpCommands.pm b/PBot/ChanOpCommands.pm index fb21efb5..57e657a0 100644 --- a/PBot/ChanOpCommands.pm +++ b/PBot/ChanOpCommands.pm @@ -26,8 +26,6 @@ sub initialize { $self->{pbot}->{commands}->register(sub { $self->mute_user(@_) }, "mute", 1); $self->{pbot}->{commands}->register(sub { $self->unmute_user(@_) }, "unmute", 1); $self->{pbot}->{commands}->register(sub { $self->kick_user(@_) }, "kick", 1); - $self->{pbot}->{commands}->register(sub { $self->checkban(@_) }, "checkban", 0); - $self->{pbot}->{commands}->register(sub { $self->checkmute(@_) }, "checkmute", 0); $self->{pbot}->{commands}->register(sub { $self->op_user(@_) }, "op", 1); $self->{pbot}->{commands}->register(sub { $self->deop_user(@_) }, "deop", 1); $self->{pbot}->{commands}->register(sub { $self->voice_user(@_) }, "voice", 1); @@ -337,28 +335,6 @@ sub mode { else { return ""; } } -sub checkban { - my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_; - my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2); - - return "Usage: checkban [channel]" if not defined $target; - $channel = $from if not defined $channel; - - return "Please specify a channel." if $channel !~ /^#/; - return $self->{pbot}->{chanops}->checkban($channel, $target); -} - -sub checkmute { - my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_; - my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2); - - return "Usage: checkmute [channel]" if not defined $target; - $channel = $from if not defined $channel; - - return "Please specify a channel." if $channel !~ /^#/; - return $self->{pbot}->{chanops}->checkmute($channel, $target); -} - sub ban_user { my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_; my ($target, $channel, $length) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3); @@ -383,6 +359,7 @@ sub ban_user { my $no_length = 0; if (not defined $length) { + # TODO: user account length override $length = $self->{pbot}->{registry}->get_value($channel, 'default_ban_timeout', 0, $stuff) // $self->{pbot}->{registry}->get_value('general', 'default_ban_timeout', 0, $stuff) // 60 * 60 * 24; # 24 hours $no_length = 1; @@ -405,15 +382,16 @@ sub ban_user { my $duration; foreach my $t (@targets) { - my $mask = lc $self->{pbot}->{chanops}->nick_to_banmask($t); + my $mask = lc $self->{pbot}->{banlist}->nick_to_banmask($t); - if ($no_length && $self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $mask)) { - my $timeout = $self->{pbot}->{chanops}->{unban_timeout}->get_data($channel, $mask, 'timeout'); - my $d = duration($timeout - gettimeofday); + my $timeout = $self->{pbot}->{banlist}->{banlist}->get_data($channel, $mask, 'timeout') // 0; + + if ($no_length && $timeout > 0) { + my $d = duration($timeout - gettimeofday); $result .= "$sep$mask has $d remaining on their $channel ban"; $sep = '; '; } else { - $self->{pbot}->{chanops}->ban_user_timed("$nick!$user\@$host", undef, $mask, $channel, $length, $immediately); + $self->{pbot}->{banlist}->ban_user_timed($channel, 'b', $mask, $length, "$nick!$user\@$host", undef, $immediately); $duration = $length > 0 ? duration $length : 'all eternity'; if ($immediately) { $result .= "$sep$mask banned in $channel for $duration"; @@ -427,7 +405,7 @@ sub ban_user { if (not $immediately) { $result .= " banned in $channel for $duration"; - $self->{pbot}->{chanops}->check_ban_queue; + $self->{pbot}->{banlist}->flush_ban_queue; } $result = "/msg $nick $result" if $result !~ m/remaining on their/; @@ -468,17 +446,19 @@ sub unban_user { return "/msg $nick Clearing the channel bans requires the can-clear-bans capability, which your user account does not have."; } $channel = lc $channel; - if (exists $self->{pbot}->{bantracker}->{banlist}->{$channel} && exists $self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+b'}) { + if ($self->{pbot}->{banlist}->{banlist}->exists($channel)) { $immediately = 0; - foreach my $banmask (keys %{$self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+b'}}) { $self->{pbot}->{chanops}->unban_user($banmask, $channel, $immediately); } + foreach my $banmask ($self->{pbot}->{banlist}->{banlist}->get_keys($channel)) { + $self->{pbot}->{banlist}->unban_user($banmask, 'b', $channel, $immediately); + } last; } } else { - $self->{pbot}->{chanops}->unban_user($t, $channel, $immediately); + $self->{pbot}->{banlist}->unban_user($t, 'b', $channel, $immediately); } } - $self->{pbot}->{chanops}->check_unban_queue if not $immediately; + $self->{pbot}->{banlist}->flush_unban_queue if not $immediately; return "/msg $nick $target has been unbanned from $channel."; } @@ -531,15 +511,16 @@ sub mute_user { my $duration; foreach my $t (@targets) { - my $mask = lc $self->{pbot}->{chanops}->nick_to_banmask($t); + my $mask = lc $self->{pbot}->{banlist}->nick_to_banmask($t); - if ($no_length && $self->{pbot}->{chanops}->{unmute_timeout}->exists($channel, $mask)) { - my $timeout = $self->{pbot}->{chanops}->{unmute_timeout}->get_data($channel, $mask, 'timeout'); - my $d = duration($timeout - gettimeofday); + my $timeout = $self->{pbot}->{banlist}->{quietlist}->get_data($channel, $mask, 'timeout') // 0; + + if ($no_length && $timeout > 0) { + my $d = duration($timeout - gettimeofday); $result .= "$sep$mask has $d remaining on their $channel mute"; $sep = '; '; } else { - $self->{pbot}->{chanops}->mute_user_timed("$nick!$user\@$host", undef, $t, $channel, $length, $immediately); + $self->{pbot}->{banlist}->ban_user_timed($channel, 'q', $t, $length, "$nick!$user\@$host", undef, $immediately); $duration = $length > 0 ? duration $length : 'all eternity'; if ($immediately) { $result .= "$sep$mask muted in $channel for $duration"; @@ -553,7 +534,7 @@ sub mute_user { if (not $immediately) { $result .= " muted in $channel for $duration"; - $self->{pbot}->{chanops}->check_ban_queue; + $self->{pbot}->{banlist}->flush_ban_queue; } $result = "/msg $nick $result" if $result !~ m/remaining on their/; @@ -594,17 +575,19 @@ sub unmute_user { return "/msg $nick Clearing the channel mutes requires the can-clear-mutes capability, which your user account does not have."; } $channel = lc $channel; - if (exists $self->{pbot}->{bantracker}->{banlist}->{$channel} && exists $self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+q'}) { + if ($self->{pbot}->{banlist}->{quietlist}->exists($channel)) { $immediately = 0; - foreach my $banmask (keys %{$self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+q'}}) { $self->{pbot}->{chanops}->unmute_user($banmask, $channel, $immediately); } + foreach my $banmask ($self->{pbot}->{banlist}->{quietlist}->get_keys($channel)) { + $self->{pbot}->{banlist}->unban_user($channel, 'q', $banmask, $immediately); + } last; } } else { - $self->{pbot}->{chanops}->unmute_user($t, $channel, $immediately); + $self->{pbot}->{banlist}->unban_user($channel, 'q', $t, $immediately); } } - $self->{pbot}->{chanops}->check_unban_queue if not $immediately; + $self->{pbot}->{banlist}->flush_unban_queue if not $immediately; return "/msg $nick $target has been unmuted in $channel."; } diff --git a/PBot/ChanOps.pm b/PBot/ChanOps.pm index 8587ed71..d24c91a9 100644 --- a/PBot/ChanOps.pm +++ b/PBot/ChanOps.pm @@ -21,27 +21,6 @@ use Time::Duration qw(concise duration); sub initialize { my ($self, %conf) = @_; - $self->{unban_timeout} = PBot::DualIndexHashObject->new( - pbot => $self->{pbot}, - name => 'Unban Timeouts', - filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/unban_timeouts' - ); - - $self->{unban_timeout}->load; - - $self->{unmute_timeout} = PBot::DualIndexHashObject->new( - pbot => $self->{pbot}, - name => 'Unmute Timeouts', - filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/unmute_timeouts' - ); - - $self->{unmute_timeout}->load; - - $self->enqueue_timeouts; - - $self->{ban_queue} = {}; - $self->{unban_queue} = {}; - $self->{op_commands} = {}; $self->{is_opped} = {}; $self->{op_requested} = {}; @@ -51,7 +30,28 @@ sub initialize { $self->{pbot}->{registry}->add_default('text', 'general', 'deop_timeout', 300); $self->{pbot}->{timer}->register(sub { $self->check_opped_timeouts }, 10, 'Check Opped Timeouts'); - $self->{pbot}->{timer}->register(sub { $self->check_unban_queue }, 30, 'Check Unban Queue'); +} + +sub track_mode { + my ($self, $source, $channel, $mode, $target) = @_; + + $channel = lc $channel; + $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"); + } + } } sub can_gain_ops { @@ -121,389 +121,6 @@ sub perform_op_commands { $self->{pbot}->{logger}->log("Done.\n"); } -sub ban_user { - my $self = shift; - my ($mask, $channel, $immediately) = @_; - $self->add_to_ban_queue($channel, 'b', $mask); - if (not defined $immediately or $immediately != 0) { $self->check_ban_queue; } -} - -sub get_bans { - my ($self, $mask, $channel) = @_; - my $masks; - - if ($mask !~ m/[!@\$]/) { - my ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask); - - if (defined $hostmask) { - my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account); - $masks = $self->{pbot}->{bantracker}->get_baninfo($hostmask, $channel, $nickserv); - } - - my %akas = $self->{pbot}->{messagehistory}->{database}->get_also_known_as($mask); - - foreach my $aka (keys %akas) { - next if $akas{$aka}->{type} == $self->{pbot}->{messagehistory}->{database}->{alias_type}->{WEAK}; - next if $akas{$aka}->{nickchange} == 1; - - my $b = $self->{pbot}->{bantracker}->get_baninfo($aka, $channel); - if (defined $b) { - $masks = {} if not defined $masks; - push @$masks, @$b; - } - } - } - return $masks; -} - -sub unmode_user { - my ($self, $mask, $channel, $immediately, $mode) = @_; - - $mask = lc $mask; - $channel = lc $channel; - $self->{pbot}->{logger}->log("Removing mode $mode from $mask in $channel\n"); - - my $bans = $self->get_bans($mask, $channel); - my %unbanned; - - if (not defined $bans) { - my $baninfo = {}; - $baninfo->{banmask} = $mask; - $baninfo->{type} = '+' . $mode; - push @$bans, $baninfo; - } - - foreach my $baninfo (@$bans) { - next if $baninfo->{type} ne '+' . $mode; - next if exists $unbanned{$baninfo->{banmask}}; - $unbanned{$baninfo->{banmask}} = 1; - $self->add_to_unban_queue($channel, $mode, $baninfo->{banmask}); - } - $self->check_unban_queue if $immediately; -} - -sub nick_to_banmask { - my ($self, $mask) = @_; - - if ($mask !~ m/[!@\$]/) { - my ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask); - if (defined $hostmask) { - my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account); - if (defined $nickserv && length $nickserv) { $mask = '$a:' . $nickserv; } - else { - my ($nick, $user, $host) = $hostmask =~ m/([^!]+)!([^@]+)@(.*)/; - $mask = "*!$user\@" . $self->{pbot}->{antiflood}->address_to_mask($host); - } - } else { - $mask .= '!*@*'; - } - } - return $mask; -} - -sub unban_user { - my ($self, $mask, $channel, $immediately) = @_; - $mask = lc $mask; - $channel = lc $channel; - $self->{pbot}->{logger}->log("Unbanning $channel $mask\n"); - $self->unmode_user($mask, $channel, $immediately, 'b'); -} - -sub ban_user_timed { - my ($self, $owner, $reason, $mask, $channel, $length, $immediately) = @_; - - $channel = lc $channel; - $mask = lc $mask; - - $mask = $self->nick_to_banmask($mask); - $self->ban_user($mask, $channel, $immediately); - - my $data = {}; - $data->{timeout} = $length > 0 ? gettimeofday + $length : -1; - $data->{owner} = $owner if defined $owner; - $data->{reason} = $reason if defined $reason; - $self->{unban_timeout}->add($channel, $mask, $data); - - $self->{pbot}->{timer}->dequeue_event("unban_timeout $channel $mask"); - - if ($length > 0) { - $self->enqueue_unban_timeout($channel, $mask, $length); - } -} - -sub checkban { - my ($self, $channel, $target) = @_; - my $mask = $self->nick_to_banmask($target); - - if ($self->{unban_timeout}->exists($channel, $mask)) { - my $timeout = $self->{unban_timeout}->get_data($channel, $mask, 'timeout'); - my $owner = $self->{unban_timeout}->get_data($channel, $mask, 'owner'); - my $reason = $self->{unban_timeout}->get_data($channel, $mask, 'reason'); - my $duration = concise duration($timeout - gettimeofday); - - my $result = "$mask banned in $channel "; - - $result .= "by $owner " if defined $owner; - $result .= "for $reason " if defined $reason; - $result .= $timeout > 0 ? "($duration remaining)" : "(permanent)"; - return $result; - } else { - return "$mask has no ban timeout."; - } -} - -sub mute_user { - my $self = shift; - my ($mask, $channel, $immediately) = @_; - $self->add_to_ban_queue($channel, 'q', $mask); - if (not defined $immediately or $immediately != 0) { $self->check_ban_queue; } -} - -sub unmute_user { - my ($self, $mask, $channel, $immediately) = @_; - $mask = lc $mask; - $channel = lc $channel; - $self->{pbot}->{logger}->log("Unmuting $channel $mask\n"); - $self->unmode_user($mask, $channel, $immediately, 'q'); -} - -sub mute_user_timed { - my $self = shift; - my ($owner, $reason, $mask, $channel, $length, $immediately) = @_; - - $mask = $self->nick_to_banmask($mask); - $self->mute_user($mask, $channel, $immediately); - - my $data = {}; - $data->{timeout} = $length > 0 ? gettimeofday + $length : -1; - $data->{owner} = $owner if defined $owner; - $data->{reason} = $reason if defined $reason; - $self->{unmute_timeout}->add($channel, $mask, $data); - - $self->{pbot}->{timer}->dequeue_event("unmute_timeout $channel $mask"); - - if ($length > 0) { - $self->enqueue_unmute_timeout($channel, $mask, $length); - } -} - -sub checkmute { - my ($self, $channel, $target) = @_; - my $mask = $self->nick_to_banmask($target); - - if ($self->{unmute_timeout}->exists($channel, $mask)) { - my $timeout = $self->{unmute_timeout}->get_data($channel, $mask, 'timeout'); - my $owner = $self->{unmute_timeout}->get_data($channel, $mask, 'owner'); - my $reason = $self->{unmute_timeout}->get_data($channel, $mask, 'reason'); - my $duration = concise duration($timeout - gettimeofday); - - my $result = "$mask muted in $channel "; - - $result .= "by $owner " if defined $owner; - $result .= "for $reason " if defined $reason; - $result .= $timeout > 0 ? "($duration remaining)" : "(permanent)"; - - return $result; - } else { - return "$mask has no mute timeout."; - } -} - -sub join_channel { - my ($self, $channels) = @_; - - $self->{pbot}->{conn}->join($channels); - - foreach my $channel (split /,/, $channels) { - $channel = lc $channel; - $self->{pbot}->{event_dispatcher}->dispatch_event('pbot.join', {channel => $channel}); - - delete $self->{is_opped}->{$channel}; - delete $self->{op_requested}->{$channel}; - - if ($self->{pbot}->{channels}->{channels}->exists($channel) and $self->{pbot}->{channels}->{channels}->get_data($channel, 'permop')) { $self->gain_ops($channel); } - - $self->{pbot}->{conn}->mode($channel); - } -} - -sub part_channel { - my ($self, $channel) = @_; - $channel = lc $channel; - $self->{pbot}->{event_dispatcher}->dispatch_event('pbot.part', {channel => $channel}); - $self->{pbot}->{conn}->part($channel); - delete $self->{is_opped}->{$channel}; - delete $self->{op_requested}->{$channel}; -} - -sub has_ban_timeout { - my ($self, $channel, $mask) = @_; - return $self->{unban_timeout}->exists($channel, $mask); -} - -sub has_mute_timeout { - my ($self, $channel, $mask) = @_; - return $self->{unmute_timeout}->exists($channel, $mask); -} - -sub add_to_ban_queue { - my ($self, $channel, $mode, $target) = @_; - push @{$self->{ban_queue}->{$channel}->{$mode}}, $target; - $self->{pbot}->{logger}->log("Added +$mode $target for $channel to ban queue.\n"); -} - -sub check_ban_queue { - my $self = shift; - - my $MAX_COMMANDS = 4; - my $commands = 0; - - foreach my $channel (keys %{$self->{ban_queue}}) { - my $done = 0; - while (not $done) { - my ($list, $count, $modes); - $list = ''; - $modes = '+'; - $count = 0; - - foreach my $mode (keys %{$self->{ban_queue}->{$channel}}) { - while (@{$self->{ban_queue}->{$channel}->{$mode}}) { - my $target = pop @{$self->{ban_queue}->{$channel}->{$mode}}; - $list .= " $target"; - $modes .= $mode; - last if ++$count >= $self->{pbot}->{ircd}->{MODES}; - } - - if (not @{$self->{ban_queue}->{$channel}->{$mode}}) { delete $self->{ban_queue}->{$channel}->{$mode}; } - - last if $count >= $self->{pbot}->{ircd}->{MODES}; - } - - if (not keys %{$self->{ban_queue}->{$channel}}) { - delete $self->{ban_queue}->{$channel}; - $done = 1; - } - - if ($count) { - $self->add_op_command($channel, "mode $channel $modes $list"); - $self->gain_ops($channel); - - return if ++$commands >= $MAX_COMMANDS; - } - } - } -} - -sub add_to_unban_queue { - my ($self, $channel, $mode, $target) = @_; - if (not grep { $_ eq $target } @{$self->{unban_queue}->{$channel}->{$mode}}) { - push @{$self->{unban_queue}->{$channel}->{$mode}}, $target; - $self->{pbot}->{logger}->log("Added -$mode $target for $channel to unban queue.\n"); - } -} - -sub check_unban_queue { - my $self = shift; - - my $MAX_COMMANDS = 4; - my $commands = 0; - - foreach my $channel (keys %{$self->{unban_queue}}) { - my $done = 0; - while (not $done) { - my ($list, $count, $modes); - $list = ''; - $modes = '-'; - $count = 0; - - foreach my $mode (keys %{$self->{unban_queue}->{$channel}}) { - while (@{$self->{unban_queue}->{$channel}->{$mode}}) { - my $target = pop @{$self->{unban_queue}->{$channel}->{$mode}}; - $list .= " $target"; - $modes .= $mode; - last if ++$count >= $self->{pbot}->{ircd}->{MODES}; - } - - if (not @{$self->{unban_queue}->{$channel}->{$mode}}) { delete $self->{unban_queue}->{$channel}->{$mode}; } - - last if $count >= $self->{pbot}->{ircd}->{MODES}; - } - - if (not keys %{$self->{unban_queue}->{$channel}}) { - delete $self->{unban_queue}->{$channel}; - $done = 1; - } - - if ($count) { - $self->add_op_command($channel, "mode $channel $modes $list"); - $self->gain_ops($channel); - - return if ++$commands >= $MAX_COMMANDS; - } - } - } -} - -sub enqueue_unmute_timeout { - my ($self, $channel, $hostmask, $interval) = @_; - - $self->{pbot}->{timer}->enqueue_event( - sub { - return if not $self->{pbot}->{joined_channels}; - $self->{pbot}->{timer}->update_interval("unmute_timeout $channel $hostmask", 60 * 15, 1); # try again in 15 minutes - $self->unmute_user($hostmask, $channel); - }, $interval, "unmute_timeout $channel $hostmask", 1 - ); -} - -sub enqueue_unban_timeout { - my ($self, $channel, $hostmask, $interval) = @_; - - $self->{pbot}->{timer}->enqueue_event( - sub { - return if not $self->{pbot}->{joined_channels}; - $self->{pbot}->{timer}->update_interval("unban_timeout $channel $hostmask", 60 * 15, 1); # try again in 15 minutes - $self->unban_user($hostmask, $channel); - }, $interval, "unban_timeout $channel $hostmask", 1 - ); -} - -sub enqueue_unmute_timeouts { - my ($self) = @_; - my $now = time; - - foreach my $channel ($self->{unmute_timeout}->get_keys) { - foreach my $mask ($self->{unmute_timeout}->get_keys($channel)) { - my $timeout = $self->{unmute_timeout}->get_data($channel, $mask, 'timeout'); - next if $timeout <= 0; - my $interval = $timeout - $now; - $interval = 10 if $interval < 10; - $self->enqueue_unmute_timeout($channel, $mask, $interval); - } - } -} - -sub enqueue_unban_timeouts { - my ($self) = @_; - my $now = time; - - foreach my $channel ($self->{unban_timeout}->get_keys) { - foreach my $mask ($self->{unban_timeout}->get_keys($channel)) { - my $timeout = $self->{unban_timeout}->get_data($channel, $mask, 'timeout'); - next if $timeout <= 0; - my $interval = $timeout - $now; - $interval = 10 if $interval < 10; - $self->enqueue_unban_timeout($channel, $mask, $interval); - } - } -} - -sub enqueue_timeouts { - my ($self) = @_; - $self->enqueue_unmute_timeouts; - $self->enqueue_unban_timeouts; -} - sub check_opped_timeouts { my $self = shift; my $now = gettimeofday(); diff --git a/PBot/Channels.pm b/PBot/Channels.pm index e46cc24b..c2115fa6 100644 --- a/PBot/Channels.pm +++ b/PBot/Channels.pm @@ -18,34 +18,34 @@ sub initialize { $self->{channels} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Channels', filename => $conf{filename}); $self->{channels}->load; - $self->{pbot}->{commands}->register(sub { $self->join(@_) }, "join", 1); - $self->{pbot}->{commands}->register(sub { $self->part(@_) }, "part", 1); - $self->{pbot}->{commands}->register(sub { $self->set(@_) }, "chanset", 1); - $self->{pbot}->{commands}->register(sub { $self->unset(@_) }, "chanunset", 1); - $self->{pbot}->{commands}->register(sub { $self->add(@_) }, "chanadd", 1); - $self->{pbot}->{commands}->register(sub { $self->remove(@_) }, "chanrem", 1); - $self->{pbot}->{commands}->register(sub { $self->list(@_) }, "chanlist", 1); + $self->{pbot}->{commands}->register(sub { $self->join_cmd(@_) }, "join", 1); + $self->{pbot}->{commands}->register(sub { $self->part_cmd(@_) }, "part", 1); + $self->{pbot}->{commands}->register(sub { $self->set(@_) }, "chanset", 1); + $self->{pbot}->{commands}->register(sub { $self->unset(@_) }, "chanunset", 1); + $self->{pbot}->{commands}->register(sub { $self->add(@_) }, "chanadd", 1); + $self->{pbot}->{commands}->register(sub { $self->remove(@_) }, "chanrem", 1); + $self->{pbot}->{commands}->register(sub { $self->list(@_) }, "chanlist", 1); $self->{pbot}->{capabilities}->add('admin', 'can-join', 1); $self->{pbot}->{capabilities}->add('admin', 'can-part', 1); $self->{pbot}->{capabilities}->add('admin', 'can-chanlist', 1); } -sub join { +sub join_cmd { my ($self, $from, $nick, $user, $host, $arguments) = @_; foreach my $channel (split /[\s+,]/, $arguments) { $self->{pbot}->{logger}->log("$nick!$user\@$host made me join $channel\n"); - $self->{pbot}->{chanops}->join_channel($channel); + $self->join($channel); } return "/msg $nick Joining $arguments"; } -sub part { +sub part_cmd { my ($self, $from, $nick, $user, $host, $arguments) = @_; $arguments = $from if not $arguments; foreach my $channel (split /[\s+,]/, $arguments) { $self->{pbot}->{logger}->log("$nick!$user\@$host made me part $channel\n"); - $self->{pbot}->{chanops}->part_channel($channel); + $self->part($channel); } return "/msg $nick Parting $arguments"; } @@ -81,17 +81,11 @@ sub remove { my ($self, $from, $nick, $user, $host, $arguments) = @_; return "Usage: chanrem " if not defined $arguments or not length $arguments; - # clear unban timeouts - if ($self->{pbot}->{chanops}->{unban_timeout}->exists($arguments)) { - $self->{pbot}->{chanops}->{unban_timeout}->remove($arguments); - $self->{pbot}->{timer}->dequeue_event("unban_timeout $arguments .*"); - } - - # clear unmute timeouts - if ($self->{pbot}->{chanops}->{unmute_timeout}->exists($arguments)) { - $self->{pbot}->{chanops}->{unmute_timeout}->remove($arguments); - $self->{pbot}->{timer}->dequeue_event("unmute_timeout $arguments .*"); - } + # clear banlists + $self->{pbot}->{banlist}->remove($arguments); + $self->{pbot}->{quietlist}->remove($arguments); + $self->{pbot}->{timer}->dequeue_event("unban $arguments .*"); + $self->{pbot}->{timer}->dequeue_event("unmute $arguments .*"); # TODO: ignores, etc? return $self->{channels}->remove($arguments); @@ -112,21 +106,51 @@ sub list { return $result; } +sub join { + my ($self, $channels) = @_; + + $self->{pbot}->{conn}->join($channels); + + 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->{channels}->exists($channel) and $self->{channels}->get_data($channel, 'permop')) { + $self->gain_ops($channel); + } + + $self->{pbot}->{conn}->mode($channel); + } +} + +sub part { + my ($self, $channel) = @_; + $channel = lc $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 { my ($self) = @_; return if $self->{pbot}->{joined_channels}; my $channels; foreach my $channel ($self->{channels}->get_keys) { - if ($self->{channels}->get_data($channel, 'enabled')) { $channels .= $self->{channels}->get_key_name($channel) . ','; } + if ($self->{channels}->get_data($channel, 'enabled')) { + $channels .= $self->{channels}->get_key_name($channel) . ','; + } } $self->{pbot}->{logger}->log("Joining channels: $channels\n"); - $self->{pbot}->{chanops}->join_channel($channels); + $self->join($channels); $self->{pbot}->{joined_channels} = 1; } sub is_active { my ($self, $channel) = @_; - # returns undef if channel doesn't exist; otherwise, the value of 'enabled' return $self->{channels}->get_data($channel, 'enabled'); } diff --git a/PBot/IRCHandlers.pm b/PBot/IRCHandlers.pm index 3a081d06..332435eb 100644 --- a/PBot/IRCHandlers.pm +++ b/PBot/IRCHandlers.pm @@ -203,7 +203,7 @@ sub on_action { sub on_mode { my ($self, $event_type, $event) = @_; - my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host); + my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host); my $mode_string = $event->{event}->{args}[0]; my $channel = $event->{event}->{to}[0]; $channel = lc $channel; @@ -228,15 +228,18 @@ sub on_mode { $self->{pbot}->{logger}->log("Mode $channel [$mode" . (length $target ? " $target" : '') . "] by $nick!$user\@$host\n"); - if ($mode eq "-b" or $mode eq "+b" or $mode eq "-q" or $mode eq "+q") { $self->{pbot}->{bantracker}->track_mode("$nick!$user\@$host", $mode, $target, $channel); } + $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", $self->{pbot}->{messagehistory}->{MSG_CHAT}); if ($modifier eq '-') { $self->{pbot}->{nicklist}->delete_meta($channel, $target, "+$mode_char"); } else { $self->{pbot}->{nicklist}->set_meta($channel, $target, $mode, 1); } } else { + # mode set on channel my $modes = $self->{pbot}->{channels}->get_meta($channel, 'MODE'); if (defined $modes) { if ($modifier eq '+') { @@ -248,78 +251,8 @@ sub on_mode { $self->{pbot}->{channels}->{channels}->set($channel, 'MODE', $modes, 1); } } - - if (defined $target && $target eq $event->{conn}->nick) { # bot targeted - if ($mode eq "+o") { - $self->{pbot}->{logger}->log("$nick 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("$nick removed my ops in $channel\n"); - delete $self->{pbot}->{chanops}->{is_opped}->{$channel}; - } elsif ($mode eq "+b") { - $self->{pbot}->{logger}->log("Got banned in $channel, attempting unban."); - $event->{conn}->privmsg("chanserv", "unban $channel"); - } - } else { # bot not targeted - if ($mode eq "+b") { - if ($nick eq "ChanServ" or $target =~ m/##fix_your_connection$/i) { - if ($self->{pbot}->{chanops}->can_gain_ops($channel)) { - if ($self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $target)) { - $self->{pbot}->{chanops}->{unban_timeout}->set($channel, $target, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('bantracker', 'chanserv_ban_timeout')); - $self->{pbot}->{timer}->update_interval("unban_timeout $channel $target", $self->{pbot}->{registry}->get_value('bantracker', '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('bantracker', 'chanserv_ban_timeout'), - }; - $self->{pbot}->{chanops}->{unban_timeout}->add($channel, $target, $data); - $self->{pbot}->{chanops}->enqueue_unban_timeout($channel, $target, $self->{pbot}->{registry}->get_value('bantracker', 'chanserv_ban_timeout')); - } - } - } elsif ($target =~ m/^\*!\*@/ or $target =~ m/^\*!.*\@gateway\/web/i) { - my $timeout = 60 * 60 * 24 * 7; - - if ($target =~ m/\// and $target !~ m/\@gateway/) { - $timeout = 0; # permanent bans for cloaks that aren't gateway - } - - if ($timeout && $self->{pbot}->{chanops}->can_gain_ops($channel)) { - if (not $self->{pbot}->{chanops}->{unban_timeout}->exists($channel, $target)) { - $self->{pbot}->{logger}->log("Temp ban for $target in $channel.\n"); - my $data = { - reason => 'Temp ban for *!*@... banmask', - timeout => gettimeofday + $timeout, - owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'), - }; - $self->{pbot}->{chanops}->{unban_timeout}->add($channel, $target, $data); - $self->{pbot}->{chanops}->enqueue_unban_timeout($channel, $target, $timeout); - } - } - } - } elsif ($mode eq "+q") { - if ($nick ne $event->{conn}->nick) { # bot muted - if ($self->{pbot}->{chanops}->can_gain_ops($channel)) { - if ($self->{pbot}->{chanops}->{unmute_timeout}->exists($channel, $target)) { - $self->{pbot}->{chanops}->{unmute_timeout}->set($channel, $target, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('bantracker', 'chanserv_ban_timeout')); - $self->{pbot}->{timer}->update_interval("unmute_timeout $channel $target", $self->{pbot}->{registry}->get_value('bantracker', 'chanserv_ban_timeout')); - } else { - my $data = { - reason => 'Temp mute', - owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'), - timeout => gettimeofday + $self->{pbot}->{registry}->get_value('bantracker', 'mute_timeout'), - }; - $self->{pbot}->{chanops}->{unmute_timeout}->add($channel, $target, $data); - $self->{pbot}->{chanops}->enqueue_unmute_timeout($channel, $target, $self->{pbot}->{registry}->get_value('bantracker', 'mute_timeout')); - } - } - } - } - } } + return 0; } diff --git a/PBot/Interpreter.pm b/PBot/Interpreter.pm index 6e8b050d..1702b496 100644 --- a/PBot/Interpreter.pm +++ b/PBot/Interpreter.pm @@ -51,12 +51,12 @@ sub process_line { my $chanmodes = $self->{pbot}->{channels}->get_meta($from, 'MODE'); if (defined $chanmodes and $chanmodes =~ m/z/) { $stuff->{'chan-z'} = 1; - if (exists $self->{pbot}->{bantracker}->{banlist}->{$from}->{'+q'}->{'$~a'}) { + if ($self->{pbot}->{banlist}->{quietlist}->exists($from, '$~a')) { my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account); if (not defined $nickserv or not length $nickserv) { $stuff->{unidentified} = 1; } } - if ($self->{pbot}->{bantracker}->is_banned($nick, $user, $host, $from)) { $stuff->{banned} = 1; } + $stuff->{banned} = 1 if $self->{pbot}->{banlist}->is_banned($nick, $user, $host, $from); } } diff --git a/PBot/PBot.pm b/PBot/PBot.pm index c113ba80..74c6352e 100644 --- a/PBot/PBot.pm +++ b/PBot/PBot.pm @@ -29,7 +29,7 @@ use PBot::IRC; use PBot::EventDispatcher; use PBot::IRCHandlers; use PBot::Channels; -use PBot::BanTracker; +use PBot::BanList; use PBot::NickList; use PBot::LagChecker; use PBot::MessageHistory; @@ -231,7 +231,6 @@ sub initialize { $self->{select_handler} = PBot::SelectHandler->new(pbot => $self, %conf); $self->{users} = PBot::Users->new(pbot => $self, filename => "$data_dir/users", %conf); $self->{stdin_reader} = PBot::StdinReader->new(pbot => $self, %conf); - $self->{bantracker} = PBot::BanTracker->new(pbot => $self, %conf); $self->{lagchecker} = PBot::LagChecker->new(pbot => $self, %conf); $self->{messagehistory} = PBot::MessageHistory->new(pbot => $self, filename => "$data_dir/message_history.sqlite3", %conf); $self->{antiflood} = PBot::AntiFlood->new(pbot => $self, %conf); @@ -241,6 +240,7 @@ sub initialize { $self->{irc} = PBot::IRC->new(); $self->{channels} = PBot::Channels->new(pbot => $self, filename => "$data_dir/channels", %conf); $self->{chanops} = PBot::ChanOps->new(pbot => $self, %conf); + $self->{banlist} = PBot::BanList->new(pbot => $self, %conf); $self->{nicklist} = PBot::NickList->new(pbot => $self, %conf); $self->{webpaste} = PBot::WebPaste->new(pbot => $self, %conf); $self->{parsedate} = PBot::Utils::ParseDate->new(pbot => $self, %conf); @@ -319,7 +319,6 @@ sub connect { 'motdstart', 'endofmotd', 'away', - 'endofbanlist' ], sub { } ); @@ -479,18 +478,14 @@ sub reload { return "Channels reloaded."; }, - 'bantimeouts' => sub { - $self->{timer}->dequeue_event('unban_timeout .*'); - $self->{chanops}->{unban_timeout}->load; - $self->{chanops}->enqueue_unban_timeouts; - return "Ban timeouts reloaded."; - }, - - 'mutetimeouts' => sub { - $self->{timer}->dequeue_event('unmute_timeout .*'); - $self->{chanops}->{unmute_timeout}->load; - $self->{chanops}->enqueue_unmute_timeouts; - return "Mute timeouts reloaded."; + 'banlist' => sub { + $self->{timer}->dequeue_event('unban #.*'); + $self->{timer}->dequeue_event('unmute #.*'); + $self->{banlist}->{banlist}->load; + $self->{banlist}->{quietlist}->load; + $self->{chanops}->enqueue_timeouts($self->{banlist}->{banlist}, 'b'); + $self->{chanops}->enqueue_timeouts($self->{banlist}->{quietlist}, 'q'); + return "Ban list reloaded."; }, 'registry' => sub { diff --git a/Plugins/AntiAway.pm b/Plugins/AntiAway.pm index 51c755dc..82ebdd22 100644 --- a/Plugins/AntiAway.pm +++ b/Plugins/AntiAway.pm @@ -15,8 +15,9 @@ use feature 'unicode_strings'; sub initialize { my ($self, %conf) = @_; - $self->{pbot}->{registry} - ->add_default('text', 'antiaway', 'bad_nicks', $conf{bad_nicks} // '([[:punct:]](afk|brb|bbl|away|sleep|z+|work|gone|study|out|home|busy|off)[[:punct:]]*$|.+\[.*\]$)'); + $self->{pbot}->{registry}->add_default('text', 'antiaway', 'bad_nicks', + $conf{bad_nicks} // '([[:punct:]](afk|brb|bbl|away|sleep|z+|work|gone|study|out|home|busy|off)[[:punct:]]*$|.+\[.*\]$)' + ); $self->{pbot}->{registry}->add_default('text', 'antiaway', 'bad_actions', $conf{bad_actions} // '^/me (is (away|gone)|.*auto.?away)'); $self->{pbot}->{registry}->add_default('text', 'antiaway', 'kick_msg', 'http://sackheads.org/~bnaylor/spew/away_msgs.html'); diff --git a/Plugins/AntiKickAutoRejoin.pm b/Plugins/AntiKickAutoRejoin.pm index 4b2b527d..3ad167bb 100644 --- a/Plugins/AntiKickAutoRejoin.pm +++ b/Plugins/AntiKickAutoRejoin.pm @@ -35,14 +35,16 @@ sub unload { sub on_kick { my ($self, $event_type, $event) = @_; - my ($nick, $user, $host, $target, $channel, $reason) = - ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to, $event->{event}->{args}[0], $event->{event}->{args}[1]); + my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host); + my ($target, $channel, $reason) = ($event->{event}->to, $event->{event}->{args}[0], $event->{event}->{args}[1]); $channel = lc $channel; return 0 if not $self->{pbot}->{chanops}->can_gain_ops($channel); return 0 if $reason eq '*BANG!*'; # roulette - if (not exists $self->{kicks}->{$channel} or not exists $self->{kicks}->{$channel}->{$target}) { $self->{kicks}->{$channel}->{$target}->{rejoins} = 0; } + if (not exists $self->{kicks}->{$channel} or not exists $self->{kicks}->{$channel}->{$target}) { + $self->{kicks}->{$channel}->{$target}->{rejoins} = 0; + } $self->{kicks}->{$channel}->{$target}->{last_kick} = gettimeofday; return 0; @@ -65,12 +67,20 @@ sub on_join { my $duration = duration($timeout); $duration =~ s/s$//; # hours -> hour, minutes -> minute - $self->{pbot}->{chanops}->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'autorejoining after kick', "*!$user\@$host", $channel, $timeout); + $self->{pbot}->{banlist}->ban_user_timed( + $channel, + 'b', + "*!$user\@$host", + $timeout, + $self->{pbot}->{registry}->get_value('irc', 'botnick'), + 'autorejoining after kick', + ); $self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick $duration ban for auto-rejoining after kick; use this time to think about why you were kicked"); $self->{pbot}->{chanops}->gain_ops($channel); $self->{kicks}->{$channel}->{$nick}->{rejoins}++; } } + return 0; } diff --git a/Plugins/AntiNickSpam.pm b/Plugins/AntiNickSpam.pm index 2aa2e632..b7284dcc 100644 --- a/Plugins/AntiNickSpam.pm +++ b/Plugins/AntiNickSpam.pm @@ -69,7 +69,14 @@ sub check_flood { if (exists $self->{nicks}->{$channel} and @{$self->{nicks}->{$channel}} >= 10) { $self->{pbot}->{logger}->log("Nick spam flood detected in $channel\n"); - $self->{pbot}->{chanops}->mute_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'nick spam flooding', '$~a', $channel, 60 * 15); + $self->{pbot}->{banlist}->ban_user_timed( + $channel, + 'q', + '$~a', + 60 * 15, + $self->{pbot}->{registry}->get_value('irc', 'botnick'), + 'nick spam flooding', + ); } } @@ -79,9 +86,13 @@ sub clear_old_nicks { return if not exists $self->{nicks}->{$channel}; while (1) { - if (@{$self->{nicks}->{$channel}} and $self->{nicks}->{$channel}->[0]->[0] <= $now - 15) { shift @{$self->{nicks}->{$channel}}; } - else { last; } + if (@{$self->{nicks}->{$channel}} and $self->{nicks}->{$channel}->[0]->[0] <= $now - 15) { + shift @{$self->{nicks}->{$channel}}; + } else { + last; + } } + delete $self->{nicks}->{$channel} if not @{$self->{nicks}->{$channel}}; } diff --git a/Plugins/AntiRepeat.pm b/Plugins/AntiRepeat.pm index 5d5ab7eb..9dc3137b 100644 --- a/Plugins/AntiRepeat.pm +++ b/Plugins/AntiRepeat.pm @@ -59,7 +59,7 @@ sub on_public { # don't enforce anti-repeat for unreg spam my $chanmodes = $self->{pbot}->{channels}->get_meta($channel, 'MODE'); - if (defined $chanmodes and $chanmodes =~ m/z/ and exists $self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+q'}->{'$~a'}) { + if (defined $chanmodes and $chanmodes =~ m/z/ and $self->{pbot}->{banlist}->{quietlist}->exists($channel, '$~a')) { my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($account); return 0 if not defined $nickserv or not length $nickserv; } @@ -136,9 +136,9 @@ sub on_public { $self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nick Stop repeating yourself"); $self->{pbot}->{chanops}->gain_ops($channel); } - when (2) { $self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 30); } - when (3) { $self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 60 * 5); } - default { $self->{pbot}->{chanops}->ban_user_timed($botnick, 'repeating messages', "*!*\@$host", $channel, 60 * 60); } + when (2) { $self->{pbot}->{banlist}->ban_user_timed($channel, 'b', "*!*\@$host", 30, $botnick, 'repeating messages'); } + when (3) { $self->{pbot}->{banlist}->ban_user_timed($channel, 'b', "*!*\@$host", 60 * 5, $botnick, 'repeating messages'); } + default { $self->{pbot}->{banlist}->ban_user_timed($channel, 'b', "*!*\@$host", 60 * 60, $botnick, 'repeating messages'); } } return 0; } diff --git a/Plugins/AntiTwitter.pm b/Plugins/AntiTwitter.pm index 933bae7c..bba2dcb1 100644 --- a/Plugins/AntiTwitter.pm +++ b/Plugins/AntiTwitter.pm @@ -51,15 +51,25 @@ sub on_public { $self->{pbot}->{logger}->log("$nick!$user\@$host is a twit. ($self->{offenses}->{$channel}->{$nick}->{offenses} offenses) $channel: $msg\n"); given ($self->{offenses}->{$channel}->{$nick}->{offenses}) { - when (1) { $event->{conn}->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly."); } - when (2) { - $event->{conn} - ->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly. Doing this again will result in a temporary ban."); + when (1) { + $event->{conn}->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly."); } + + when (2) { + $event->{conn}->privmsg($nick, "Please do not use \@nick to address people. Drop the @ symbol; it's not necessary and it's ugly. Doing this again will result in a temporary ban."); + } + default { my $offenses = $self->{offenses}->{$channel}->{$nick}->{offenses} - 2; my $length = 60 * ($offenses * $offenses + 1); - $self->{pbot}->{chanops}->ban_user_timed($self->{pbot}->{registry}->get_value('irc', 'botnick'), 'using @nick too much', "*!*\@$host", $channel, $length); + $self->{pbot}->{banlist}->ban_user_timed( + $channel, + 'b', + "*!*\@$host", + $length, + $self->{pbot}->{registry}->get_value('irc', 'botnick'), + 'using @nick too much', + ); $self->{pbot}->{chanops}->gain_ops($channel); $self->{pbot}->{timer}->enqueue_event(sub { @@ -69,7 +79,7 @@ sub on_public { delete $self->{offenses}->{$channel} if not keys %{$self->{offenses}->{$channel}}; $event->{repeating} = 0; } - }, 60 * 60 * 24 * 2, "antitwitter offenses-- $channel $nick", 1 + }, 60 * 60 * 24 * 2, "antitwitter $channel $nick", 1 ); $length = duration $length; diff --git a/Plugins/RelayUnreg.pm b/Plugins/RelayUnreg.pm index 122e4b29..110bd0d0 100644 --- a/Plugins/RelayUnreg.pm +++ b/Plugins/RelayUnreg.pm @@ -30,7 +30,7 @@ sub on_public { return 0 if not length $msg; # exit if channel hasn't muted $~a - return 0 if not exists $self->{pbot}->{bantracker}->{banlist}->{$channel}->{'+q'}->{'$~a'}; + return 0 if not $self->{pbot}->{banlist}->{quietlist}->exists($channel, '$~a'); # exit if channel isn't +z my $chanmodes = $self->{pbot}->{channels}->get_meta($channel, 'MODE'); @@ -101,7 +101,7 @@ sub check_queue { # if nick is still present in channel, send the message if ($self->{pbot}->{nicklist}->is_present($channel, $nick)) { # ensure they're not banned (+z allows us to see +q/+b messages as normal ones) - my $banned = $self->{pbot}->{bantracker}->is_banned($nick, $user, $host, $channel); + my $banned = $self->{pbot}->{banlist}->is_banned($nick, $user, $host, $channel); $self->{pbot}->{logger} ->log("[RelayUnreg] $nick!$user\@$host $banned->{mode} as $banned->{banmask} in $banned->{channel} by $banned->{owner}, not relaying unregistered message\n") if $banned; diff --git a/Plugins/RestrictedMod.pm b/Plugins/RestrictedMod.pm index 09004ecc..25cefc9c 100644 --- a/Plugins/RestrictedMod.pm +++ b/Plugins/RestrictedMod.pm @@ -50,8 +50,11 @@ sub help { my ($self, $stuff) = @_; my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}) // 'help'; - if (exists $self->{commands}->{$command}) { return $self->{commands}->{$command}->{help}; } - else { return "No such mod command '$command'. I can't help you with that."; } + if (exists $self->{commands}->{$command}) { + return $self->{commands}->{$command}->{help}; + } else { + return "No such mod command '$command'. I can't help you with that."; + } } sub list { @@ -85,17 +88,17 @@ sub generic_command { return "Missing target. Usage: mod $command " if not defined $target; if ($command eq 'unban') { - my $reason = $self->{pbot}->{chanops}->checkban($channel, $target); + my $reason = $self->{pbot}->{banlist}->checkban($channel, 'b', $target); if ($reason =~ m/moderator ban/) { - $self->{pbot}->{chanops}->unban_user($target, $channel, 1); + $self->{pbot}->{chanops}->unban_user($channel, 'b', $target, 1); return ""; } else { return "I don't think so. That ban was not set by a moderator."; } } elsif ($command eq 'unmute') { - my $reason = $self->{pbot}->{chanops}->checkmute($channel, $target); + my $reason = $self->{pbot}->{banlist}->checkban($channel, 'q', $target); if ($reason =~ m/moderator mute/) { - $self->{pbot}->{chanops}->unmute_user($target, $channel, 1); + $self->{pbot}->{banlist}->unban_user($channel, 'q', $target, 1); return ""; } else { return "I don't think so. That mute was not set by a moderator."; @@ -120,14 +123,24 @@ sub generic_command { $self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $target Have a nice day!"); $self->{pbot}->{chanops}->gain_ops($channel); } elsif ($command eq 'ban') { - $self->{pbot}->{chanops}->ban_user_timed( + $self->{pbot}->{banlist}->ban_user_timed( + $channel, + 'b', + $target, + 3600 * 24, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}", - "doing something naughty (moderator ban)", $target, $channel, 3600 * 24, 1 + "doing something naughty (moderator ban)", + 1 ); } elsif ($command eq 'mute') { - $self->{pbot}->{chanops}->mute_user_timed( + $self->{pbot}->{banlist}->ban_user_timed( + $channel, + 'q', + $target, + 3600 * 24, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}", - "doing something naughty (moderator mute)", $target, $channel, 3600 * 24, 1 + "doing something naughty (moderator mute)", + 1 ); } return "";