3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-11-26 13:59:47 +01:00

Massive refactor: added support for generic users!

Renamed data/admins to data/users
Moved Admins.pm to Users.pm
Moved various subroutines in AdminCommands.pm to more appropriate locations
Deleted AdminCommands.pm
Improvements to Users.pm
Added `my` command
This commit is contained in:
Pragmatic Software 2020-01-25 12:28:05 -08:00
parent cc8f2b827a
commit 5dd03f1c0c
16 changed files with 759 additions and 737 deletions

View File

@ -1,394 +0,0 @@
# File: AdminCommands.pm
# Author: pragma_
#
# Purpose: Administrative command subroutines.
# 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::AdminCommands;
use warnings;
use strict;
use feature 'unicode_strings';
use feature 'switch';
no if $] >= 5.018, warnings => "experimental::smartmatch";
use Carp ();
sub new {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref($_[1]) eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{pbot}->{commands}->register(sub { return $self->login(@_) }, "login", 0);
$self->{pbot}->{commands}->register(sub { return $self->logout(@_) }, "logout", 0);
$self->{pbot}->{commands}->register(sub { return $self->in_channel(@_) }, "in", 0);
$self->{pbot}->{commands}->register(sub { return $self->join_channel(@_) }, "join", 40);
$self->{pbot}->{commands}->register(sub { return $self->part_channel(@_) }, "part", 40);
$self->{pbot}->{commands}->register(sub { return $self->ack_die(@_) }, "die", 90);
$self->{pbot}->{commands}->register(sub { return $self->adminadd(@_) }, "adminadd", 60);
$self->{pbot}->{commands}->register(sub { return $self->adminrem(@_) }, "adminrem", 60);
$self->{pbot}->{commands}->register(sub { return $self->adminset(@_) }, "adminset", 60);
$self->{pbot}->{commands}->register(sub { return $self->adminunset(@_) }, "adminunset", 60);
$self->{pbot}->{commands}->register(sub { return $self->sl(@_) }, "sl", 90);
$self->{pbot}->{commands}->register(sub { return $self->export(@_) }, "export", 90);
$self->{pbot}->{commands}->register(sub { return $self->reload(@_) }, "reload", 90);
$self->{pbot}->{commands}->register(sub { return $self->evalcmd(@_) }, "eval", 99);
}
sub sl {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
if (not length $arguments) {
return "Usage: sl <ircd command>";
}
$self->{pbot}->{conn}->sl($arguments);
return "";
}
sub in_channel {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: in <channel> <command>";
if (not $arguments) {
return $usage;
}
my ($channel, $command) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return $usage if not defined $channel or not defined $command;
$stuff->{admin_channel_override} = $channel;
$stuff->{command} = $command;
return $self->{pbot}->{interpreter}->interpret($stuff);
}
sub login {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $channel = $from;
if (not $arguments) {
return "Usage: login [channel] password";
}
if ($arguments =~ m/^([^ ]+)\s+(.+)/) {
$channel = $1;
$arguments = $2;
}
if ($self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host")) {
return "/msg $nick You are already logged into channel $channel.";
}
my $result = $self->{pbot}->{admins}->login($channel, "$nick!$user\@$host", $arguments);
return "/msg $nick $result";
}
sub logout {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
return "/msg $nick Uh, you aren't logged into channel $from." if (not $self->{pbot}->{admins}->loggedin($from, "$nick!$user\@$host"));
$self->{pbot}->{admins}->logout($from, "$nick!$user\@$host");
return "/msg $nick Good-bye, $nick.";
}
sub adminadd {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($name, $channel, $hostmask, $level, $password) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 5);
if (not defined $name or not defined $channel or not defined $hostmask or not defined $level
or not defined $password) {
return "/msg $nick Usage: adminadd <name> <channel> <hostmask> <level> <password>";
}
$channel = '.*' if lc $channel eq 'global';
my $admin = $self->{pbot}->{admins}->find_admin($from, "$nick!$user\@$host");
if (not $admin) {
return "You are not an admin in $from.\n";
}
if ($admin->{level} < 90 and $level > 60) {
return "You may not set admin level higher than 60.\n";
}
$self->{pbot}->{admins}->add_admin($name, $channel, $hostmask, $level, $password);
return "Admin added.";
}
sub adminrem {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $hostmask) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
if (not defined $channel or not defined $hostmask) {
return "/msg $nick Usage: adminrem <channel> <hostmask/name>";
}
$channel = lc $channel;
$hostmask = lc $hostmask;
$channel = '.*' if $channel eq 'global';
if (exists $self->{pbot}->{admins}->{admins}->{hash}->{$channel}) {
if (not exists $self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$hostmask}) {
foreach my $mask (keys %{ $self->{pbot}->{admins}->{admins}->{hash}->{$channel} }) {
next if $mask eq '_name';
if ($self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$mask}->{name} eq $hostmask) {
$hostmask = $mask;
last;
}
}
}
}
return $self->{pbot}->{admins}->remove_admin($channel, $hostmask);
}
sub adminset {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $hostmask, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 4);
if (not defined $channel or not defined $hostmask) {
return "Usage: adminset <channel> <hostmask/name> [key] [value]";
}
$channel = lc $channel;
$hostmask = lc $hostmask;
$channel = '.*' if $channel eq 'global';
if (exists $self->{pbot}->{admins}->{admins}->{hash}->{$channel}) {
if (not exists $self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$hostmask}) {
foreach my $mask (keys %{ $self->{pbot}->{admins}->{admins}->{hash}->{$channel} }) {
next if $mask eq '_name';
if ($self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$mask}->{name} eq $hostmask) {
$hostmask = $mask;
last;
}
}
}
}
my $admin = $self->{pbot}->{admins}->find_admin($from, "$nick!$user\@$host");
my $target = $self->{pbot}->{admins}->find_admin($channel, $hostmask);
if (not $admin) {
return "You are not an admin in $from.";
}
if (not $target) {
return "There is no admin $hostmask in channel $channel.";
}
if ($key eq 'level' && $admin->{level} < 90 and $value > 60) {
return "You may not set admin level higher than 60.\n";
}
if ($target->{level} > $admin->{level}) {
return "You may not modify admins higher in level than you.";
}
my $result = $self->{pbot}->{admins}->{admins}->set($channel, $hostmask, $key, $value);
$result =~ s/^password => .*;$/password => <private>;/m;
return $result;
}
sub adminunset {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $hostmask, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
if (not defined $channel or not defined $hostmask) {
return "Usage: adminunset <channel> <hostmask/name> <key>";
}
$channel = lc $channel;
$hostmask = lc $hostmask;
$channel = '.*' if $channel eq 'global';
if (exists $self->{pbot}->{admins}->{admins}->{hash}->{$channel}) {
if (not exists $self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$hostmask}) {
foreach my $mask (keys %{ $self->{pbot}->{admins}->{admins}->{hash}->{$channel} }) {
next if $mask eq '_name';
if ($self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$mask}->{name} eq $hostmask) {
$hostmask = $mask;
last;
}
}
}
}
return $self->{pbot}->{admins}->{admins}->unset($channel, $hostmask, $key);
}
sub join_channel {
my $self = shift;
my ($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);
}
return "/msg $nick Joining $arguments";
}
sub part_channel {
my $self = shift;
my ($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);
}
return "/msg $nick Parting $arguments";
}
sub ack_die {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
$self->{pbot}->{logger}->log("$nick!$user\@$host made me exit.\n");
$self->{pbot}->atexit();
$self->{pbot}->{conn}->privmsg($from, "Good-bye.") if defined $from;
$self->{pbot}->{conn}->quit("Departure requested.");
exit 0;
}
sub export {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
if (not defined $arguments) {
return "/msg $nick Usage: export <factoids>";
}
if ($arguments =~ /^factoids$/i) {
return $self->{pbot}->{factoids}->export_factoids;
}
}
sub evalcmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
$self->{pbot}->{logger}->log("[$from] $nick!$user\@$host Evaluating [$arguments]\n");
my $ret;
my $result = eval $arguments;
if ($@) {
if (length $result) {
$ret .= "[Error: $@] ";
} else {
$ret .= "Error: $@";
}
$ret =~ s/ at \(eval \d+\) line 1.//;
}
return "/say $ret $result";
}
sub reload {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my %reloadables = (
'commands' => sub {
$self->{pbot}->{commands}->load_metadata;
return "Commands metadata reloaded.";
},
'blacklist' => sub {
$self->{pbot}->{blacklist}->clear_blacklist;
$self->{pbot}->{blacklist}->load_blacklist;
return "Blacklist reloaded.";
},
'whitelist' => sub {
$self->{pbot}->{antiflood}->{whitelist}->clear;
$self->{pbot}->{antiflood}->{whitelist}->load;
return "Whitelist reloaded.";
},
'ignores' => sub {
$self->{pbot}->{ignorelist}->clear_ignores;
$self->{pbot}->{ignorelist}->load_ignores;
return "Ignore list reloaded.";
},
'admins' => sub {
$self->{pbot}->{admins}->{admins}->clear;
$self->{pbot}->{admins}->load_admins;
return "Admins reloaded.";
},
'channels' => sub {
$self->{pbot}->{channels}->{channels}->clear;
$self->{pbot}->{channels}->load_channels;
return "Channels reloaded.";
},
'bantimeouts' => sub {
$self->{pbot}->{chanops}->{unban_timeout}->clear;
$self->{pbot}->{chanops}->{unban_timeout}->load;
return "Ban timeouts reloaded.";
},
'mutetimeouts' => sub {
$self->{pbot}->{chanops}->{unmute_timeout}->clear;
$self->{pbot}->{chanops}->{unmute_timeout}->load;
return "Mute timeouts reloaded.";
},
'registry' => sub {
$self->{pbot}->{registry}->{registry}->clear;
$self->{pbot}->{registry}->load;
return "Registry reloaded.";
},
'factoids' => sub {
$self->{pbot}->{factoids}->{factoids}->clear;
$self->{pbot}->{factoids}->load_factoids;
return "Factoids reloaded.";
},
'funcs' => sub {
$self->{pbot}->{func_cmd}->init_funcs;
return "Funcs reloaded.";
}
);
if (not length $arguments or not exists $reloadables{$arguments}) {
my $usage = 'Usage: reload <';
$usage .= join '|', sort keys %reloadables;
$usage .= '>';
return $usage;
}
return $reloadables{$arguments}();
}
1;

