3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-11-18 09:59:37 +01:00
pbot/PBot/ChanOps.pm

485 lines
14 KiB
Perl
Raw Normal View History

# File: ChanOps.pm
# Author: pragma_
#
# Purpose: Provides channel operator status tracking and commands.
License project under MPL2 This patch adds the file LICENSE which is the verbatim copy of the Mozilla Public License Version 2.0 as retreived from https://www.mozilla.org/media/MPL/2.0/index.815ca599c9df.txt on 2017-03-05. This patch also places license headers for the MPL2 type A variant of the license header in the following files: PBot/AntiFlood.pm PBot/BanTracker.pm PBot/BlackList.pm PBot/BotAdminCommands.pm PBot/BotAdmins.pm PBot/ChanOpCommands.pm PBot/ChanOps.pm PBot/Channels.pm PBot/Commands.pm PBot/DualIndexHashObject.pm PBot/EventDispatcher.pm PBot/FactoidCommands.pm PBot/FactoidModuleLauncher.pm PBot/Factoids.pm PBot/HashObject.pm PBot/IRCHandlers.pm PBot/IgnoreList.pm PBot/IgnoreListCommands.pm PBot/Interpreter.pm PBot/LagChecker.pm PBot/Logger.pm PBot/MessageHistory.pm PBot/MessageHistory_SQLite.pm PBot/NickList.pm PBot/PBot.pm PBot/Plugins.pm PBot/Plugins/AntiAway.pm PBot/Plugins/AntiKickAutoRejoin.pm PBot/Plugins/AntiRepeat.pm PBot/Plugins/AntiTwitter.pm PBot/Plugins/AutoRejoin.pm PBot/Plugins/Counter.pm PBot/Plugins/Quotegrabs.pm PBot/Plugins/Quotegrabs/Quotegrabs_Hashtable.pm PBot/Plugins/Quotegrabs/Quotegrabs_SQLite.pm PBot/Plugins/UrlTitles.pm PBot/Plugins/_Example.pm PBot/Refresher.pm PBot/Registerable.pm PBot/Registry.pm PBot/RegistryCommands.pm PBot/SQLiteLogger.pm PBot/SQLiteLoggerLayer.pm PBot/SelectHandler.pm PBot/StdinReader.pm PBot/Timer.pm PBot/Utils/ParseDate.pm PBot/VERSION.pm build/update-version.pl modules/acronym.pl modules/ago.pl modules/c11std.pl modules/c2english.pl modules/c2english/CGrammar.pm modules/c2english/c2eng.pl modules/c99std.pl modules/cdecl.pl modules/cfaq.pl modules/cjeopardy/IRCColors.pm modules/cjeopardy/QStatskeeper.pm modules/cjeopardy/Scorekeeper.pm modules/cjeopardy/cjeopardy.pl modules/cjeopardy/cjeopardy_answer.pl modules/cjeopardy/cjeopardy_filter.pl modules/cjeopardy/cjeopardy_hint.pl modules/cjeopardy/cjeopardy_qstats.pl modules/cjeopardy/cjeopardy_scores.pl modules/cjeopardy/cjeopardy_show.pl modules/codepad.pl modules/compiler_block.pl modules/compiler_client.pl modules/compiler_vm/Diff.pm modules/compiler_vm/cc modules/compiler_vm/compiler_client.pl modules/compiler_vm/compiler_server.pl modules/compiler_vm/compiler_server_vbox_win32.pl modules/compiler_vm/compiler_server_watchdog.pl modules/compiler_vm/compiler_vm_client.pl modules/compiler_vm/compiler_vm_server.pl modules/compiler_vm/compiler_watchdog.pl modules/compiler_vm/languages/_c_base.pm modules/compiler_vm/languages/_default.pm modules/compiler_vm/languages/bash.pm modules/compiler_vm/languages/bc.pm modules/compiler_vm/languages/bf.pm modules/compiler_vm/languages/c11.pm modules/compiler_vm/languages/c89.pm modules/compiler_vm/languages/c99.pm modules/compiler_vm/languages/clang.pm modules/compiler_vm/languages/clang11.pm modules/compiler_vm/languages/clang89.pm modules/compiler_vm/languages/clang99.pm modules/compiler_vm/languages/clangpp.pm modules/compiler_vm/languages/clisp.pm modules/compiler_vm/languages/cpp.pm modules/compiler_vm/languages/freebasic.pm modules/compiler_vm/languages/go.pm modules/compiler_vm/languages/haskell.pm modules/compiler_vm/languages/java.pm modules/compiler_vm/languages/javascript.pm modules/compiler_vm/languages/ksh.pm modules/compiler_vm/languages/lua.pm modules/compiler_vm/languages/perl.pm modules/compiler_vm/languages/python.pm modules/compiler_vm/languages/python3.pm modules/compiler_vm/languages/qbasic.pm modules/compiler_vm/languages/scheme.pm modules/compiler_vm/languages/server/_c_base.pm modules/compiler_vm/languages/server/_default.pm modules/compiler_vm/languages/server/c11.pm modules/compiler_vm/languages/server/c89.pm modules/compiler_vm/languages/server/c99.pm modules/compiler_vm/languages/server/clang.pm modules/compiler_vm/languages/server/clang11.pm modules/compiler_vm/languages/server/clang89.pm modules/compiler_vm/languages/server/clang99.pm modules/compiler_vm/languages/server/cpp.pm modules/compiler_vm/languages/server/freebasic.pm modules/compiler_vm/languages/server/haskell.pm modules/compiler_vm/languages/server/java.pm modules/compiler_vm/languages/server/qbasic.pm modules/compiler_vm/languages/server/tendra.pm modules/compiler_vm/languages/sh.pm modules/compiler_vm/languages/tendra.pm modules/compliment modules/cstd.pl modules/define.pl modules/dice_roll.pl modules/excuse.sh modules/expand_macros.pl modules/fnord.pl modules/funnyish_quote.pl modules/g.pl modules/gdefine.pl modules/gen_cfacts.pl modules/gencstd.pl modules/get_title.pl modules/getcfact.pl modules/google.pl modules/gspy.pl modules/gtop10.pl modules/gtop15.pl modules/headlines.pl modules/horoscope modules/horrorscope modules/ideone.pl modules/insult.pl modules/love_quote.pl modules/man.pl modules/map.pl modules/math.pl modules/prototype.pl modules/qalc.pl modules/random_quote.pl modules/seen.pl modules/urban modules/weather.pl modules/wikipedia.pl pbot.pl pbot.sh It is highly recommended that this list of files is reviewed to ensure that all files are the copyright of the sole maintainer of the repository. If any files with license headers contain the intellectual property of anyone else, it is recommended that a request is made to revise this patch or that the explicit permission of the co-author is gained to allow for the license of the work to be changed. I (Tomasz Kramkowski), the contributor, take no responsibility for any legal action taken against the maintainer of this repository for incorrectly claiming copyright to any work not owned by the maintainer of this repository.
2017-03-05 22:33:31 +01:00
# 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::ChanOps;
use warnings;
use strict;
2019-07-11 03:40:53 +02:00
use feature 'unicode_strings';
use PBot::ChanOpCommands;
use Time::HiRes qw(gettimeofday);
sub new {
if (ref($_[1]) eq 'HASH') {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference");
}
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to ChanOps");
$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;
2015-05-27 19:45:43 +02:00
$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->{ban_queue} = {};
$self->{unban_queue} = {};
$self->{op_commands} = {};
$self->{is_opped} = {};
$self->{op_requested} = {};
$self->{commands} = PBot::ChanOpCommands->new(pbot => $self->{pbot});
$self->{pbot}->{registry}->add_default('text', 'general', 'deop_timeout', $conf{'deop_timeout'} // 300);
2015-05-27 19:45:43 +02:00
$self->{pbot}->{timer}->register(sub { $self->check_opped_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unban_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unmute_timeouts }, 10);
$self->{pbot}->{timer}->register(sub { $self->check_unban_queue }, 30);
}
sub can_gain_ops {
my ($self, $channel) = @_;
2016-10-29 19:47:08 +02:00
$channel = lc $channel;
return exists $self->{pbot}->{channels}->{channels}->hash->{$channel}
&& $self->{pbot}->{channels}->{channels}->hash->{$channel}{chanop}
&& $self->{pbot}->{channels}->{channels}->hash->{$channel}{enabled};
}
sub gain_ops {
my $self = shift;
my $channel = shift;
2016-10-29 19:47:08 +02:00
$channel = lc $channel;
return if exists $self->{op_requested}->{$channel};
return if not $self->can_gain_ops($channel);
if (not exists $self->{is_opped}->{$channel}) {
$self->{pbot}->{conn}->privmsg("chanserv", "op $channel");
$self->{op_requested}->{$channel} = scalar gettimeofday;
} else {
$self->perform_op_commands($channel);
}
}
sub lose_ops {
my $self = shift;
my $channel = shift;
2016-10-29 19:47:08 +02:00
$channel = lc $channel;
$self->{pbot}->{conn}->privmsg("chanserv", "op $channel -" . $self->{pbot}->{registry}->get_value('irc', 'botnick'));
}
sub add_op_command {
my ($self, $channel, $command) = @_;
2016-10-29 19:47:08 +02:00
$channel = lc $channel;
return if not $self->can_gain_ops($channel);
push @{ $self->{op_commands}->{$channel} }, $command;
}
sub perform_op_commands {
my $self = shift;
my $channel = shift;
2016-10-29 19:47:08 +02:00
$channel = lc $channel;
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}->{logger}->log(" executing kick on $1 $2 $3\n");
}
}
$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 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 = shift;
my ($mask, $channel, $length, $immediately) = @_;
2016-10-29 19:47:08 +02:00
$channel = lc $channel;
$mask = lc $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\@" . PBot::AntiFlood::address_to_mask($host);
}
} else {
$mask .= '!*@*';
}
}
$self->ban_user($mask, $channel, $immediately);
if ($length > 0) {
$self->{unban_timeout}->hash->{$channel}->{$mask}{timeout} = gettimeofday + $length;
$self->{unban_timeout}->save;
} else {
if ($self->{pbot}->{chanops}->{unban_timeout}->find_index($channel, $mask)) {
$self->{pbot}->{chanops}->{unban_timeout}->remove($channel, $mask);
}
}
}
2015-05-27 19:45:43 +02:00
sub mute_user {
my $self = shift;
my ($mask, $channel, $immediately) = @_;
2015-05-27 19:45:43 +02:00
$self->add_to_ban_queue($channel, 'q', $mask);
if (not defined $immediately or $immediately != 0) {
$self->check_ban_queue;
}
2015-05-27 19:45:43 +02:00
}
sub unmute_user {
my ($self, $mask, $channel, $immediately) = @_;
2016-10-29 19:47:08 +02:00
$mask = lc $mask;
$channel = lc $channel;
2015-05-27 19:45:43 +02:00
$self->{pbot}->{logger}->log("Unmuting $channel $mask\n");
$self->unmode_user($mask, $channel, $immediately, 'q');
2015-05-27 19:45:43 +02:00
}
sub mute_user_timed {
my $self = shift;
my ($mask, $channel, $length, $immediately) = @_;
2015-05-27 19:45:43 +02:00
2016-10-29 19:47:08 +02:00
$channel = lc $channel;
$mask = lc $mask;
2015-05-27 19:45:43 +02:00
$mask .= '!*@*' if $mask !~ m/[\$!@]/;
$self->mute_user($mask, $channel, $immediately);
2015-05-27 19:45:43 +02:00
if ($length > 0) {
$self->{unmute_timeout}->hash->{$channel}->{$mask}{timeout} = gettimeofday + $length;
$self->{unmute_timeout}->save;
} else {
if ($self->{pbot}->{chanops}->{unmute_timeout}->find_index($channel, $mask)) {
$self->{pbot}->{chanops}->{unmute_timeout}->remove($channel, $mask);
}
2015-05-27 19:45:43 +02:00
}
}
sub join_channel {
my ($self, $channels) = @_;
$self->{pbot}->{conn}->join($channels);
foreach my $channel (split /,/, $channels) {
2016-10-29 19:47:08 +02:00
$channel = lc $channel;
$self->{pbot}->{event_dispatcher}->dispatch_event('pbot.join', { channel => $channel });
delete $self->{is_opped}->{$channel};
delete $self->{op_requested}->{$channel};
if (exists $self->{pbot}->{channels}->{channels}->hash->{$channel}
and exists $self->{pbot}->{channels}->{channels}->hash->{$channel}{permop}
and $self->{pbot}->{channels}->{channels}->hash->{$channel}{permop}) {
$self->gain_ops($channel);
}
$self->{pbot}->{conn}->mode($channel);
}
}
sub part_channel {
my ($self, $channel) = @_;
2016-10-29 19:47:08 +02:00
$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) = @_;
2016-10-29 19:47:08 +02:00
return exists $self->{unban_timeout}->hash->{lc $channel}->{lc $mask};
}
sub has_mute_timeout {
my ($self, $channel, $mask) = @_;
2016-10-29 19:47:08 +02:00
return exists $self->{unmute_timeout}->hash->{lc $channel}->{lc $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) = @_;
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;
2017-08-03 22:30:42 +02:00
my $MAX_COMMANDS = 4;
my $commands = 0;
foreach my $channel (keys %{$self->{unban_queue}}) {
2017-08-03 22:30:42 +02:00
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};
}
2017-08-09 10:22:13 +02:00
if (not keys %{ $self->{unban_queue}->{$channel} }) {
2017-08-03 22:30:42 +02:00
delete $self->{unban_queue}->{$channel};
$done = 1;
}
2017-08-03 22:30:42 +02:00
if ($count) {
$self->add_op_command($channel, "mode $channel $modes $list");
$self->gain_ops($channel);
2017-08-03 22:30:42 +02:00
return if ++$commands >= $MAX_COMMANDS;
}
}
}
}
sub check_unban_timeouts {
my $self = shift;
return if not $self->{pbot}->{joined_channels};
my $now = gettimeofday();
foreach my $channel (keys %{ $self->{unban_timeout}->hash }) {
foreach my $mask (keys %{ $self->{unban_timeout}->hash->{$channel} }) {
if ($self->{unban_timeout}->hash->{$channel}->{$mask}{timeout} < $now) {
$self->{unban_timeout}->hash->{$channel}->{$mask}{timeout} = $now + 7200;
$self->unban_user($mask, $channel);
}
}
}
}
2015-05-27 19:45:43 +02:00
sub check_unmute_timeouts {
my $self = shift;
return if not $self->{pbot}->{joined_channels};
my $now = gettimeofday();
foreach my $channel (keys %{ $self->{unmute_timeout}->hash }) {
foreach my $mask (keys %{ $self->{unmute_timeout}->hash->{$channel} }) {
if ($self->{unmute_timeout}->hash->{$channel}->{$mask}{timeout} < $now) {
$self->{unmute_timeout}->hash->{$channel}->{$mask}{timeout} = $now + 7200;
2015-05-27 19:45:43 +02:00
$self->unmute_user($mask, $channel);
}
}
}
}
sub check_opped_timeouts {
my $self = shift;
my $now = gettimeofday();
foreach my $channel (keys %{ $self->{is_opped} }) {
if ($self->{is_opped}->{$channel}{timeout} < $now) {
unless (exists $self->{pbot}->{channels}->{channels}->hash->{$channel}
and exists $self->{pbot}->{channels}->{channels}->hash->{$channel}{permop}
and $self->{pbot}->{channels}->{channels}->hash->{$channel}{permop}) {
$self->lose_ops($channel);
delete $self->{is_opped}->{$channel}; # assume chanserv is alive and deop will succeed
}
} else {
# my $timediff = $self->{is_opped}->{$channel}{timeout} - $now;
# $self->{pbot}->{logger}->log("deop $channel in $timediff seconds\n");
}
}
foreach my $channel (keys %{ $self->{op_requested} }) {
if ($now - $self->{op_requested}->{$channel} > 60 * 5) {
if (exists $self->{pbot}->{channels}->{channels}->hash->{$channel}
and $self->{pbot}->{channels}->{channels}->hash->{$channel}{enabled}) {
$self->{pbot}->{logger}->log("5 minutes since OP request for $channel and no OP yet; trying again ...\n");
delete $self->{op_requested}->{$channel};
$self->gain_ops($channel);
} else {
$self->{pbot}->{logger}->log("Disregarding OP request for $channel (channel is disabled)\n");
delete $self->{op_requested}->{$channel};
}
}
}
}
1;