2010-03-22 08:33:44 +01:00
# File: Commands.pm
2020-02-02 07:17:20 +01:00
#
2020-02-03 18:50:38 +01:00
# Purpose: Registers commands. Invokes commands with user capability
# validation.
2010-03-22 08:33:44 +01:00
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
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' ;
2010-03-22 08:33:44 +01:00
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/ ;
2010-03-22 08:33:44 +01:00
2023-04-14 06:04:12 +02:00
sub initialize ($self, %conf) {
2021-07-31 04:01:24 +02:00
# registered commands hashtable
2021-07-24 03:26:07 +02:00
$ self - > { commands } = { } ;
2010-03-22 08:33:44 +01:00
2021-06-09 00:52:47 +02:00
# 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 ;
2020-05-04 22:21:35 +02:00
}
2021-07-31 04:50:30 +02:00
# load commands in PBot::Core::Commands directory
2023-04-14 06:04:12 +02:00
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' ) ;
2020-05-04 22:21:35 +02:00
}
2021-07-31 04:50:30 +02:00
# named-parameters interface to register()
2023-04-14 06:04:12 +02:00
sub add ($self, %args) {
2021-07-31 21:04:50 +02:00
# expected parameters
my @ valid = qw( subref name requires_cap help ) ;
2021-07-31 04:01:24 +02:00
2021-07-31 21:04:50 +02:00
# check for unexpected parameters
my @ invalid ;
2021-07-31 04:01:24 +02:00
foreach my $ key ( keys % args ) {
2021-07-31 21:04:50 +02:00
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" ;
2021-07-31 04:01:24 +02:00
}
2021-07-31 21:04:50 +02:00
# register command
$ self - > register (
$ args { subref } ,
$ args { name } ,
$ args { requires_cap } ,
$ args { help } ,
) ;
2021-07-31 04:01:24 +02:00
}
2021-07-31 04:50:30 +02:00
# alias to unregister() for consistency
2023-04-14 06:04:12 +02:00
sub remove ($self, @args) {
$ self - > unregister ( @ args ) ;
2021-07-31 04:01:24 +02:00
}
2023-04-14 06:04:12 +02:00
sub register ($self, $subref, $name, $requires_cap = 0, $help = '') {
2021-06-09 00:52:47 +02:00
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-06-09 00:52:47 +02:00
2021-07-24 03:26:07 +02:00
# register command
$ self - > { commands } - > { $ name } = {
2021-07-31 04:01:24 +02:00
requires_cap = > $ requires_cap ,
2021-07-24 03:26:07 +02:00
subref = > $ subref ,
} ;
2020-02-15 23:38:32 +01:00
2021-06-09 00:52:47 +02:00
# update command metadata
2021-06-05 22:20:03 +02:00
if ( not $ self - > { metadata } - > exists ( $ name ) ) {
2021-07-24 03:26:07 +02:00
# create new metadata
2021-07-31 04:01:24 +02:00
$ self - > { metadata } - > add ( $ name , { requires_cap = > $ requires_cap , help = > $ help } , 1 ) ;
2021-06-05 22:20:03 +02:00
} else {
2021-07-31 04:01:24 +02:00
# 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.
2021-06-05 22:20:03 +02:00
if ( not defined $ self - > get_meta ( $ name , 'requires_cap' ) ) {
$ self - > { metadata } - > set ( $ name , 'requires_cap' , $ requires_cap , 1 ) ;
}
2021-07-31 04:01:24 +02:00
# 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 07:13:08 +01:00
}
2020-01-19 06:49:55 +01:00
2021-06-09 00:52:47 +02:00
# add can-<command> capability to PBot capabilities if required
if ( $ requires_cap ) {
$ self - > { pbot } - > { capabilities } - > add ( "can-$name" , undef , 1 ) ;
}
2010-03-22 08:33:44 +01:00
}
2023-04-14 06:04:12 +02:00
sub unregister ($self, $name) {
2020-02-15 23:38:32 +01:00
Carp:: croak ( "Missing name parameter to Commands::unregister" ) if not defined $ name ;
2021-07-24 03:37:45 +02:00
delete $ self - > { commands } - > { lc $ name } ;
2010-03-22 08:33:44 +01:00
}
2023-04-14 06:04:12 +02:00
sub exists ($self, $name) {
2021-07-24 03:26:07 +02:00
return exists $ self - > { commands } - > { lc $ name } ;
2015-04-04 00:33:19 +02:00
}
2023-04-14 06:04:12 +02:00
sub set_meta ($self, $command, $key, $value, $save = 0) {
2020-05-04 22:21:35 +02:00
return undef if not $ self - > { metadata } - > exists ( $ command ) ;
$ self - > { metadata } - > set ( $ command , $ key , $ value , ! $ save ) ;
return 1 ;
}
2023-04-14 06:04:12 +02:00
sub get_meta ($self, $command, $key) {
2020-05-04 22:21:35 +02:00
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
2021-07-28 06:27:03 +02:00
# see also PBot::Core::Factoids::Interpreter for factoid commands
2023-04-14 06:04:12 +02:00
sub interpreter ($self, $context) {
2021-06-09 00:52:47 +02: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 ;
2024-10-22 18:50:10 +02:00
$ Data:: Dumper:: Sortkeys = sub { [ sort grep { not /(?:cmdlist|arglist)/ } keys %$ context ] } ;
2022-07-06 08:12:12 +02:00
$ 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 ) ;
2024-10-22 18:50:10 +02:00
$ Data:: Dumper:: Sortkeys = 1 ;
2020-02-15 23:38:32 +01:00
}
2021-06-11 23:58:16 +02: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 ;
2021-06-11 23:58:16 +02: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
2021-06-05 22:20:03 +02:00
$ 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 ;
2021-06-11 23:58:16 +02:00
# 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
2021-06-11 23:58:16 +02:00
# check for a capability override
2020-02-15 23:38:32 +01:00
my $ cap_override ;
2021-06-11 23:58:16 +02:00
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-06-11 23:58:16 +02:00
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-06-11 23:58:16 +02:00
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." ;
2020-02-10 23:42:29 +01:00
}
2020-02-03 18:50:38 +01:00
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." ;
2020-05-22 04:57:11 +02:00
}
2021-07-24 03:26:07 +02:00
}
2024-11-06 01:48:59 +01:00
if ( $ context - > { factoid } && ! $ context - > { locked } ) {
return "/msg $context->{nick} The $keyword command requires the can-$keyword capability and cannot be invoked from an unlocked factoid." ;
}
2021-07-24 03:26:07 +02:00
}
2020-03-18 07:56:44 +01:00
2024-11-04 09:25:36 +01:00
if ( $ self - > get_meta ( $ keyword , 'condense-whitespace' ) ) {
$ context - > { 'condense-whitespace' } = 1 ;
2021-07-24 03:26:07 +02:00
}
2021-06-11 23:58:16 +02:00
2021-07-28 06:27:03 +02:00
# tell PBot::Core::Interpreter to prepend caller's nick to output
if ( $ self - > get_meta ( $ keyword , 'add_nick' ) ) {
$ context - > { add_nick } = 1 ;
}
2021-07-28 08:25:38 +02:00
unless ( $ context - > { 'dont-replace-pronouns' } ) {
2021-07-27 06:39:44 +02:00
$ 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-06-11 23:58:16 +02:00
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-06-11 23:58:16 +02:00
2021-07-24 03:26:07 +02:00
# otherwise set timeout to default value
$ timeout // = $ self - > { pbot } - > { registry } - > get_value ( 'processmanager' , 'default_timeout' ) ;
2021-06-11 23:58:16 +02:00
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-06-11 23:58:16 +02:00
2021-07-24 03:26:07 +02:00
# return no output since it will be handled by process manager
2024-11-03 01:53:37 +01:00
$ 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-06-11 23:58:16 +02:00
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-06-11 23:58:16 +02:00
2021-07-24 03:26:07 +02:00
# return command output
return $ result ;
2010-03-22 08:33:44 +01:00
}
}
1 ;