View File

@ -1,218 +0,0 @@
# File: Admins.pm
# Author: pragma_
#
# Purpose: Manages list of bot admins and whether they are logged in.
# 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::Admins;
use warnings;
use strict;
use feature 'unicode_strings';
use PBot::DualIndexHashObject;
use PBot::AdminCommands;
use Carp ();
sub new {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref($_[1]) eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{admins} = PBot::DualIndexHashObject->new(name => 'Admins', filename => $conf{filename}, pbot => $conf{pbot});
$self->load_admins;
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
$self->{commands} = PBot::AdminCommands->new(pbot => $conf{pbot});
}
sub on_join {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
my $admin = $self->find_admin($channel, "$nick!$user\@$host");
if (defined $admin) {
if ($self->{pbot}->{chanops}->can_gain_ops($channel)) {
my $modes = '+';
my $targets = '';
if ($admin->{autoop}) {
$self->{pbot}->{logger}->log("[admin] $nick!$user\@$host autoop in $channel\n");
$modes .= 'o';
$targets .= "$nick ";
}
if ($admin->{autovoice}) {
$self->{pbot}->{logger}->log("[admin] $nick!$user\@$host autovoice in $channel\n");
$modes .= 'v';
$targets .= "$nick ";
}
if (length $modes > 1) {
$self->{pbot}->{chanops}->add_op_command($channel, "mode $channel $modes $targets");
$self->{pbot}->{chanops}->gain_ops($channel);
}
}
if ($admin->{autologin}) {
$self->{pbot}->{logger}->log("[admin] $nick!$user\@$host autologin to $admin->{name} ($admin->{level}) for $channel\n");
$admin->{loggedin} = 1;
}
}
return 0;
}
sub add_admin {
my $self = shift;
my ($name, $channel, $hostmask, $level, $password, $dont_save) = @_;
$channel = '.*' if $channel !~ m/^#/;
my $data = {
name => $name,
level => $level,
password => $password
};
$self->{pbot}->{logger}->log("Adding new level $level admin: [$name] [$hostmask] for channel [$channel]\n");
$self->{admins}->add($channel, $hostmask, $data, $dont_save);
}
sub remove_admin {
my $self = shift;
my ($channel, $hostmask) = @_;
return $self->{admins}->remove($channel, $hostmask);
}
sub load_admins {
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{admins}->{filename}; }
if (not defined $filename) {
Carp::carp "No admins path specified -- skipping loading of admins";
return;
}
$self->{admins}->load;
my $i = 0;
foreach my $channel (sort keys %{ $self->{admins}->{hash} } ) {
foreach my $hostmask (sort keys %{ $self->{admins}->{hash}->{$channel} }) {
next if $hostmask eq '_name';
$i++;
my $name = $self->{admins}->{hash}->{$channel}->{$hostmask}->{name};
my $level = $self->{admins}->{hash}->{$channel}->{$hostmask}->{level};
my $password = $self->{admins}->{hash}->{$channel}->{$hostmask}->{password};
if (not defined $name or not defined $level or not defined $password) {
Carp::croak "An admin in $filename is missing critical data\n";
}
my $chan = $channel eq '.*' ? 'global' : $channel;
$self->{pbot}->{logger}->log("Adding new level $level $chan admin: $name $hostmask\n");
}
}
$self->{pbot}->{logger}->log(" $i admins loaded.\n");
}
sub save_admins {
my $self = shift;
$self->{admins}->save;
}
sub find_admin {
my ($self, $from, $hostmask) = @_;
$from = $self->{pbot}->{registry}->get_value('irc', 'botnick') if not defined $from;
$hostmask = '.*' if not defined $hostmask;
$hostmask = lc $hostmask;
my $result = eval {
my $admin;
foreach my $channel_regex (keys %{ $self->{admins}->{hash} }) {
if ($from !~ m/^#/ or $from =~ m/^$channel_regex$/i) {
foreach my $hostmask_regex (keys %{ $self->{admins}->{hash}->{$channel_regex} }) {
next if $hostmask_regex eq '_name';
if ($hostmask_regex =~ m/[*?]/) {
# contains * or ? so it's converted to a regex
my $hostmask_quoted = quotemeta $hostmask_regex;
$hostmask_quoted =~ s/\\\*/.*?/g;
$hostmask_quoted =~ s/\\\?/./g;
if ($hostmask =~ m/^$hostmask_quoted$/i) {
my $temp = $self->{admins}->{hash}->{$channel_regex}->{$hostmask_regex};
$admin = $temp if not defined $admin or $admin->{level} < $temp->{level};
}
} else {
# direct comparison
if ($hostmask eq lc $hostmask_regex) {
my $temp = $self->{admins}->{hash}->{$channel_regex}->{$hostmask_regex};
$admin = $temp if not defined $admin or $admin->{level} < $temp->{level};
}
}
}
}
}
return $admin;
};
if ($@) {
$self->{pbot}->{logger}->log("Error in find_admin parameters: $@\n");
}
return $result;
}
sub loggedin {
my ($self, $channel, $hostmask) = @_;
my $admin = $self->find_admin($channel, $hostmask);
if (defined $admin && $admin->{loggedin}) {
return $admin;
} else {
return undef;
}
}
sub login {
my ($self, $channel, $hostmask, $password) = @_;
my $admin = $self->find_admin($channel, $hostmask);
if (not defined $admin) {
$self->{pbot}->{logger}->log("Attempt to login non-existent [$channel][$hostmask] failed\n");
return "You do not have an account in $channel.";
}
if ($admin->{password} ne $password) {
$self->{pbot}->{logger}->log("Bad login password for [$channel][$hostmask]\n");
return "I don't think so.";
}
$admin->{loggedin} = 1;
$self->{pbot}->{logger}->log("$hostmask logged into $channel\n");
return "Logged into $channel.";
}
sub logout {
my ($self, $channel, $hostmask) = @_;
my $admin = $self->find_admin($channel, $hostmask);
delete $admin->{loggedin} if defined $admin;
}
1;

