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

Replaced admin-levels with user-capabilities [WIP commit 1 of 2]

This commit is contained in:
Pragmatic Software 2020-02-03 09:50:38 -08:00
parent 186f0d3d65
commit 866d802850
8 changed files with 300 additions and 100 deletions

View File

@ -417,9 +417,9 @@ sub check_flood {
$self->{whois_pending}->{$nick} = gettimeofday; $self->{whois_pending}->{$nick} = gettimeofday;
} }
} else { } else {
if ($mode == $self->{pbot}->{messagehistory}->{MSG_JOIN} && exists $self->{pbot}->{capabilities}->{'extended-join'}) { if ($mode == $self->{pbot}->{messagehistory}->{MSG_JOIN} && exists $self->{pbot}->{irc_capabilities}->{'extended-join'}) {
# don't WHOIS joins if extended-join capability is active # don't WHOIS joins if extended-join capability is active
} elsif (not exists $self->{pbot}->{capabilities}->{'account-notify'}) { } elsif (not exists $self->{pbot}->{irc_capabilities}->{'account-notify'}) {
if (not exists $self->{whois_pending}->{$nick}) { if (not exists $self->{whois_pending}->{$nick}) {
$self->{pbot}->{messagehistory}->{database}->set_current_nickserv_account($account, ''); $self->{pbot}->{messagehistory}->{database}->set_current_nickserv_account($account, '');
$self->{pbot}->{conn}->whois($nick); $self->{pbot}->{conn}->whois($nick);
@ -820,7 +820,7 @@ sub check_bans {
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data); $self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
} }
} else { } else {
if (not exists $self->{pbot}->{capabilities}->{'account-notify'}) { if (not exists $self->{pbot}->{irc_capabilities}->{'account-notify'}) {
# mark this account as needing check-bans when nickserv account is identified # mark this account as needing check-bans when nickserv account is identified
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated'); my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
if (not $channel_data->{validated} & $self->{NEEDS_CHECKBAN}) { if (not $channel_data->{validated} & $self->{NEEDS_CHECKBAN}) {

163
PBot/Capabilities.pm Normal file
View File

@ -0,0 +1,163 @@
# 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::Capabilities;
# purpose: provides interface to set/remove/modify/query user capabilities.
#
# Examples:
#
use warnings;
use strict;
use feature 'unicode_strings';
use feature 'switch';
no if $] >= 5.018, warnings => "experimental::smartmatch";
use PBot::HashObject;
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__);
my $filename = $conf{filename} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/capabilities';
$self->{caps} = PBot::HashObject->new(name => 'Capabilities', filename => $filename, pbot => $self->{pbot});
$self->{caps}->load;
# 'cap' command registered in PBot.pm because $self->{pbot}->{commands} is not yet loaded.
}
sub has {
my ($self, $cap, $subcap, $depth) = @_;
my $cap_data = $self->{caps}->get_data($cap);
return 0 if not defined $cap_data;
$depth //= 2;
if (--$depth <= 0) {
$self->{pbot}->{logger}->log("Max recursion reached for PBot::Capabilities->has()\n");
return 0;
}
foreach my $c (keys %{$cap_data}) {
next if $c eq '_name';
return 1 if $c eq $subcap;
return 1 if $self->has($c, $subcap, $depth);
}
return 0;
}
sub userhas {
my ($self, $user, $cap) = @_;
return 0 if not defined $user;
return 1 if $user->{$cap};
foreach my $key (keys %{$user}) {
next if $key eq '_name';
return 1 if $self->has($key, $cap, 10);
}
return 0;
}
sub exists {
my ($self, $cap) = @_;
$cap = lc $cap;
foreach my $c (keys %{$self->{caps}->{hash}}) {
next if $c eq '_name';
return 1 if $c eq $cap;
foreach my $sub_cap (keys %{$self->{caps}->{hash}->{$c}}) {
return 1 if $sub_cap eq $cap;
}
}
return 0;
}
sub add {
my ($self, $cap, $subcap, $dontsave) = @_;
if (not defined $subcap) {
if (not $self->{caps}->exists($cap)) {
$self->{caps}->add($cap, {}, $dontsave);
}
} else {
if ($self->{caps}->exists($cap)) {
$self->{caps}->set($cap, $subcap, 1, $dontsave);
} else {
$self->{caps}->add($cap, { $subcap => 1 }, $dontsave);
}
}
}
sub remove {
}
sub rebuild_botowner_capabilities {
my ($self) = @_;
$self->{caps}->remove('botowner');
foreach my $cap (keys %{$self->{caps}->{hash}}) {
next if $cap eq '_name';
$self->add('botowner', $cap, 1);
}
}
sub capcmd {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
my $command = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
my $result;
given ($command) {
when ('list') {
my $cap = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
if (defined $cap) {
$cap = lc $cap;
return "No such capability $cap." if not exists $self->{caps}->{hash}->{$cap};
return "Capability $cap has no sub-capabilities." if keys %{$self->{caps}->{hash}->{$cap}} == 1;
$result = "Sub-capabilities for $cap: ";
$result .= join(', ', grep { $_ ne '_name' } sort keys %{$self->{caps}->{hash}->{$cap}});
} else {
return "No capabilities defined." if keys(%{$self->{caps}->{hash}}) == 0;
$result = "Capabilities: ";
my @caps;
# first list all capabilities that have sub-capabilities (i.e. grouped capabilities)
foreach my $cap (sort keys %{$self->{caps}->{hash}}) {
my $count = keys(%{$self->{caps}->{hash}->{$cap}}) - 1;
push @caps, "$cap [$count]" if $count;
}
# then list stand-alone capabilities
foreach my $cap (sort keys %{$self->{caps}->{hash}}) {
next if keys(%{$self->{caps}->{hash}->{$cap}}) > 1;
push @caps, $cap;
}
$result .= join ', ', @caps;
}
}
when ('userhas') {
}
when ('add') {
}
when ('remove') {
}
default {
$result = "Usage: cap list [capability] | cap add <capability> [sub-capability] | cap remove <capability> [sub-capability] | cap userhas <user> <capability>";
}
}
return $result;
}
1;

View File

@ -28,22 +28,50 @@ sub new {
sub initialize { sub initialize {
my ($self, %conf) = @_; my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__); $self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
$self->{pbot}->{commands}->register(sub { return $self->ban_user(@_) }, "ban", 10); # register commands
$self->{pbot}->{commands}->register(sub { return $self->unban_user(@_) }, "unban", 10); $self->{pbot}->{commands}->register(sub { return $self->ban_user(@_) }, "ban", 1);
$self->{pbot}->{commands}->register(sub { return $self->mute_user(@_) }, "mute", 10); $self->{pbot}->{commands}->register(sub { return $self->unban_user(@_) }, "unban", 1);
$self->{pbot}->{commands}->register(sub { return $self->unmute_user(@_) }, "unmute", 10); $self->{pbot}->{commands}->register(sub { return $self->mute_user(@_) }, "mute", 1);
$self->{pbot}->{commands}->register(sub { return $self->kick_user(@_) }, "kick", 10); $self->{pbot}->{commands}->register(sub { return $self->unmute_user(@_) }, "unmute", 1);
$self->{pbot}->{commands}->register(sub { return $self->kick_user(@_) }, "kick", 1);
$self->{pbot}->{commands}->register(sub { return $self->checkban(@_) }, "checkban", 0); $self->{pbot}->{commands}->register(sub { return $self->checkban(@_) }, "checkban", 0);
$self->{pbot}->{commands}->register(sub { return $self->checkmute(@_) }, "checkmute", 0); $self->{pbot}->{commands}->register(sub { return $self->checkmute(@_) }, "checkmute", 0);
$self->{pbot}->{commands}->register(sub { return $self->op_user(@_) }, "op", 10); $self->{pbot}->{commands}->register(sub { return $self->op_user(@_) }, "op", 1);
$self->{pbot}->{commands}->register(sub { return $self->deop_user(@_) }, "deop", 10); $self->{pbot}->{commands}->register(sub { return $self->deop_user(@_) }, "deop", 1);
$self->{pbot}->{commands}->register(sub { return $self->voice_user(@_) }, "voice", 10); $self->{pbot}->{commands}->register(sub { return $self->voice_user(@_) }, "voice", 1);
$self->{pbot}->{commands}->register(sub { return $self->devoice_user(@_) }, "devoice", 10); $self->{pbot}->{commands}->register(sub { return $self->devoice_user(@_) }, "devoice", 1);
$self->{pbot}->{commands}->register(sub { return $self->mode(@_) }, "mode", 40); $self->{pbot}->{commands}->register(sub { return $self->mode(@_) }, "mode", 1);
$self->{pbot}->{commands}->register(sub { return $self->invite(@_) }, "invite", 10); $self->{pbot}->{commands}->register(sub { return $self->invite(@_) }, "invite", 1);
# allow commands to set modes
$self->{pbot}->{capabilities}->add('can-ban', 'can-mode-b', 1);
$self->{pbot}->{capabilities}->add('can-unban', 'can-mode-b', 1);
$self->{pbot}->{capabilities}->add('can-mute', 'can-mode-q', 1);
$self->{pbot}->{capabilities}->add('can-unmute', 'can-mode-q', 1);
$self->{pbot}->{capabilities}->add('can-op', 'can-mode-o', 1);
$self->{pbot}->{capabilities}->add('can-deop', 'can-mode-o', 1);
$self->{pbot}->{capabilities}->add('can-voice', 'can-mode-v', 1);
$self->{pbot}->{capabilities}->add('can-devoice', 'can-mode-v', 1);
# create can-mode-any capabilities group
foreach my $mode ("a" .. "z", "A" .. "Z") {
$self->{pbot}->{capabilities}->add('can-mode-any', "can-mode-$mode", 1);
}
$self->{pbot}->{capabilities}->add('can-mode-any', 'can-mode', 1);
# create chanop capabilities group
$self->{pbot}->{capabilities}->add('chanop', 'can-ban', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-unban', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-mute', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-unmute', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-kick', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-op', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-deop', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-voice', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-devoice', 1);
$self->{pbot}->{capabilities}->add('chanop', 'can-invite', 1);
$self->{invites} = {}; # track who invited who in order to direct invite responses to them $self->{invites} = {}; # track who invited who in order to direct invite responses to them
@ -127,7 +155,6 @@ sub generic_mode_user {
if ($channel !~ m/^#/) { if ($channel !~ m/^#/) {
# from message # from message
$channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist}); $channel = $self->{pbot}->{interpreter}->shift_arg($stuff->{arglist});
$result = 'Done.';
if (not defined $channel) { if (not defined $channel) {
return "Usage from message: $mode_name <channel> [nick]"; return "Usage from message: $mode_name <channel> [nick]";
} elsif ($channel !~ m/^#/) { } elsif ($channel !~ m/^#/) {
@ -159,10 +186,11 @@ sub generic_mode_user {
if ($i >= $max_modes) { if ($i >= $max_modes) {
my $args = "$channel $mode $list"; my $args = "$channel $mode $list";
$stuff->{arglist} = $self->{pbot}->{interpreter}->make_args($args); $stuff->{arglist} = $self->{pbot}->{interpreter}->make_args($args);
$self->mode($channel, $nick, $stuff->{user}, $stuff->{host}, $args, $stuff); $result = $self->mode($channel, $nick, $stuff->{user}, $stuff->{host}, $args, $stuff);
$mode = $flag; $mode = $flag;
$list = ''; $list = '';
$i = 0; $i = 0;
last if $result ne '' and $result ne 'Done.';
} }
} }
} }
@ -170,13 +198,12 @@ sub generic_mode_user {
if ($i) { if ($i) {
my $args = "$channel $mode $list"; my $args = "$channel $mode $list";
$stuff->{arglist} = $self->{pbot}->{interpreter}->make_args($args); $stuff->{arglist} = $self->{pbot}->{interpreter}->make_args($args);
$self->mode($channel, $nick, $stuff->{user}, $stuff->{host}, $args, $stuff); $result = $self->mode($channel, $nick, $stuff->{user}, $stuff->{host}, $args, $stuff);
} }
return $result; return $result;
} }
sub op_user { sub op_user {
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_; my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
return $self->generic_mode_user('+o', 'op', $from, $nick, $stuff); return $self->generic_mode_user('+o', 'op', $from, $nick, $stuff);
@ -222,6 +249,8 @@ sub mode {
my ($new_modes, $new_targets) = ("", ""); my ($new_modes, $new_targets) = ("", "");
my $max_modes = $self->{pbot}->{ircd}->{MODES} // 1; my $max_modes = $self->{pbot}->{ircd}->{MODES} // 1;
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
while ($modes =~ m/(.)/g) { while ($modes =~ m/(.)/g) {
my $mode = $1; my $mode = $1;
@ -231,6 +260,10 @@ sub mode {
next; next;
} }
if (not $self->{pbot}->{capabilities}->userhas($u, "can-mode-$mode")) {
return "/msg $nick Your user account does not have the can-mode-$mode capability required to set this mode.";
}
my $target = $targets[$arg++] // ""; my $target = $targets[$arg++] // "";
if (($mode eq 'v' or $mode eq 'o') and $target =~ m/\*/) { if (($mode eq 'v' or $mode eq 'o') and $target =~ m/\*/) {
@ -297,6 +330,8 @@ sub mode {
if ($from !~ m/^#/) { if ($from !~ m/^#/) {
return "Done."; return "Done.";
} else {
return "";
} }
} }
@ -363,10 +398,6 @@ sub ban_user {
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick'); my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
return "I don't think so." if $target =~ /^\Q$botnick\E!/i; 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}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "You are not an admin for $channel.";
}
my $result = ''; my $result = '';
my $sep = ''; my $sep = '';
my @targets = split /,/, $target; my @targets = split /,/, $target;
@ -430,10 +461,6 @@ sub unban_user {
return "Usage for /msg: unban <nick/mask> <channel> [false value to use unban queue]" if $channel !~ /^#/; return "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}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "You are not an admin for $channel.";
}
my @targets = split /,/, $target; my @targets = split /,/, $target;
$immediately = 0 if @targets > 1; $immediately = 0 if @targets > 1;
@ -494,10 +521,6 @@ sub mute_user {
my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick'); my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick');
return "I don't think so." if $target =~ /^\Q$botnick\E!/i; 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}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "You are not an admin for $channel.";
}
my $result = ''; my $result = '';
my $sep = ''; my $sep = '';
my @targets = split /,/, $target; my @targets = split /,/, $target;
@ -561,10 +584,6 @@ sub unmute_user {
return "Usage for /msg: unmute <nick/mask> <channel> [false value to use unban queue]" if $channel !~ /^#/; return "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}->{users}->loggedin_admin($channel, "$nick!$user\@$host")) {
return "You are not an admin for $channel.";
}
my @targets = split /,/, $target; my @targets = split /,/, $target;
$immediately = 0 if @targets > 1; $immediately = 0 if @targets > 1;
@ -615,10 +634,6 @@ sub kick_user {
$channel = $1; $channel = $1;
} }
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 "You are not an admin for $channel.";
}
my @insults; my @insults;
if (not length $reason) { if (not length $reason) {
if (open my $fh, '<', $self->{pbot}->{registry}->get_value('general', 'module_dir') . '/insults.txt') { if (open my $fh, '<', $self->{pbot}->{registry}->get_value('general', 'module_dir') . '/insults.txt') {

View File

@ -2,10 +2,8 @@
# #
# Author: pragma_ # Author: pragma_
# #
# Purpose: Derives from Registerable class to provide functionality to # Purpose: Registers commands. Invokes commands with user capability
# register subroutines, along with a command name and admin level. # validation.
# Registered items will then be executed if their command name matches
# a name provided via input.
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # License, v. 2.0. If a copy of the MPL was not distributed with this
@ -40,32 +38,35 @@ sub initialize {
$self->{metadata} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Commands', filename => $conf{filename}); $self->{metadata} = PBot::HashObject->new(pbot => $self->{pbot}, name => 'Commands', filename => $conf{filename});
$self->load_metadata; $self->load_metadata;
$self->register(sub { $self->cmdset(@_) }, "cmdset", 90); $self->register(sub { $self->cmdset(@_) }, "cmdset", 1);
$self->register(sub { $self->cmdunset(@_) }, "cmdunset", 90); $self->register(sub { $self->cmdunset(@_) }, "cmdunset", 1);
$self->register(sub { $self->help(@_) }, "help", 0); $self->register(sub { $self->help(@_) }, "help", 0);
$self->register(sub { $self->uptime(@_) }, "uptime", 0); $self->register(sub { $self->uptime(@_) }, "uptime", 0);
$self->register(sub { $self->in_channel(@_) }, "in", 10); $self->register(sub { $self->in_channel(@_) }, "in", 1);
} }
sub register { sub register {
my ($self, $subref, $name, $level) = @_; my ($self, $subref, $name, $requires_cap) = @_;
if (not defined $subref or not defined $name or not defined $level) { if (not defined $subref or not defined $name) {
Carp::croak("Missing parameters to Commands::register"); Carp::croak("Missing parameters to Commands::register");
} }
my $ref = $self->SUPER::register($subref); my $ref = $self->SUPER::register($subref);
$ref->{name} = lc $name; $ref->{name} = lc $name;
$ref->{level} = $level; $ref->{requires_cap} = $requires_cap // 0;
if (not $self->{metadata}->exists($name)) { if (not $self->{metadata}->exists($name)) {
$self->{metadata}->add($name, { level => $level, help => '' }, 1); $self->{metadata}->add($name, { requires_cap => $requires_cap, help => '' }, 1);
} else { } else {
if (not defined $self->get_meta($name, 'level')) { if (not defined $self->get_meta($name, 'requires_cap')) {
$self->{metadata}->set($name, 'level', $level, 1); $self->{metadata}->set($name, 'requires_cap', $requires_cap, 1);
} }
} }
# add can-cmd capability
$self->{pbot}->{capabilities}->add("can-$name", undef, 1);
return $ref; return $ref;
} }
@ -96,39 +97,44 @@ sub interpreter {
$self->{pbot}->{logger}->log(Dumper $stuff); $self->{pbot}->{logger}->log(Dumper $stuff);
} }
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}->{users}->loggedin_admin($admin_channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
my $admin_level = defined $admin ? $admin->{level} : 0;
my $keyword = lc $stuff->{keyword}; my $keyword = lc $stuff->{keyword};
my $from = $stuff->{from};
if (exists $stuff->{'effective-level'}) { my ($cmd_channel) = $stuff->{arguments} =~ m/\B(#[^ ]+)/; # assume command is invoked in regards to first channel-like argument
$self->{pbot}->{logger}->log("override level to $stuff->{'effective-level'}\n"); $cmd_channel = $from if not defined $cmd_channel; # otherwise command is invoked in regards to the channel the user is in
$admin_level = $stuff->{'effective-level'}; my $user = $self->{pbot}->{users}->loggedin($cmd_channel, "$stuff->{nick}!$stuff->{user}\@$stuff->{host}");
my $cap_override;
if (exists $stuff->{'cap-override'}) {
$self->{pbot}->{logger}->log("Override cap to $stuff->{'cap-override'}\n");
$cap_override = $stuff->{'cap-override'};
} }
foreach my $ref (@{ $self->{handlers} }) { foreach my $ref (@{ $self->{handlers} }) {
if ($ref->{name} eq $keyword) { if ($ref->{name} eq $keyword) {
my $cmd_level = $self->get_meta($keyword, 'level') // $ref->{level}; my $requires_cap = $self->get_meta($keyword, 'requires_cap') // $ref->{requires_cap};
if ($admin_level >= $cmd_level) { if ($requires_cap) {
$stuff->{no_nickoverride} = 1; if (defined $cap_override) {
my $result = &{ $ref->{subref} }($stuff->{from}, $stuff->{nick}, $stuff->{user}, $stuff->{host}, $stuff->{arguments}, $stuff); if (not $self->{pbot}->{capabilities}->has($cap_override, "can-$keyword")) {
if ($stuff->{referenced}) { return "/msg $stuff->{nick} The $keyword command requires the can-$keyword capability, which cap-override $cap_override does not have.";
return undef if $result =~ m/(?:usage:|no results)/i;
} }
return $result;
} else { } else {
return undef if $stuff->{referenced}; if (not defined $user) {
if ($admin_level == 0) { return "/msg $stuff->{nick} You must be logged into your user account to use $keyword.";
return "/msg $stuff->{nick} You must be an admin to use this command.";
} else {
return "/msg $stuff->{nick} Your level is too low to use this command.";
} }
if (not $self->{pbot}->{capabilities}->userhas($user, "can-$keyword")) {
return "/msg $stuff->{nick} The $keyword command requires the can-$keyword capability, which your user account does not have.";
} }
} }
} }
$stuff->{no_nickoverride} = 1;
my $result = &{ $ref->{subref} }($stuff->{from}, $stuff->{nick}, $stuff->{user}, $stuff->{host}, $stuff->{arguments}, $stuff);
return undef if $stuff->{referenced} and $result =~ m/(?:usage:|no results)/i;
return $result;
}
}
return undef; return undef;
} }
@ -185,12 +191,12 @@ sub help {
if ($self->exists($keyword)) { if ($self->exists($keyword)) {
if (exists $self->{metadata}->{hash}->{$keyword}) { if (exists $self->{metadata}->{hash}->{$keyword}) {
my $name = $self->{metadata}->{hash}->{$keyword}->{_name}; my $name = $self->{metadata}->{hash}->{$keyword}->{_name};
my $level = $self->{metadata}->{hash}->{$keyword}->{level}; my $requires_cap = $self->{metadata}->{hash}->{$keyword}->{requires_cap};
my $help = $self->{metadata}->{hash}->{$keyword}->{help}; my $help = $self->{metadata}->{hash}->{$keyword}->{help};
my $result = "/say $name: "; my $result = "/say $name: ";
if (defined $level and $level > 0) { if ($requires_cap) {
$result .= "[Level $level admin command] "; $result .= "[Requires can-$keyword] ";
} }
if (not defined $help or not length $help) { if (not defined $help or not length $help) {

View File

@ -345,7 +345,7 @@ sub on_join {
my $msg = 'JOIN'; my $msg = 'JOIN';
if (exists $self->{pbot}->{capabilities}->{'extended-join'}) { if (exists $self->{pbot}->{irc_capabilities}->{'extended-join'}) {
$msg .= " $event->{event}->{args}[0] :$event->{event}->{args}[1]"; $msg .= " $event->{event}->{args}[0] :$event->{event}->{args}[1]";
$self->{pbot}->{messagehistory}->{database}->update_gecos($message_account, $event->{event}->{args}[1], scalar gettimeofday); $self->{pbot}->{messagehistory}->{database}->update_gecos($message_account, $event->{event}->{args}[1], scalar gettimeofday);
@ -480,7 +480,7 @@ sub on_cap {
my @caps = split /\s+/, $event->{event}->{args}->[1]; my @caps = split /\s+/, $event->{event}->{args}->[1];
foreach my $cap (@caps) { foreach my $cap (@caps) {
$self->{pbot}->{capabilities}->{$cap} = 1; $self->{pbot}->{irc_capabilities}->{$cap} = 1;
} }
} else { } else {
$self->{pbot}->{logger}->log(Dumper $event->{event}); $self->{pbot}->{logger}->log(Dumper $event->{event});

View File

@ -21,6 +21,7 @@ use Carp ();
use PBot::Logger; use PBot::Logger;
use PBot::VERSION; use PBot::VERSION;
use PBot::Registry; use PBot::Registry;
use PBot::Capabilities;
use PBot::SelectHandler; use PBot::SelectHandler;
use PBot::StdinReader; use PBot::StdinReader;
use PBot::IRC; use PBot::IRC;
@ -99,16 +100,22 @@ sub initialize {
exit; exit;
} }
# then capabilities so commands can add new capabilities
$self->{capabilities} = PBot::Capabilities->new(pbot => $self, filename => "$data_dir/capabilities", %conf);
# then commands so the modules can register new commands # then commands so the modules can register new commands
$self->{commands} = PBot::Commands->new(pbot => $self, filename => "$data_dir/commands", %conf); $self->{commands} = PBot::Commands->new(pbot => $self, filename => "$data_dir/commands", %conf);
# add some commands # add some commands
$self->{commands}->register(sub { $self->listcmd(@_) }, "list", 0); $self->{commands}->register(sub { $self->listcmd(@_) }, "list");
$self->{commands}->register(sub { $self->ack_die(@_) }, "die", 90); $self->{commands}->register(sub { $self->ack_die(@_) }, "die", 1);
$self->{commands}->register(sub { $self->export(@_) }, "export", 90); $self->{commands}->register(sub { $self->export(@_) }, "export", 1);
$self->{commands}->register(sub { $self->reload(@_) }, "reload", 90); $self->{commands}->register(sub { $self->reload(@_) }, "reload", 1);
$self->{commands}->register(sub { $self->evalcmd(@_) }, "eval", 99); $self->{commands}->register(sub { $self->evalcmd(@_) }, "eval", 1);
$self->{commands}->register(sub { $self->sl(@_) }, "sl", 90); $self->{commands}->register(sub { $self->sl(@_) }, "sl", 1);
# add 'cap' capability command
$self->{commands}->register(sub { $self->{capabilities}->capcmd(@_) }, "cap");
# prepare the version # prepare the version
$self->{version} = PBot::VERSION->new(pbot => $self, %conf); $self->{version} = PBot::VERSION->new(pbot => $self, %conf);
@ -225,6 +232,9 @@ sub initialize {
# start timer # start timer
$self->{timer}->start(); $self->{timer}->start();
# give botowner all capabilities
$self->{capabilities}->rebuild_botowner_capabilities();
} }
sub random_nick { sub random_nick {

View File

@ -34,10 +34,10 @@ sub initialize {
$self->{pbot}->{commands}->register(sub { return $self->logincmd(@_) }, "login", 0); $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->logoutcmd(@_) }, "logout", 0);
$self->{pbot}->{commands}->register(sub { return $self->useradd(@_) }, "useradd", 60); $self->{pbot}->{commands}->register(sub { return $self->useradd(@_) }, "useradd", 1);
$self->{pbot}->{commands}->register(sub { return $self->userdel(@_) }, "userdel", 60); $self->{pbot}->{commands}->register(sub { return $self->userdel(@_) }, "userdel", 1);
$self->{pbot}->{commands}->register(sub { return $self->userset(@_) }, "userset", 60); $self->{pbot}->{commands}->register(sub { return $self->userset(@_) }, "userset", 1);
$self->{pbot}->{commands}->register(sub { return $self->userunset(@_) }, "userunset", 60); $self->{pbot}->{commands}->register(sub { return $self->userunset(@_) }, "userunset", 1);
$self->{pbot}->{commands}->register(sub { return $self->mycmd(@_) }, "my", 0); $self->{pbot}->{commands}->register(sub { return $self->mycmd(@_) }, "my", 0);
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) }); $self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
@ -502,21 +502,22 @@ sub mycmd {
if (defined $key) { if (defined $key) {
$key = lc $key; $key = lc $key;
if (defined $value and $u->{level} == 0) { if (defined $value and not $self->{pbot}->{capabilities}->userhas($u, 'admin')) {
my @disallowed = qw/name level autoop autovoice/; my @disallowed = qw/name autoop autovoice/;
if (grep { $_ eq $key } @disallowed) { if (grep { $_ eq $key } @disallowed) {
return "You must be an admin to set $key."; return "The $key metadata requires the admin capability to set, which your user account does not have.";
} }
} }
if ($key eq 'level' and defined $value and $u->{level} < 90 and $value > $u->{level}) { if (defined $value and not $self->{pbot}->{capabilities}->userhas($u, 'can-modify-capabilities')) {
return "You may not increase your level!"; if ($key =~ m/^can-/ or $self->{pbot}->{capabilities}->exists($key)) {
return "The $key metadata requires the can-modify-capabilities capability, which your user account does not have.";
}
} }
} else { } else {
$result = "Usage: my <key> [value]; "; $result = "Usage: my <key> [value]; ";
} }
my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask); my ($found_channel, $found_hostmask) = $self->find_user_account($channel, $hostmask);
$found_channel = $channel if not defined $found_channel; # let DualIndexHashObject disambiguate $found_channel = $channel if not defined $found_channel; # let DualIndexHashObject disambiguate
$result .= $self->{users}->set($found_channel, $found_hostmask, $key, $value); $result .= $self->{users}->set($found_channel, $found_hostmask, $key, $value);

View File

@ -32,6 +32,10 @@ sub initialize {
$self->{pbot}->{commands}->register(sub { $self->modcmd(@_) }, 'mod', 0); $self->{pbot}->{commands}->register(sub { $self->modcmd(@_) }, 'mod', 0);
$self->{pbot}->{commands}->set_meta('mod', 'help', 'Provides restricted moderation abilities to voiced users. They can kick/ban/etc only users that are not admins, whitelisted, voiced or opped.'); $self->{pbot}->{commands}->set_meta('mod', 'help', 'Provides restricted moderation abilities to voiced users. They can kick/ban/etc only users that are not admins, whitelisted, voiced or opped.');
$self->{pbot}->{capabilities}->add('chanmod', 'can-mod', 1);
$self->{pbot}->{capabilities}->add('chanmod', 'can-voice', 1);
$self->{pbot}->{capabilities}->add('chanmod', 'can-devoice', 1);
$self->{commands} = { $self->{commands} = {
'help' => { subref => sub { $self->help(@_) }, help => "Provides help about this command. Usage: mod help <mod command>; see also: mod help list" }, 'help' => { subref => sub { $self->help(@_) }, help => "Provides help about this command. Usage: mod help <mod command>; see also: mod help list" },
'list' => { subref => sub { $self->list(@_) }, help => "Lists available mod commands. Usage: mod list" }, 'list' => { subref => sub { $self->list(@_) }, help => "Lists available mod commands. Usage: mod list" },
@ -47,6 +51,7 @@ sub initialize {
sub unload { sub unload {
my ($self) = @_; my ($self) = @_;
$self->{pbot}->{commands}->unregister('mod'); $self->{pbot}->{commands}->unregister('mod');
$self->{pbot}->{capabilities}->remove('chanmod');
} }
sub help { sub help {