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

208 lines
7.6 KiB
Perl
Raw Normal View History

# File: Commands.pm
#
# Purpose: Registers commands. Invokes commands with user capability
# validation.
2021-07-11 00:00:22 +02:00
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# 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;
use parent 'PBot::Core::Class', 'PBot::Core::Registerable';
2021-06-19 06:23:34 +02:00
use PBot::Imports;
2019-07-11 03:40:53 +02:00
use PBot::Utils::LoadPackages qw/load_packages/;
sub initialize {
2020-02-15 23:38:32 +01:00
my ($self, %conf) = @_;
2021-07-21 07:44:51 +02:00
# PBot::Core::Commands can register subrefs
$self->PBot::Core::Registerable::initialize(%conf);
# command metadata stored as a HashObject
$self->{metadata} = PBot::Storage::HashObject->new(pbot => $self->{pbot}, name => 'Command metadata', filename => $conf{filename});
2020-02-15 23:38:32 +01:00
$self->{metadata}->load;
}
sub register_commands {
my ($self) = @_;
# register commands in Commands directory
$self->{pbot}->{logger}->log("Registering commands:\n");
load_packages($self, 'Commands');
}
sub register {
2020-02-15 23:38:32 +01:00
my ($self, $subref, $name, $requires_cap) = @_;
if (not defined $subref or not defined $name) {
Carp::croak("Missing parameters to Commands::register");
}
# register subref
2021-07-21 07:44:51 +02:00
my $command = $self->PBot::Core::Registerable::register($subref);
# update internal metadata
$command->{name} = lc $name;
$command->{requires_cap} = $requires_cap // 0;
2020-02-15 23:38:32 +01:00
# update command metadata
if (not $self->{metadata}->exists($name)) {
$self->{metadata}->add($name, { requires_cap => $requires_cap, help => '' }, 1);
} else {
# metadata already exists, just 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);
}
}
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);
}
return $command;
}
sub unregister {
2020-02-15 23:38:32 +01:00
my ($self, $name) = @_;
Carp::croak("Missing name parameter to Commands::unregister") if not defined $name;
$name = lc $name;
@{$self->{handlers}} = grep { $_->{name} ne $name } @{$self->{handlers}};
}
sub exists {
2020-02-15 23:38:32 +01:00
my ($self, $keyword) = @_;
$keyword = lc $keyword;
foreach my $command (@{$self->{handlers}}) { return 1 if $command->{name} eq $keyword; }
2020-02-15 23:38:32 +01:00
return 0;
}
sub set_meta {
my ($self, $command, $key, $value, $save) = @_;
return undef if not $self->{metadata}->exists($command);
$self->{metadata}->set($command, $key, $value, !$save);
return 1;
}
sub get_meta {
my ($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 {
2020-05-02 05:59:51 +02:00
my ($self, $context) = @_;
2020-02-15 23:38:32 +01:00
# 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 = 1;
$self->{pbot}->{logger}->log("Commands::interpreter\n");
2020-05-02 05:59:51 +02:00
$self->{pbot}->{logger}->log(Dumper $context);
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
# 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
}
# go through all commands
# TODO: maybe use a hash lookup
foreach my $command (@{$self->{handlers}}) {
# is this the command
if ($command->{name} eq $keyword) {
# does this command require capabilities
my $requires_cap = $self->get_meta($keyword, 'requires_cap') // $command->{requires_cap};
2020-02-15 23:38:32 +01:00
if ($requires_cap) {
if (defined $cap_override) {
if (not $self->{pbot}->{capabilities}->has($cap_override, "can-$keyword")) {
2020-05-02 05:59:51 +02:00
return "/msg $context->{nick} The $keyword command requires the can-$keyword capability, which cap-override $cap_override does not have.";
2020-02-15 23:38:32 +01:00
}
} else {
if (not defined $user) {
my ($found_chan, $found_mask) = $self->{pbot}->{users}->find_user_account($cmd_channel, $context->{hostmask}, 1);
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`.";
} else {
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
} elsif (not $user->{loggedin}) {
2020-05-02 05:59:51 +02:00
return "/msg $context->{nick} You must be logged into your user account to use $keyword.";
2020-02-15 23:38:32 +01:00
}
if (not $self->{pbot}->{capabilities}->userhas($user, "can-$keyword")) {
2020-05-02 05:59:51 +02:00
return "/msg $context->{nick} The $keyword command requires the can-$keyword capability, which your user account does not have.";
2020-02-15 23:38:32 +01:00
}
}
}
if ($self->get_meta($keyword, 'preserve_whitespace')) {
$context->{preserve_whitespace} = 1;
}
unless ($self->get_meta($keyword, 'dont-replace-pronouns')) {
$context->{arguments} = $self->{pbot}->{factoids}->expand_factoid_vars($context, $context->{arguments});
$context->{arglist} = $self->{pbot}->{interpreter}->make_args($context->{arguments});
}
# $self->{pbot}->{logger}->log("Disabling nickprefix\n");
#$context->{nickprefix_disabled} = 1;
2020-02-15 23:38:32 +01:00
if ($self->get_meta($keyword, 'background-process')) {
# execute this command as a backgrounded process
# set timeout to command metadata value
my $timeout = $self->get_meta($keyword, 'process-timeout');
# otherwise set timeout to default value
$timeout //= $self->{pbot}->{registry}->get_value('processmanager', 'default_timeout');
# execute command in background
2020-02-15 23:38:32 +01:00
$self->{pbot}->{process_manager}->execute_process(
2020-05-02 05:59:51 +02:00
$context,
sub { $context->{result} = $command->{subref}->($context) },
$timeout,
2020-02-15 23:38:32 +01:00
);
# return no output since it will be handled by process manager
return '';
2020-02-15 23:38:32 +01:00
} else {
# execute this command normally
my $result = $command->{subref}->($context);
# disregard undesired command output if command is embedded
2020-05-02 05:59:51 +02:00
return undef if $context->{referenced} and $result =~ m/(?:usage:|no results)/i;
# return command output
2020-02-15 23:38:32 +01:00
return $result;
}
}
}
2020-02-15 23:38:32 +01:00
return undef;
}
1;