View File

@ -446,7 +446,7 @@ sub check_flood {
}
# do not do flood enforcement for logged in bot admins
if ($self->{pbot}->{registry}->get_value('antiflood', 'dont_enforce_admins') and $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host")) {
if ($self->{pbot}->{registry}->get_value('antiflood', 'dont_enforce_admins') and $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
$self->{channels}->{$channel}->{last_spoken_nick} = $nick;
next;
}

View File

@ -251,7 +251,7 @@ sub mode {
# removing mode -- check against whitelist, etc
next if $nick_data->{nick} eq $self->{pbot}->{registry}->get_value('irc', 'botnick');
next if $self->{pbot}->{antiflood}->whitelisted($channel, $nick_data->{hostmask});
next if $self->{pbot}->{admins}->loggedin($channel, $nick_data->{hostmask});
next if $self->{pbot}->{users}->loggedin_admin($channel, $nick_data->{hostmask});
}
# skip nick if already has mode set/unset
@ -418,7 +418,7 @@ sub ban_user {
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
return "I don't think so." if $target =~ /^\Q$botnick\E!/i;
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host")) {
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "/msg $nick You are not an admin for $channel.";
}
@ -485,7 +485,7 @@ sub unban_user {
return "/msg $nick Usage for /msg: unban <nick/mask> <channel> [false value to use unban queue]" if $channel !~ /^#/;
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host")) {
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "/msg $nick You are not an admin for $channel.";
}
@ -549,7 +549,7 @@ sub mute_user {
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
return "I don't think so." if $target =~ /^\Q$botnick\E!/i;
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host")) {
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "/msg $nick You are not an admin for $channel.";
}
@ -616,7 +616,7 @@ sub unmute_user {
return "/msg $nick Usage for /msg: unmute <nick/mask> <channel> [false value to use unban queue]" if $channel !~ /^#/;
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host")) {
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "/msg $nick You are not an admin for $channel.";
}
@ -670,7 +670,7 @@ sub kick_user {
$channel = $1;
}
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host")) {
if ($self->{pbot}->{commands}->get_meta($stuff->{keyword}, 'level') and not $stuff->{'effective-level'} and not $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "/msg $nick You are not an admin for $channel.";
}
@ -704,7 +704,7 @@ sub kick_user {
next if $nick_data->{nick} eq $self->{pbot}->{registry}->get_value('irc', 'botnick');
next if $self->{pbot}->{antiflood}->whitelisted($channel, $nick_data->{hostmask});
next if $self->{pbot}->{admins}->loggedin($channel, $nick_data->{hostmask});
next if $self->{pbot}->{users}->loggedin_admin($channel, $nick_data->{hostmask});
$self->{pbot}->{chanops}->add_op_command($channel, "kick $channel $nl $reason");
}

View File

@ -33,6 +33,8 @@ sub initialize {
$self->{channels} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Channels', filename => $conf{filename});
$self->load_channels;
$self->{pbot}->{commands}->register(sub { $self->join(@_) }, "join", 40);
$self->{pbot}->{commands}->register(sub { $self->part(@_) }, "part", 40);
$self->{pbot}->{commands}->register(sub { $self->set(@_) }, "chanset", 40);
$self->{pbot}->{commands}->register(sub { $self->unset(@_) }, "chanunset", 40);
$self->{pbot}->{commands}->register(sub { $self->add(@_) }, "chanadd", 40);
@ -40,6 +42,30 @@ sub initialize {
$self->{pbot}->{commands}->register(sub { $self->list(@_) }, "chanlist", 10);
}
sub join {
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);
}
return "/msg $nick Joining $arguments";
}
sub part {
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);
}
return "/msg $nick Parting $arguments";
}
sub set {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);

View File

