2010-03-22 07:33:44 +00:00
# File: Commands.pm
2020-02-01 22:17:20 -08:00
#
2020-02-03 09:50:38 -08:00
# Purpose: Registers commands. Invokes commands with user capability
# validation.
2010-03-22 07:33:44 +00:00
2021-07-10 15:00:22 -07:00
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
2017-03-05 21:33:31 +00:00
2021-07-20 22:44:51 -07:00
package PBot::Core::Commands ;
2021-07-23 18:26:07 -07:00
use parent 'PBot::Core::Class' ;
2010-03-22 07:33:44 +00:00
2021-06-18 21:23:34 -07:00
use PBot::Imports ;
2019-07-10 18:40:53 -07:00
2021-07-23 19:22:25 -07:00
use PBot::Core::Utils::LoadModules qw/load_modules/ ;
2010-03-22 07:33:44 +00:00
sub initialize {
2020-02-15 14:38:32 -08:00
my ( $ self , % conf ) = @ _ ;
2021-06-08 15:52:47 -07:00
2021-07-23 18:26:07 -07:00
# registered commands hash table
$ self - > { commands } = { } ;
2010-03-22 07:33:44 +00:00
2021-06-08 15:52:47 -07:00
# command metadata stored as a HashObject
2021-07-23 19:22:25 -07:00
$ self - > { metadata } = PBot::Core::Storage::HashObject - > new (
2021-07-23 18:26:07 -07:00
pbot = > $ self - > { pbot } ,
name = > 'Command metadata' ,
filename = > $ conf { filename } ,
) ;
2020-02-15 14:38:32 -08:00
$ self - > { metadata } - > load ;
2020-05-04 13:21:35 -07:00
}
2021-07-23 18:26:07 -07:00
sub load_commands {
2021-07-20 21:38:07 -07:00
my ( $ self ) = @ _ ;
2021-06-08 15:52:47 -07:00
2021-07-23 18:26:07 -07:00
# load commands in Commands directory
$ self - > { pbot } - > { logger } - > log ( "Loading commands:\n" ) ;
2021-07-22 05:36:46 -07:00
load_modules ( $ self , 'PBot::Core::Commands' ) ;
2020-05-04 13:21:35 -07:00
}
2010-03-22 07:33:44 +00:00
sub register {
2020-02-15 14:38:32 -08:00
my ( $ self , $ subref , $ name , $ requires_cap ) = @ _ ;
2021-06-08 15:52:47 -07:00
if ( not defined $ subref or not defined $ name ) {
Carp:: croak ( "Missing parameters to Commands::register" ) ;
}
2021-07-23 18:26:07 -07:00
$ name = lc $ name ;
if ( exists $ self - > { commands } - > { $ name } ) {
$ self - > { pbot } - > { logger } - > log ( "Commands: warning: overwriting existing command $name\n" ) ;
}
2021-06-08 15:52:47 -07:00
2021-07-23 18:26:07 -07:00
# register command
$ self - > { commands } - > { $ name } = {
requires_cap = > $ requires_cap // 0 ,
subref = > $ subref ,
} ;
2020-02-15 14:38:32 -08:00
2021-06-08 15:52:47 -07:00
# update command metadata
2021-06-05 13:20:03 -07:00
if ( not $ self - > { metadata } - > exists ( $ name ) ) {
2021-07-23 18:26:07 -07:00
# create new metadata
2021-06-08 15:52:47 -07:00
$ self - > { metadata } - > add ( $ name , { requires_cap = > $ requires_cap , help = > '' } , 1 ) ;
2021-06-05 13:20:03 -07:00
} else {
2021-06-08 15:52:47 -07:00
# metadata already exists, just update requires_cap unless it's already set.
2021-06-05 13:20:03 -07:00
if ( not defined $ self - > get_meta ( $ name , 'requires_cap' ) ) {
$ self - > { metadata } - > set ( $ name , 'requires_cap' , $ requires_cap , 1 ) ;
}
2020-01-18 22:13:08 -08:00
}
2020-01-18 21:49:55 -08:00
2021-06-08 15:52:47 -07:00
# add can-<command> capability to PBot capabilities if required
if ( $ requires_cap ) {
$ self - > { pbot } - > { capabilities } - > add ( "can-$name" , undef , 1 ) ;
}
2010-03-22 07:33:44 +00:00
}
2016-02-13 18:38:43 -08:00
sub unregister {
2020-02-15 14:38:32 -08:00
my ( $ self , $ name ) = @ _ ;
Carp:: croak ( "Missing name parameter to Commands::unregister" ) if not defined $ name ;
2021-07-23 18:37:45 -07:00
delete $ self - > { commands } - > { lc $ name } ;
2010-03-22 07:33:44 +00:00
}
2015-04-03 15:33:19 -07:00
sub exists {
2021-07-23 18:26:07 -07:00
my ( $ self , $ name ) = @ _ ;
return exists $ self - > { commands } - > { lc $ name } ;
2015-04-03 15:33:19 -07:00
}
2020-05-04 13:21:35 -07:00
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-20 22:44:51 -07:00
# main entry point for PBot::Core::Interpreter to interpret a registered bot command
# see also PBot::Core::Factoids::interpreter() for factoid commands
2010-03-22 07:33:44 +00:00
sub interpreter {
2020-05-01 20:59:51 -07:00
my ( $ self , $ context ) = @ _ ;
2020-02-15 14:38:32 -08:00
2021-06-08 15:52:47 -07:00
# debug flag to trace $context location and contents
2020-02-15 14:38:32 -08: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-01 20:59:51 -07:00
$ self - > { pbot } - > { logger } - > log ( Dumper $ context ) ;
2020-02-15 14:38:32 -08:00
}
2021-06-11 14:58:16 -07:00
# some convenient aliases
2020-05-01 20:59:51 -07:00
my $ keyword = lc $ context - > { keyword } ;
my $ from = $ context - > { from } ;
2020-02-15 14:38:32 -08:00
2021-07-23 18:26:07 -07: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 14:58:16 -07:00
# set the channel the command is in reference to
2020-05-01 20:59:51 -07:00
my ( $ cmd_channel ) = $ context - > { arguments } =~ m/\B(#[^ ]+)/ ; # assume command is invoked in regards to first channel-like argument
2021-06-05 13:20:03 -07: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 14:58:16 -07:00
# get the user's bot account
my $ user = $ self - > { pbot } - > { users } - > find_user ( $ cmd_channel , $ context - > { hostmask } ) ;
2020-02-15 14:38:32 -08:00
2021-06-11 14:58:16 -07:00
# check for a capability override
2020-02-15 14:38:32 -08:00
my $ cap_override ;
2021-06-11 14:58:16 -07:00
2020-05-01 20:59:51 -07: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 14:38:32 -08:00
}
2021-07-23 18:26:07 -07:00
# does this command require capabilities
my $ requires_cap = $ self - > get_meta ( $ keyword , 'requires_cap' ) // $ command - > { requires_cap } ;
2021-06-11 14:58:16 -07:00
2021-07-23 18:26:07 -07: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 14:58:16 -07:00
2021-07-23 18:26:07 -07: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 14:38:32 -08:00
} else {
2021-07-23 18:26:07 -07: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 14:38:32 -08:00
}
2021-07-23 18:26:07 -07:00
} elsif ( not $ user - > { loggedin } ) {
return "/msg $context->{nick} You must be logged into your user account to use $keyword." ;
2020-02-10 14:42:29 -08:00
}
2020-02-03 09:50:38 -08:00
2021-07-23 18:26:07 -07: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-21 19:57:11 -07:00
}
2021-07-23 18:26:07 -07:00
}
}
2020-03-17 23:56:44 -07:00
2021-07-23 18:26:07 -07:00
if ( $ self - > get_meta ( $ keyword , 'preserve_whitespace' ) ) {
$ context - > { preserve_whitespace } = 1 ;
}
2021-06-11 14:58:16 -07:00
2021-07-23 18:26:07 -07:00
unless ( $ self - > get_meta ( $ keyword , 'dont-replace-pronouns' ) ) {
2021-07-26 21:39:44 -07:00
$ context - > { arguments } = $ self - > { pbot } - > { factoids } - > { variables } - > expand_factoid_vars ( $ context , $ context - > { arguments } ) ;
2021-07-23 18:26:07 -07:00
$ context - > { arglist } = $ self - > { pbot } - > { interpreter } - > make_args ( $ context - > { arguments } ) ;
}
2021-06-11 14:58:16 -07:00
2021-07-23 18:26:07 -07: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 14:58:16 -07:00
2021-07-23 18:26:07 -07:00
# otherwise set timeout to default value
$ timeout // = $ self - > { pbot } - > { registry } - > get_value ( 'processmanager' , 'default_timeout' ) ;
2021-06-11 14:58:16 -07:00
2021-07-23 18:26:07 -07:00
# execute command in background
$ self - > { pbot } - > { process_manager } - > execute_process (
$ context ,
sub { $ context - > { result } = $ command - > { subref } - > ( $ context ) } ,
$ timeout ,
) ;
2021-06-11 14:58:16 -07:00
2021-07-23 18:26:07 -07:00
# return no output since it will be handled by process manager
return '' ;
} else {
# execute this command normally
my $ result = $ command - > { subref } - > ( $ context ) ;
2021-06-11 14:58:16 -07:00
2021-07-23 18:26:07 -07:00
# disregard undesired command output if command is embedded
return undef if $ context - > { embedded } and $ result =~ m/(?:usage:|no results)/i ;
2021-06-11 14:58:16 -07:00
2021-07-23 18:26:07 -07:00
# return command output
return $ result ;
2010-03-22 07:33:44 +00:00
}
}
1 ;