3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-11-28 23:09:39 +01:00
pbot/lib/PBot/Core/Commands.pm

246 lines
8.5 KiB
Perl
Raw Normal View History

# File: Commands.pm
#
# Purpose: Registers commands. Invokes commands with user capability
# validation.
2023-02-21 06:31:52 +01:00
# SPDX-FileCopyrightText: 2010-2023 Pragmatic Software <pragma78@gmail.com>
2021-07-11 00:00:22 +02:00
# SPDX-License-Identifier: MIT
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
2021-07-21 07:44:51 +02:00
package PBot::Core::Commands;
2021-07-24 03:26:07 +02:00
use parent 'PBot::Core::Class';
2021-06-19 06:23:34 +02:00
use PBot::Imports;
2021-07-24 04:22:25 +02:00
use PBot::Core::Utils::LoadModules qw/load_modules/;
sub initialize($self, %conf) {
# registered commands hashtable
2021-07-24 03:26:07 +02:00
$self->{commands} = {};
# command metadata stored as a HashObject
2021-07-24 04:22:25 +02:00
$self->{metadata} = PBot::Core::Storage::HashObject->new(
2021-07-24 03:26:07 +02:00
pbot => $self->{pbot},
name => 'Command metadata',
filename => $conf{filename},
);
2020-02-15 23:38:32 +01:00
$self->{metadata}->load;
}
2021-07-31 04:50:30 +02:00
# load commands in PBot::Core::Commands directory
sub load_commands($self) {
2021-07-24 03:26:07 +02:00
$self->{pbot}->{logger}->log("Loading commands:\n");
2021-07-22 14:36:46 +02:00
load_modules($self, 'PBot::Core::Commands');
}
2021-07-31 04:50:30 +02:00
# named-parameters interface to register()
sub add($self, %args) {
# expected parameters
my @valid = qw(subref name requires_cap help);
# check for unexpected parameters
my @invalid;
foreach my $key (keys %args) {
if (not grep { $_ eq $key } @valid) {
push @invalid, $key;
}
}
# die if any unexpected parameters were passed
if (@invalid) {
$self->{pbot}->{logger}->log("Commands: error: invalid arguments provided to add(): @invalid\n");
die "Commands: error: invalid arguments provided to add(): @invalid";
}
# register command
$self->register(
$args{subref},
$args{name},
$args{requires_cap},
$args{help},
);
}
2021-07-31 04:50:30 +02:00
# alias to unregister() for consistency
sub remove($self, @args) {
$self->unregister(@args);
}
sub register($self, $subref, $name, $requires_cap = 0, $help = '') {
if (not defined $subref or not defined $name) {
Carp::croak("Missing parameters to Commands::register");
}
2021-07-24 03:26:07 +02:00
$name = lc $name;
if (exists $self->{commands}->{$name}) {
$self->{pbot}->{logger}->log("Commands: warning: overwriting existing command $name\n");
}
2021-07-24 03:26:07 +02:00
# register command
$self->{commands}->{$name} = {
requires_cap => $requires_cap,
2021-07-24 03:26:07 +02:00
subref => $subref,
};
2020-02-15 23:38:32 +01:00
# update command metadata
if (not $self->{metadata}->exists($name)) {
2021-07-24 03:26:07 +02:00
# create new metadata
$self->{metadata}->add($name, { requires_cap => $requires_cap, help => $help }, 1);
} else {
# metadata already exists
# we update data unless it's already set so the metadata file can be edited manually.
# update requires_cap unless it's already set.
if (not defined $self->get_meta($name, 'requires_cap')) {
$self->{metadata}->set($name, 'requires_cap', $requires_cap, 1);
}
# update help text unless it's already set.
if (not $self->get_meta($name, 'help')) {
$self->{metadata}->set($name, 'help', $help, 1);
}
}
2020-01-19 06:49:55 +01:00
# add can-<command> capability to PBot capabilities if required
if ($requires_cap) {
$self->{pbot}->{capabilities}->add("can-$name", undef, 1);
}
}
sub unregister($self, $name) {
2020-02-15 23:38:32 +01:00
Carp::croak("Missing name parameter to Commands::unregister") if not defined $name;
delete $self->{commands}->{lc $name};
}
sub exists($self, $name) {
2021-07-24 03:26:07 +02:00
return exists $self->{commands}->{lc $name};
}
sub set_meta($self, $command, $key, $value, $save = 0) {
return undef if not $self->{metadata}->exists($command);
$self->{metadata}->set($command, $key, $value, !$save);
return 1;
}
sub get_meta($self, $command, $key) {
return $self->{metadata}->get_data($command, $key);
}
2021-07-21 07:44:51 +02:00
# main entry point for PBot::Core::Interpreter to interpret a registered bot command
# see also PBot::Core::Factoids::Interpreter for factoid commands
sub interpreter($self, $context) {
# debug flag to trace $context location and contents
2020-02-15 23:38:32 +01:00
if ($self->{pbot}->{registry}->get_value('general', 'debugcontext')) {
use Data::Dumper;
$Data::Dumper::Sortkeys = sub { [sort grep { not /(?:cmdlist|arglist)/ } keys %$context] };
$Data::Dumper::Indent = 2;
2020-02-15 23:38:32 +01:00
$self->{pbot}->{logger}->log("Commands::interpreter\n");
2020-05-02 05:59:51 +02:00
$self->{pbot}->{logger}->log(Dumper $context);
$Data::Dumper::Sortkeys = 1;
2020-02-15 23:38:32 +01:00
}
# some convenient aliases
2020-05-02 05:59:51 +02:00
my $keyword = lc $context->{keyword};
my $from = $context->{from};
2020-02-15 23:38:32 +01:00
2021-07-24 03:26:07 +02:00
# alias to the command
my $command = $self->{commands}->{$keyword};
# bail early if the command doesn't exist
return undef if not defined $command;
# set the channel the command is in reference to
2020-05-02 05:59:51 +02:00
my ($cmd_channel) = $context->{arguments} =~ m/\B(#[^ ]+)/; # assume command is invoked in regards to first channel-like argument
$cmd_channel = $from if not defined $cmd_channel; # otherwise command is invoked in regards to the channel the user is in
$context->{channel} = $cmd_channel;
# get the user's bot account
my $user = $self->{pbot}->{users}->find_user($cmd_channel, $context->{hostmask});
2020-02-15 23:38:32 +01:00
# check for a capability override
2020-02-15 23:38:32 +01:00
my $cap_override;
2020-05-02 05:59:51 +02:00
if (exists $context->{'cap-override'}) {
$self->{pbot}->{logger}->log("Override cap to $context->{'cap-override'}\n");
$cap_override = $context->{'cap-override'};
2020-02-15 23:38:32 +01:00
}
2021-07-24 03:26:07 +02:00
# does this command require capabilities
my $requires_cap = $self->get_meta($keyword, 'requires_cap') // $command->{requires_cap};
2021-07-24 03:26:07 +02:00
# validate can-command capability
if ($requires_cap) {
if (defined $cap_override) {
if (not $self->{pbot}->{capabilities}->has($cap_override, "can-$keyword")) {
return "/msg $context->{nick} The $keyword command requires the can-$keyword capability, which cap-override $cap_override does not have.";
}
} else {
if (not defined $user) {
my ($found_chan, $found_mask) = $self->{pbot}->{users}->find_user_account($cmd_channel, $context->{hostmask}, 1);
2021-07-24 03:26:07 +02:00
if (not defined $found_chan) {
return "/msg $context->{nick} You must have a user account to use $keyword. You may use the `my` command to create a personal user account. See `help my`.";
2020-02-15 23:38:32 +01:00
} else {
2021-07-24 03:26:07 +02:00
return "/msg $context->{nick} You must have a user account in $cmd_channel to use $keyword. (You have an account in $found_chan.)";
2020-02-15 23:38:32 +01:00
}
2021-07-24 03:26:07 +02:00
} elsif (not $user->{loggedin}) {
return "/msg $context->{nick} You must be logged into your user account to use $keyword.";
}
2021-07-24 03:26:07 +02:00
if (not $self->{pbot}->{capabilities}->userhas($user, "can-$keyword")) {
return "/msg $context->{nick} The $keyword command requires the can-$keyword capability, which your user account does not have.";
}
2021-07-24 03:26:07 +02:00
}
if ($context->{factoid} && $context->{locked} != 1) {
return "/msg $context->{nick} The $keyword command requires the can-$keyword capability and cannot be invoked from a factoid that does not have `locked` set to 1.";
}
2021-07-24 03:26:07 +02:00
}
if ($self->get_meta($keyword, 'condense-whitespace')) {
$context->{'condense-whitespace'} = 1;
2021-07-24 03:26:07 +02:00
}
# tell PBot::Core::Interpreter to prepend caller's nick to output
if ($self->get_meta($keyword, 'add_nick')) {
$context->{add_nick} = 1;
}
unless ($context->{'dont-replace-pronouns'}) {
$context->{arguments} = $self->{pbot}->{factoids}->{variables}->expand_factoid_vars($context, $context->{arguments});
2021-07-24 03:26:07 +02:00
$context->{arglist} = $self->{pbot}->{interpreter}->make_args($context->{arguments});
}
2021-07-24 03:26:07 +02:00
# execute this command as a backgrounded process?
if ($self->get_meta($keyword, 'background-process')) {
# set timeout to command metadata value
my $timeout = $self->get_meta($keyword, 'process-timeout');
2021-07-24 03:26:07 +02:00
# otherwise set timeout to default value
$timeout //= $self->{pbot}->{registry}->get_value('processmanager', 'default_timeout');
2021-07-24 03:26:07 +02:00
# execute command in background
$self->{pbot}->{process_manager}->execute_process(
$context,
sub { $context->{result} = $command->{subref}->($context) },
$timeout,
);
2021-07-24 03:26:07 +02:00
# return no output since it will be handled by process manager
$context->{'skip-handle-result'} = 1;
2021-07-24 03:26:07 +02:00
return '';
} else {
# execute this command normally
my $result = $command->{subref}->($context);
2021-07-24 03:26:07 +02:00
# disregard undesired command output if command is embedded
return undef if $context->{embedded} and $result =~ m/(?:usage:|no results)/i;
2021-07-24 03:26:07 +02:00
# return command output
return $result;
}
}
1;