@ -39,10 +39,11 @@ sub initialize {
$self->{metadata} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Commands', filename => $conf{filename});
$self->load_metadata;
$self->register(sub { $self->cmdset(@_) }, "cmdset", 90);
$self->register(sub { $self->cmdunset(@_) }, "cmdunset", 90);
$self->register(sub { $self->help(@_) }, "help", 0);
$self->register(sub { $self->uptime(@_) }, "uptime", 0);
$self->register(sub { $self->cmdset(@_) }, "cmdset", 90);
$self->register(sub { $self->cmdunset(@_) }, "cmdunset", 90);
$self->register(sub { $self->help(@_) }, "help", 0);
$self->register(sub { $self->uptime(@_) }, "uptime", 0);
$self->register(sub { $self->in_channel(@_) }, "in", 0);
}
sub register {
@ -97,7 +98,7 @@ sub interpreter {
my $from = exists $stuff->{admin_channel_override} ? $stuff->{admin_channel_override} : $stuff->{from};
my ($admin_channel) = $stuff->{arguments} =~ m/\B(#[^ ]+)/; # assume first channel-like argument
$admin_channel = $from if not defined $admin_channel;
my $admin = $self->{pbot}->{admins}->loggedin($admin_channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
my $admin = $self->{pbot}->{users}->loggedin_admin($admin_channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
my $admin_level = defined $admin ? $admin->{level} : 0;
my $keyword = lc $stuff->{keyword};
@ -244,4 +245,18 @@ sub uptime {
return localtime ($self->{pbot}->{startup_timestamp}) . " [" . duration (time - $self->{pbot}->{startup_timestamp}) . "]";
}
sub in_channel {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $usage = "Usage: in <channel> <command>";
return $usage if not $arguments;
my ($channel, $command) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
return $usage if not defined $channel or not defined $command;
$stuff->{admin_channel_override} = $channel;
$stuff->{command} = $command;
return $self->{pbot}->{interpreter}->interpret($stuff);
}
1;

View File

@ -78,7 +78,6 @@ sub initialize {
$self->{pbot}->{commands}->register(sub { return $self->factmove(@_) }, "factmove", 0);
$self->{pbot}->{commands}->register(sub { return $self->call_factoid(@_) }, "fact", 0);
$self->{pbot}->{commands}->register(sub { return $self->factfind(@_) }, "factfind", 0);
$self->{pbot}->{commands}->register(sub { return $self->list(@_) }, "list", 0);
$self->{pbot}->{commands}->register(sub { return $self->top20(@_) }, "top20", 0);
$self->{pbot}->{commands}->register(sub { return $self->load_module(@_) }, "load", 90);
$self->{pbot}->{commands}->register(sub { return $self->unload_module(@_) }, "unload", 90);
@ -408,7 +407,7 @@ sub factundo {
}
my $factoids = $self->{pbot}->{factoids}->{factoids}->{hash};
my $admininfo = $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host");
my $admininfo = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host");
if ($factoids->{$channel}->{$trigger}->{'locked'}) {
return "/say $trigger_name is locked and cannot be reverted." if not defined $admininfo;
@ -501,7 +500,7 @@ sub factredo {
}
my $factoids = $self->{pbot}->{factoids}->{factoids}->{hash};
my $admininfo = $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host");
my $admininfo = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host");
if ($factoids->{$channel}->{$trigger}->{'locked'}) {
return "/say $trigger_name is locked and cannot be reverted." if not defined $admininfo;
@ -563,9 +562,9 @@ sub factset {
my $admininfo;
if (defined $owner_channel) {
$admininfo = $self->{pbot}->{admins}->loggedin($owner_channel, "$nick!$user\@$host");
$admininfo = $self->{pbot}->{users}->loggedin_admin($owner_channel, "$nick!$user\@$host");
} else {
$admininfo = $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host");
$admininfo = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host");
}
my $level = 0;
@ -656,9 +655,9 @@ sub factunset {
my $admininfo;
if (defined $owner_channel) {
$admininfo = $self->{pbot}->{admins}->loggedin($owner_channel, "$nick!$user\@$host");
$admininfo = $self->{pbot}->{users}->loggedin_admin($owner_channel, "$nick!$user\@$host");
} else {
$admininfo = $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host");
$admininfo = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host");
}
my $level = 0;
@ -723,63 +722,6 @@ sub factunset {
return $result;
}
sub list {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $text;
my $usage = "Usage: list <modules|commands|admins>";
if (not defined $arguments) {
return $usage;
}
if ($arguments =~ /^modules$/i) {
$text = "Loaded modules: ";
foreach my $channel (sort keys %{ $self->{pbot}->{factoids}->{factoids}->{hash} }) {
foreach my $command (sort keys %{ $self->{pbot}->{factoids}->{factoids}->{hash}->{$channel} }) {
next if $command eq '_name';
if ($self->{pbot}->{factoids}->{factoids}->{hash}->{$channel}->{$command}->{type} eq 'module') {
$text .= "$self->{pbot}->{factoids}->{factoids}->{hash}->{$channel}->{$command}->{_name} ";
}
}
}
return $text;
}
if ($arguments =~ /^commands$/i) {
$text = "Registered commands: ";
foreach my $command (sort { $a->{name} cmp $b->{name} } @{ $self->{pbot}->{commands}->{handlers} }) {
$text .= "$command->{name} ";
$text .= "($command->{level}) " if $command->{level} > 0;
}
return $text;
}
if ($arguments =~ /^admins$/i) {
$text = "Admins: ";
my $last_channel = "";
my $sep = "";
foreach my $channel (sort keys %{ $self->{pbot}->{admins}->{admins}->{hash} }) {
next if $from =~ m/^#/ and $channel ne $from and $channel ne '.*';
if ($last_channel ne $channel) {
$text .= $sep . ($channel eq ".*" ? "global" : $channel) . ": ";
$last_channel = $channel;
$sep = "";
}
foreach my $hostmask (sort { return 0 if $a eq '_name' or $b eq '_name'; $self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$a}->{name} cmp $self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$b}->{name} } keys %{ $self->{pbot}->{admins}->{admins}->{hash}->{$channel} }) {
next if $hostmask eq '_name';
$text .= $sep;
$text .= "*" if $self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$hostmask}->{loggedin};
$text .= $self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$hostmask}->{name} . " (" . $self->{pbot}->{admins}->{admins}->{hash}->{$channel}->{$hostmask}->{level} . ")";
$sep = "; ";
}
}
return $text;
}
return $usage;
}
sub factmove {
my $self = shift;
my ($from, $nick, $user, $host, $arguments, $stuff) = @_;
@ -827,7 +769,7 @@ sub factmove {
my ($owner) = $factoids->{$found_src_channel}->{$found_source}->{'owner'} =~ m/([^!]+)/;
if ((lc $nick ne lc $owner) and (not $self->{pbot}->{admins}->loggedin($found_src_channel, "$nick!$user\@$host"))) {
if ((lc $nick ne lc $owner) and (not $self->{pbot}->{users}->loggedin_admin($found_src_channel, "$nick!$user\@$host"))) {
$self->{pbot}->{logger}->log("$nick!$user\@$host attempted to move [$found_src_channel] $found_source (not owner)\n");
my $chan = ($found_src_channel eq '.*' ? 'the global channel' : $found_src_channel);
return "You are not the owner of $source_trigger_name for $source_channel_name.";
@ -1080,7 +1022,7 @@ sub factadd {
my ($owner) = $factoids->{$channel}->{$trigger}->{'owner'} =~ m/([^!]+)/;
if ((lc $nick ne lc $owner) and (not $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host"))) {
if ((lc $nick ne lc $owner) and (not $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host"))) {
return "You are not the owner of $trigger_name for $channel_name; cannot force overwrite.";
}
}
@ -1136,7 +1078,7 @@ sub factrem {
my ($owner) = $factoids->{$channel}->{$trigger}->{'owner'} =~ m/([^!]+)/;
if ((lc $nick ne lc $owner) and (not $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host"))) {
if ((lc $nick ne lc $owner) and (not $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host"))) {
return "You are not the owner of $trigger_name for $channel_name.";
}
@ -1682,7 +1624,7 @@ sub factchange {
return "/say $trigger_name belongs to $channel_name, but this is $from_chan. Please switch to $channel_name or use /msg to change this factoid.";
}
my $admininfo = $self->{pbot}->{admins}->loggedin($channel, "$nick!$user\@$host");
my $admininfo = $self->{pbot}->{users}->loggedin_admin($channel, "$nick!$user\@$host");
if ($factoids_hash->{$channel}->{$trigger}->{'locked'}) {
return "/say $trigger_name is locked and cannot be changed." if not defined $admininfo;

View File

@ -876,7 +876,7 @@ sub interpreter {
$ratelimit = $self->{factoids}->{hash}->{$channel}->{$keyword}->{rate_limit} if not defined $ratelimit;
if (gettimeofday - $self->{factoids}->{hash}->{$channel}->{$keyword}->{last_referenced_on} < $ratelimit) {
my $ref_from = $stuff->{ref_from} ? "[$stuff->{ref_from}] " : "";
return "/msg $stuff->{nick} $ref_from'$trigger_name' is rate-limited; try again in " . duration ($ratelimit - int(gettimeofday - $self->{factoids}->{hash}->{$channel}->{$keyword}->{last_referenced_on})) . "." unless $self->{pbot}->{admins}->loggedin($channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
return "/msg $stuff->{nick} $ref_from'$trigger_name' is rate-limited; try again in " . duration ($ratelimit - int(gettimeofday - $self->{factoids}->{hash}->{$channel}->{$keyword}->{last_referenced_on})) . "." unless $self->{pbot}->{users}->loggedin_admin($channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
}
}
}
@ -1067,7 +1067,7 @@ sub handle_action {
elsif ($self->{factoids}->{hash}->{$channel}->{$keyword}->{type} eq 'text') {
# Don't allow user-custom /msg factoids, unless factoid triggered by admin
if ($action =~ m/^\/msg/i) {
my $admin = $self->{pbot}->{admins}->loggedin($stuff->{from}, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
my $admin = $self->{pbot}->{users}->loggedin_admin($stuff->{from}, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
if (not $admin or $admin->{level} < 60) {
$self->{pbot}->{logger}->log("[ABUSE] Bad factoid (contains /msg): $action\n");
return "You are not powerful enough to use /msg in a factoid.";

View File

@ -448,9 +448,9 @@ sub on_departure {
$self->{pbot}->{registry}->get_value('antiflood', 'join_flood_time_threshold'),
$self->{pbot}->{messagehistory}->{MSG_DEPARTURE});
my $admin = $self->{pbot}->{admins}->find_admin($channel, "$nick!$user\@$host");
# auto-logout admins but not users
my $admin = $self->{pbot}->{users}->find_admin($channel, "$nick!$user\@$host");
if (defined $admin and $admin->{loggedin} and not $admin->{stayloggedin}) {
$self->{pbot}->{logger}->log("Whoops, $nick left while still logged in.\n");
$self->{pbot}->{logger}->log("Logged out $nick.\n");
delete $admin->{loggedin};
}

View File

@ -18,12 +18,8 @@ use PBot::IgnoreListCommands;
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");
}
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref($_[1]) eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
@ -32,8 +28,8 @@ sub new {
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{filename} = delete $conf{filename};
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{filename} = $conf{filename};
$self->{ignore_list} = {};
$self->{ignore_flood_counter} = {};
@ -41,7 +37,7 @@ sub initialize {
$self->{commands} = PBot::IgnoreListCommands->new(pbot => $self->{pbot});
$self->load_ignores;
$self->load_ignores();
$self->{pbot}->{timer}->register(sub { $self->check_ignore_timeouts }, 10);
}

View File

@ -173,7 +173,7 @@ sub process_line {
foreach $command (@commands) {
# check if user is ignored (and command isn't `login`)
if ($command !~ /^login / && defined $from && $pbot->{ignorelist}->check_ignore($nick, $user, $host, $from)) {
my $admin = $pbot->{admins}->loggedin($from, "$nick!$user\@$host");
my $admin = $pbot->{users}->loggedin_admin($from, "$nick!$user\@$host");
if (!defined $admin || $admin->{level} < 10) {
# hostmask ignored
return 1;

View File

@ -397,7 +397,7 @@ sub recall_message {
foreach my $msg (@$messages) {
$self->{pbot}->{logger}->log("$nick ($from) recalled <$msg->{nick}/$msg->{channel}> $msg->{msg}\n");
if ($max_recall_time && gettimeofday - $msg->{timestamp} > $max_recall_time && not $self->{pbot}->{admins}->loggedin($from, "$nick!$user\@$host")) {
if ($max_recall_time && gettimeofday - $msg->{timestamp} > $max_recall_time && not $self->{pbot}->{users}->loggedin_admin($from, "$nick!$user\@$host")) {
$max_recall_time = duration($max_recall_time);
$recall_text .= "Sorry, you can not recall messages older than $max_recall_time.";
return $recall_text;

View File

@ -58,29 +58,24 @@ sub show_nicklist {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $nicklist;
my $admin = $self->{pbot}->{admins}->loggedin($from, "$nick!$user\@$host");
if (not length $arguments) {
if (not $admin) {
return "Usage: nicklist <channel> [nick]";
}
$nicklist = Dumper($self->{nicklist});
} else {
my @args = split / /, $arguments;
return "Usage: nicklist <channel> [nick]";
}
if (@args == 1) {
if (not exists $self->{nicklist}->{$arguments}) {
return "No nicklist for $arguments.";
}
$nicklist = Dumper($self->{nicklist}->{$arguments});
} else {
if (not exists $self->{nicklist}->{$args[0]}) {
return "No nicklist for $args[0].";
} elsif (not exists $self->{nicklist}->{$args[0]}->{$args[1]}) {
return "No such nick $args[1] in channel $args[0].";
}
$nicklist = Dumper($self->{nicklist}->{$args[0]}->{$args[1]});
my @args = split / /, $arguments;
if (@args == 1) {
if (not exists $self->{nicklist}->{lc $arguments}) {
return "No nicklist for $arguments.";
}
$nicklist = Dumper($self->{nicklist}->{lc $arguments});
} else {
if (not exists $self->{nicklist}->{lc $args[0]}) {
return "No nicklist for $args[0].";
} elsif (not exists $self->{nicklist}->{lc $args[0]}->{lc $args[1]}) {
return "No such nick $args[1] in channel $args[0].";
}
$nicklist = Dumper($self->{nicklist}->{lc $args[0]}->{lc $args[1]});
}
return $nicklist;

View File

@ -37,7 +37,7 @@ use PBot::Interpreter;
use PBot::Commands;
use PBot::ChanOps;
use PBot::Factoids;
use PBot::Admins;
use PBot::Users;
use PBot::IgnoreList;
use PBot::BlackList;
use PBot::Timer;
@ -102,7 +102,15 @@ sub initialize {
# then commands so the modules can register new commands
$self->{commands} = PBot::Commands->new(pbot => $self, filename => "$data_dir/commands", %conf);
# the version
# add some commands
$self->{commands}->register(sub { $self->listcmd(@_) }, "list", 0);
$self->{commands}->register(sub { $self->ack_die(@_) }, "die", 90);
$self->{commands}->register(sub { $self->export(@_) }, "export", 90);
$self->{commands}->register(sub { $self->reload(@_) }, "reload", 90);
$self->{commands}->register(sub { $self->evalcmd(@_) }, "eval", 99);
$self->{commands}->register(sub { $self->sl(@_) }, "sl", 90);
# prepare the version
$self->{version} = PBot::VERSION->new(pbot => $self, %conf);
$self->{logger}->log($self->{version}->version . "\n");
$self->{logger}->log("Args: @ARGV\n") if @ARGV;
@ -187,7 +195,7 @@ sub initialize {
$self->{event_dispatcher} = PBot::EventDispatcher->new(pbot => $self, %conf);
$self->{irchandlers} = PBot::IRCHandlers->new(pbot => $self, %conf);
$self->{select_handler} = PBot::SelectHandler->new(pbot => $self, %conf);
$self->{admins} = PBot::Admins->new(pbot => $self, filename => "$data_dir/admins", %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);
@ -219,9 +227,11 @@ sub initialize {
}
sub random_nick {
my ($self, $length) = @_;
$length //= 9;
my @chars = ("A".."Z", "a".."z", "0".."9");
my $nick = $chars[rand @chars - 10]; # nicks cannot start with a digit
$nick .= $chars[rand @chars] for 1..9;
$nick .= $chars[rand @chars] for 1..$length;
return $nick;
}
@ -238,7 +248,7 @@ sub connect {
$self->{logger}->log("Connecting to $server ...\n");
while (not $self->{conn} = $self->{irc}->newconn(
Nick => $self->{registry}->get_value('irc', 'randomize_nick') ? random_nick : $self->{registry}->get_value('irc', 'botnick'),
Nick => $self->{registry}->get_value('irc', 'randomize_nick') ? $self->random_nick : $self->{registry}->get_value('irc', 'botnick'),
Username => $self->{registry}->get_value('irc', 'username'),
Ircname => $self->{registry}->get_value('irc', 'realname'),
Server => $server,
@ -305,4 +315,211 @@ sub change_botnick_trigger {
$self->{conn}->nick($newvalue) if $self->{connected};
}
sub listcmd {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my $text;
my $usage = "Usage: list <modules|commands|admins|users>";
if (not defined $arguments) {
return $usage;
}
if ($arguments =~ /^modules$/i) {
$text = "Loaded modules: ";
foreach my $channel (sort keys %{ $self->{factoids}->{factoids}->{hash} }) {
foreach my $command (sort keys %{ $self->{factoids}->{factoids}->{hash}->{$channel} }) {
next if $command eq '_name';
if ($self->{factoids}->{factoids}->{hash}->{$channel}->{$command}->{type} eq 'module') {
$text .= "$self->{factoids}->{factoids}->{hash}->{$channel}->{$command}->{_name} ";
}
}
}
return $text;
}
if ($arguments =~ /^commands$/i) {
$text = "Registered commands: ";
foreach my $command (sort { $a->{name} cmp $b->{name} } @{ $self->{commands}->{handlers} }) {
$text .= "$command->{name} ";
$text .= "($command->{level}) " if $command->{level} > 0;
}
return $text;
}
if ($arguments =~ /^users$/i) {
$text = "Users: ";
my $last_channel = "";
my $sep = "";
foreach my $channel (sort keys %{ $self->{users}->{users}->{hash} }) {
next if $from =~ m/^#/ and $channel ne $from and $channel ne '.*';
if ($last_channel ne $channel) {
$text .= $sep . ($channel eq ".*" ? "global" : $channel) . ": ";
$last_channel = $channel;
$sep = "";
}
foreach my $hostmask (sort { return 0 if $a eq '_name' or $b eq '_name'; $self->{users}->{users}->{hash}->{$channel}->{$a}->{name} cmp $self->{users}->{users}->{hash}->{$channel}->{$b}->{name} } keys %{ $self->{users}->{users}->{hash}->{$channel} }) {
next if $hostmask eq '_name';
$text .= $sep;
$text .= "*" if $self->{users}->{users}->{hash}->{$channel}->{$hostmask}->{loggedin};
$text .= $self->{users}->{users}->{hash}->{$channel}->{$hostmask}->{name};
$text .= " (" . $self->{users}->{users}->{hash}->{$channel}->{$hostmask}->{level} . ")" if $self->{users}->{users}->{hash}->{$channel}->{$hostmask}->{level} > 0;
$sep = "; ";
}
}
return $text;
}
if ($arguments =~ /^admins$/i) {
$text = "Admins: ";
my $last_channel = "";
my $sep = "";
foreach my $channel (sort keys %{ $self->{users}->{users}->{hash} }) {
next if $from =~ m/^#/ and $channel ne $from and $channel ne '.*';
if ($last_channel ne $channel) {
$text .= $sep . ($channel eq ".*" ? "global" : $channel) . ": ";
$last_channel = $channel;
$sep = "";
}
foreach my $hostmask (sort { return 0 if $a eq '_name' or $b eq '_name'; $self->{users}->{users}->{hash}->{$channel}->{$a}->{name} cmp $self->{users}->{users}->{hash}->{$channel}->{$b}->{name} } keys %{ $self->{users}->{users}->{hash}->{$channel} }) {
next if $hostmask eq '_name';
next if $self->{users}->{users}->{hash}->{$channel}->{$hostmask}->{level} <= 0;
$text .= $sep;
$text .= "*" if $self->{users}->{users}->{hash}->{$channel}->{$hostmask}->{loggedin};
$text .= $self->{users}->{users}->{hash}->{$channel}->{$hostmask}->{name} . " (" . $self->{users}->{users}->{hash}->{$channel}->{$hostmask}->{level} . ")";
$sep = "; ";
}
}
return $text;
}
return $usage;
}
sub sl {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
return "Usage: sl <ircd command>" if not length $arguments;
$self->{conn}->sl($arguments);
return "";
}
sub ack_die {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
$self->{logger}->log("$nick!$user\@$host made me exit.\n");
$self->atexit();
$self->{conn}->privmsg($from, "Good-bye.") if defined $from;
$self->{conn}->quit("Departure requested.");
exit 0;
}
sub export {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
return "/msg $nick Usage: export <factoids>" if not defined $arguments;
if ($arguments =~ /^factoids$/i) {
return $self->{factoids}->export_factoids;
}
}
sub evalcmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
$self->{logger}->log("[$from] $nick!$user\@$host Evaluating [$arguments]\n");
my $ret;
my $result = eval $arguments;
if ($@) {
if (length $result) {
$ret .= "[Error: $@] ";
} else {
$ret .= "Error: $@";
}
$ret =~ s/ at \(eval \d+\) line 1.//;
}
return "/say $ret $result";
}
sub reload {
my $self = shift;
my ($from, $nick, $user, $host, $arguments) = @_;
my %reloadables = (
'commands' => sub {
$self->{commands}->load_metadata;
return "Commands metadata reloaded.";
},
'blacklist' => sub {
$self->{blacklist}->clear_blacklist;
$self->{blacklist}->load_blacklist;
return "Blacklist reloaded.";
},
'whitelist' => sub {
$self->{antiflood}->{whitelist}->clear;
$self->{antiflood}->{whitelist}->load;
return "Whitelist reloaded.";
},
'ignores' => sub {
$self->{ignorelist}->clear_ignores;
$self->{ignorelist}->load_ignores;
return "Ignore list reloaded.";
},
'users' => sub {
$self->{users}->load;
return "Users reloaded.";
},
'channels' => sub {
$self->{channels}->{channels}->clear;
$self->{channels}->load_channels;
return "Channels reloaded.";
},
'bantimeouts' => sub {
$self->{chanops}->{unban_timeout}->clear;
$self->{chanops}->{unban_timeout}->load;
return "Ban timeouts reloaded.";
},
'mutetimeouts' => sub {
$self->{chanops}->{unmute_timeout}->clear;
$self->{chanops}->{unmute_timeout}->load;
return "Mute timeouts reloaded.";
},
'registry' => sub {
$self->{registry}->{registry}->clear;
$self->{registry}->load;
return "Registry reloaded.";
},
'factoids' => sub {
$self->{factoids}->{factoids}->clear;
$self->{factoids}->load_factoids;
return "Factoids reloaded.";
},
'funcs' => sub {
$self->{func_cmd}->init_funcs;
return "Funcs reloaded.";
}
);
if (not length $arguments or not exists $reloadables{$arguments}) {
my $usage = 'Usage: reload <';
$usage .= join '|', sort keys %reloadables;
$usage .= '>';
return $usage;
}
return $reloadables{$arguments}();
}
1;

View File

@ -26,12 +26,12 @@ sub initialize {
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference in StdinReader");
# create implicit bot-admin account for bot
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
if (not $self->{pbot}->{admins}->find_admin('.*', '*!stdin@pbot')) {
if (not $self->{pbot}->{users}->find_admin('.*', '*!stdin@pbot')) {
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
$self->{pbot}->{logger}->log("Adding stdin admin *!stdin\@pbot...\n");
$self->{pbot}->{admins}->add_admin($botnick, '.*', '*!stdin@pbot', 100, 'notused', 1);
$self->{pbot}->{admins}->login($botnick, "$botnick!stdin\@pbot", 'notused');
$self->{pbot}->{admins}->save_admins;
$self->{pbot}->{users}->add_user($botnick, '.*', '*!stdin@pbot', 100, undef, 1);
$self->{pbot}->{users}->login($botnick, "$botnick!stdin\@pbot", undef);
$self->{pbot}->{users}->save;
}
# used to check whether process is in background or foreground, for stdin reading

443
PBot/Users.pm Normal file
View File

@ -0,0 +1,443 @@
# File: Users.pm
# Author: pragma_
#
# Purpose: Manages list of bot users/admins and their metadata.
# 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::Users;
use warnings;
use strict;
use feature 'unicode_strings';
use PBot::DualIndexHashObject;
use Carp ();
sub new {
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref($_[1]) eq 'HASH';
my ($class, %conf) = @_;
my $self = bless {}, $class;
$self->initialize(%conf);
return $self;
}
sub initialize {
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{users} = PBot::DualIndexHashObject->new(name => 'Users', filename => $conf{filename}, pbot => $conf{pbot});
$self->load;
$self->{pbot}->{commands}->register(sub { return $self->logincmd(@_) }, "login", 0);
$self->{pbot}->{commands}->register(sub { return $self->logoutcmd(@_) }, "logout", 0);
$self->{pbot}->{commands}->register(sub { return $self->useradd(@_) }, "useradd", 60);
$self->{pbot}->{commands}->register(sub { return $self->userdel(@_) }, "userdel", 60);
$self->{pbot}->{commands}->register(sub { return $self->userset(@_) }, "userset", 60);
$self->{pbot}->{commands}->register(sub { return $self->userunset(@_) }, "userunset", 60);
$self->{pbot}->{commands}->register(sub { return $self->mycmd(@_) }, "my", 0);
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
}
sub on_join {
my ($self, $event_type, $event) = @_;
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
my $u = $self->find_user($channel, "$nick!$user\@$host");
if (defined $u) {
if ($self->{pbot}->{chanops}->can_gain_ops($channel)) {
my $modes = '+';
my $targets = '';
if ($u->{autoop}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autoop in $channel\n");
$modes .= 'o';
$targets .= "$nick ";
}
if ($u->{autovoice}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autovoice in $channel\n");
$modes .= 'v';
$targets .= "$nick ";
}
if (length $modes > 1) {
$self->{pbot}->{chanops}->add_op_command($channel, "mode $channel $modes $targets");
$self->{pbot}->{chanops}->gain_ops($channel);
}
}
if ($u->{autologin}) {
$self->{pbot}->{logger}->log("$nick!$user\@$host autologin to $user->{name} ($user->{level}) for $channel\n");
$user->{loggedin} = 1;
}
}
return 0;
}
sub add_user {
my ($self, $name, $channel, $hostmask, $level, $password, $dont_save) = @_;
$channel = '.*' if $channel !~ m/^#/;
$level //= 0;
$password //= $self->{pbot}->random_nick(16);
my $data = {
name => $name,
level => $level,
password => $password
};
$self->{pbot}->{logger}->log("Adding new user (level $level): name: $name hostmask: $hostmask channel: $channel\n");
$self->{users}->add($channel, $hostmask, $data, $dont_save);
return $data;
}
sub remove_user {
my ($self, $channel, $hostmask) = @_;
return $self->{users}->remove($channel, $hostmask);
}
sub load {
my $self = shift;
my $filename;
if (@_) { $filename = shift; } else { $filename = $self->{users}->{filename}; }
if (not defined $filename) {
Carp::carp "No users path specified -- skipping loading of users";
return;
}
$self->{users}->load;
my $i = 0;
foreach my $channel (sort keys %{ $self->{users}->{hash} } ) {
foreach my $hostmask (sort keys %{ $self->{users}->{hash}->{$channel} }) {
next if $hostmask eq '_name';
$i++;
my $name = $self->{users}->{hash}->{$channel}->{$hostmask}->{name};
my $level = $self->{users}->{hash}->{$channel}->{$hostmask}->{level};
my $password = $self->{users}->{hash}->{$channel}->{$hostmask}->{password};
if (not defined $name or not defined $level or not defined $password) {
Carp::croak "A user in $filename is missing critical data\n";
}
}
}
$self->{pbot}->{logger}->log(" $i users loaded.\n");
}
sub save {
my ($self) = @_;
$self->{users}->save;
}
sub hostmask_or_account_name {
my ($self, $channel, $hostmask) = @_;
$channel = lc $channel;
$hostmask = lc $hostmask;
$channel = '.*' if $channel !~ /^#/;
if (exists $self->{users}->{hash}->{$channel}) {
if (not exists $self->{users}->{hash}->{$channel}->{$hostmask}) {
my $last_level = 0;
# find hostmask by account name or wildcard
foreach my $mask (keys %{ $self->{users}->{hash}->{$channel} }) {
next if $mask eq '_name';
if (lc $self->{users}->{hash}->{$channel}->{$mask}->{name} eq $hostmask) {
if ($last_level < $self->{users}->{hash}->{$channel}->{$mask}->{level}) {
$hostmask = $mask;
$last_level = $self->{users}->{hash}->{$channel}->{$mask}->{level};
}
}
if ($mask =~ /[*?]/) {
# contains * or ? so it's converted to a regex
my $mask_quoted = quotemeta $mask;
$mask_quoted =~ s/\\\*/.*?/g;
$mask_quoted =~ s/\\\?/./g;
if ($hostmask =~ m/^$mask_quoted$/i) {
if ($last_level < $self->{users}->{hash}->{$channel}->{$mask}->{level}) {
$hostmask = $mask;
$last_level = $self->{users}->{hash}->{$channel}->{$mask}->{level};
}
}
}
}
}
}
return $hostmask;
}
sub find_admin {
my ($self, $channel, $hostmask, $min_level) = @_;
$min_level //= 1;
$channel = $self->{pbot}->{registry}->get_value('irc', 'botnick') if not defined $channel;
$hostmask = '.*' if not defined $hostmask;
$hostmask = lc $hostmask;
my $result = eval {
my $admin;
foreach my $channel_regex (keys %{ $self->{users}->{hash} }) {
if ($channel !~ m/^#/ or $channel =~ m/^$channel_regex$/i) {
foreach my $hostmask_regex (keys %{ $self->{users}->{hash}->{$channel_regex} }) {
next if $hostmask_regex eq '_name';
if ($hostmask_regex =~ m/[*?]/) {
# contains * or ? so it's converted to a regex
my $hostmask_quoted = quotemeta $hostmask_regex;
$hostmask_quoted =~ s/\\\*/.*?/g;
$hostmask_quoted =~ s/\\\?/./g;
if ($hostmask =~ m/^$hostmask_quoted$/i) {
my $temp = $self->{users}->{hash}->{$channel_regex}->{$hostmask_regex};
$admin = $temp if $temp->{level} >= $min_level and (not defined $admin or $admin->{level} < $temp->{level});
}
} else {
# direct comparison
if ($hostmask eq lc $hostmask_regex) {
my $temp = $self->{users}->{hash}->{$channel_regex}->{$hostmask_regex};
$admin = $temp if $temp->{level} >= $min_level and (not defined $admin or $admin->{level} < $temp->{level});
}
}
}
}
}
return $admin;
};
if ($@) {
$self->{pbot}->{logger}->log("Error in find_admin parameters: $@\n");
}
return $result;
}
sub find_user {
my ($self, $from, $hostmask) = @_;
return $self->find_admin($from, $hostmask, 0);
}
sub loggedin {
my ($self, $channel, $hostmask) = @_;
my $user = $self->find_user($channel, $hostmask);
if (defined $user and $user->{loggedin}) {
return $user;
}
return undef;
}
sub loggedin_admin {
my ($self, $channel, $hostmask) = @_;
my $user = $self->loggedin($channel, $hostmask);
if (defined $user and $user->{level} > 0) {
return $user;
}
return undef;
}
sub login {
my ($self, $channel, $hostmask, $password) = @_;
my $user = $self->find_user($channel, $hostmask);
if (not defined $user) {
$self->{pbot}->{logger}->log("Attempt to login non-existent [$channel][$hostmask] failed\n");
return "You do not have an account in $channel.";
}
if (defined $password and $user->{password} ne $password) {
$self->{pbot}->{logger}->log("Bad login password for [$channel][$hostmask]\n");
return "I don't think so.";
}
$user->{loggedin} = 1;
$self->{pbot}->{logger}->log("$hostmask logged into $channel\n");
return "Logged into $channel.";
}
sub logout {
my ($self, $channel, $hostmask) = @_;
my $user = $self->find_user($channel, $hostmask);
delete $user->{loggedin} if defined $user;
}
sub logincmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
my $channel = $from;
if (not $arguments) {
return "Usage: login [channel] password";
}
if ($arguments =~ m/^([^ ]+)\s+(.+)/) {
$channel = $1;
$arguments = $2;
}
if ($self->loggedin($channel, "$nick!$user\@$host")) {
return "/msg $nick You are already logged into channel $channel.";
}
my $result = $self->login($channel, "$nick!$user\@$host", $arguments);
return "/msg $nick $result";
}
sub logoutcmd {
my ($self, $from, $nick, $user, $host, $arguments) = @_;
return "/msg $nick Uh, you aren't logged into channel $from." if (not $self->loggedin($from, "$nick!$user\@$host"));
$self->logout($from, "$nick!$user\@$host");
return "/msg $nick Good-bye, $nick.";
}
sub useradd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($name, $channel, $hostmask, $level, $password) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 5);
$level //= 0;
if (not defined $name or not defined $channel or not defined $hostmask) {
return "/msg $nick Usage: useradd <account name> <channel> <hostmask> [level] [password]";
}
$channel = '.*' if $channel !~ /^#/;
my $admin = $self->{pbot}->{users}->find_admin($channel, "$nick!$user\@$host");
if (not $admin) {
return "You are not an admin for $channel; cannot add users to that channel.\n";
}
# don't allow non-bot-owners to add admins that can also add admins
if ($admin->{level} < 90 and $level > 40) {
return "You may not set admin level higher than 40.\n";
}
$self->{pbot}->{users}->add_user($name, $channel, $hostmask, $level, $password);
return not $level ? "User added." : "Admin added.";
}
sub userdel {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $hostmask) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
if (not defined $channel or not defined $hostmask) {
return "/msg $nick Usage: userdel <channel> <hostmask or account name>";
}
$hostmask = $self->hostmask_or_account_name($channel, $hostmask);
$channel = '.*' if $channel !~ /^#/;
return $self->remove_user($channel, $hostmask);
}
sub userset {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $hostmask, $key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 4);
if (not defined $channel or not defined $hostmask) {
return "Usage: userset <channel> <hostmask or account name> [key] [value]";
}
$hostmask = $self->hostmask_or_account_name($channel, $hostmask);
my $admin = $self->find_admin($channel, "$nick!$user\@$host");
my $target = $self->find_user($channel, $hostmask);
if (not $admin) {
return "You are not an admin for $channel; cannot modify their users.";
}
if (not $target) {
return "There is no user $hostmask in channel $channel.";
}
# don't allow non-bot-owners to add admins that can also add admins
if (defined $key and $key eq 'level' and $admin->{level} < 90 and $value > 40) {
return "You may not set user level higher than 40.\n";
}
if (defined $key and $target->{level} > $admin->{level}) {
return "You may not modify users higher in level than you.";
}
$channel = '.*' if $channel !~ /^#/;
my $result = $self->{users}->set($channel, $hostmask, $key, $value);
$result =~ s/^password => .*;$/password => <private>;/m;
return $result;
}
sub userunset {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($channel, $hostmask, $key) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 3);
if (not defined $channel or not defined $hostmask) {
return "Usage: userunset <channel> <hostmask or account name> <key>";
}
my $admin = $self->find_admin($channel, "$nick!$user\@$host");
$hostmask = $self->hostmask_or_account_name($channel, $hostmask);
my $target = $self->find_user($channel, $hostmask);
if (not $admin) {
return "You are not an admin for $channel; cannot modify their users.";
}
if (not $target) {
return "There is no user $hostmask in channel $channel.";
}
if ($target->{level} >= $user->{level}) {
return "You may not modify users equal or higher in level than you.";
}
$channel = '.*' if $channel !~ /^#/;
return $self->{users}->unset($channel, $hostmask, $key);
}
sub mycmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my ($key, $value) = $self->{pbot}->{interpreter}->split_args($stuff->{arglist}, 2);
if (not defined $key) {
return "Usage: my <key> [value]";
}
my $channel = $from;
$channel = '.*' if $channel !~ /^#/;
my $hostmask = $self->hostmask_or_account_name($channel, "$nick!$user\@$host");
my $u = $self->find_user($channel, $hostmask);
print "hostmask: $hostmask\n";
use Data::Dumper;
print Dumper \$u;
if (not $u) {
$channel = '.*';
$hostmask = "$nick!*\@*";
$u = $self->add_user("my_$nick", $channel, $hostmask);
$u->{autologin} = 1;
$u->{loggedin} = 1;
}
if ($u->{level} == 0) {
my @disallowed = qw/level autoop autovoice/;
if (grep { lc $key } @disallowed) {
return "You must be an admin to set $key.";
}
}
my $result = $self->{users}->set($channel, $hostmask, $key, $value);
$result =~ s/^password => .*;$/password => <private>;/m;
return $result;
}
1;