2020-02-14 18:52:41 -08:00
# File: Modules.pm
2021-06-18 21:23:34 -07:00
#
2021-06-06 19:12:14 -07:00
# Purpose: Modules are command-line programs and scripts that can be loaded
# via PBot factoids. Command arguments are passed as command-line arguments.
# The standard output from the script is returned as the bot command result.
# The standard error output is stored in a file named <module>-stderr in the
# modules/ directory.
2021-07-10 15:00:22 -07:00
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
2020-02-14 18:52:41 -08:00
2021-07-20 22:44:51 -07:00
package PBot::Core::Modules ;
use parent 'PBot::Core::Class' ;
2020-02-14 18:52:41 -08:00
2021-06-18 21:23:34 -07:00
use PBot::Imports ;
2020-02-14 18:52:41 -08:00
use IPC::Run qw/run timeout/ ;
use Encode ;
sub initialize {
2021-07-23 07:24:30 -07:00
# nothing to do here
2020-02-14 18:52:41 -08:00
}
sub execute_module {
2020-05-01 20:59:51 -07:00
my ( $ self , $ context ) = @ _ ;
2020-02-15 14:38:32 -08:00
my $ text ;
if ( $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'debugcontext' ) ) {
use Data::Dumper ;
$ Data:: Dumper:: Sortkeys = 1 ;
$ self - > { pbot } - > { logger } - > log ( "execute_module\n" ) ;
2020-05-01 20:59:51 -07:00
$ self - > { pbot } - > { logger } - > log ( Dumper $ context ) ;
2020-02-15 14:38:32 -08:00
}
2020-02-14 18:52:41 -08:00
2020-05-01 20:59:51 -07:00
$ self - > { pbot } - > { process_manager } - > execute_process ( $ context , sub { $ self - > launch_module ( @ _ ) } ) ;
2020-02-14 18:52:41 -08:00
}
sub launch_module {
2020-05-01 20:59:51 -07:00
my ( $ self , $ context ) = @ _ ;
2021-06-06 19:12:14 -07:00
$ context - > { arguments } // = '' ;
2021-07-26 21:39:44 -07:00
my @ factoids = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ context - > { from } , $ context - > { keyword } , exact_channel = > 2 , exact_trigger = > 2 ) ;
2021-06-06 19:12:14 -07:00
2020-02-15 14:38:32 -08:00
if ( not @ factoids or not $ factoids [ 0 ] ) {
2020-05-01 20:59:51 -07:00
$ context - > { checkflood } = 1 ;
$ self - > { pbot } - > { interpreter } - > handle_result ( $ context , "/msg $context->{nick} Failed to find module for '$context->{keyword}' in channel $context->{from}\n" ) ;
2020-02-15 14:38:32 -08:00
return ;
2020-02-14 18:52:41 -08:00
}
2020-02-15 14:38:32 -08:00
my ( $ channel , $ trigger ) = ( $ factoids [ 0 ] - > [ 0 ] , $ factoids [ 0 ] - > [ 1 ] ) ;
2021-06-06 19:12:14 -07:00
2020-05-01 20:59:51 -07:00
$ context - > { channel } = $ channel ;
$ context - > { keyword } = $ trigger ;
$ context - > { trigger } = $ trigger ;
2020-02-15 14:38:32 -08:00
2021-07-26 21:39:44 -07:00
my $ module = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , 'action' ) ;
2021-06-06 19:12:14 -07:00
2020-05-21 19:23:30 -07:00
$ self - > { pbot } - > { logger } - > log (
2021-06-06 19:12:14 -07:00
'(' . ( defined $ context - > { from } ? $ context - > { from } : "(undef)" ) . '): '
. "$context->{hostmask}: Executing module [$context->{command}] $module $context->{arguments}\n"
2020-05-21 19:23:30 -07:00
) ;
2021-07-26 21:39:44 -07:00
$ context - > { arguments } = $ self - > { pbot } - > { factoids } - > { variables } - > expand_factoid_vars ( $ context , $ context - > { arguments } ) ;
2020-02-15 14:38:32 -08:00
my $ module_dir = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'module_dir' ) ;
2021-06-06 19:12:14 -07:00
2020-02-15 14:38:32 -08:00
if ( not chdir $ module_dir ) {
$ self - > { pbot } - > { logger } - > log ( "Could not chdir to '$module_dir': $!\n" ) ;
Carp:: croak ( "Could not chdir to '$module_dir': $!" ) ;
2020-02-14 18:52:41 -08:00
}
2020-02-15 14:38:32 -08:00
2021-07-26 21:39:44 -07:00
if ( $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > exists ( $ channel , $ trigger , 'workdir' ) ) {
chdir $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , 'workdir' ) ;
2020-02-15 14:38:32 -08:00
}
# FIXME -- add check to ensure $module exists
2021-06-06 19:12:14 -07:00
2020-02-15 14:38:32 -08:00
my ( $ exitval , $ stdout , $ stderr ) = eval {
2020-05-01 20:59:51 -07:00
my $ args = $ context - > { arguments } ;
2021-06-06 19:12:14 -07:00
if ( not $ context - > { args_utf8 } ) {
$ args = encode ( 'UTF-8' , $ args ) ;
}
2021-07-25 08:24:16 -07:00
my @ cmdline = ( "./$module" , $ self - > { pbot } - > { interpreter } - > split_line ( $ args ) ) ;
2021-06-06 19:12:14 -07:00
2020-02-15 14:38:32 -08:00
my $ timeout = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'module_timeout' ) // 30 ;
2021-06-06 19:12:14 -07:00
2020-02-15 14:38:32 -08:00
my ( $ stdin , $ stdout , $ stderr ) ;
2021-06-06 19:12:14 -07:00
2020-02-15 14:38:32 -08:00
run \ @ cmdline , \ $ stdin , \ $ stdout , \ $ stderr , timeout ( $ timeout ) ;
2021-06-06 19:12:14 -07:00
2020-02-15 14:38:32 -08:00
my $ exitval = $? >> 8 ;
2021-06-06 19:12:14 -07:00
utf8:: decode $ stdout ;
utf8:: decode $ stderr ;
2020-02-15 14:38:32 -08:00
return ( $ exitval , $ stdout , $ stderr ) ;
} ;
if ( $@ ) {
my $ error = $@ ;
2021-02-07 13:41:22 -08:00
if ( $ error =~ m/timeout on timer/ ) {
( $ exitval , $ stdout , $ stderr ) = ( - 1 , "$context->{trigger}: timed-out" , '' ) ;
} else {
( $ exitval , $ stdout , $ stderr ) = ( - 1 , '' , $ error ) ;
2021-06-06 19:12:14 -07:00
$ self - > { pbot } - > { logger } - > log ( "$context->{trigger}: error executing module: $error\n" ) ;
2021-02-07 13:41:22 -08:00
}
2020-02-15 14:38:32 -08:00
}
if ( length $ stderr ) {
if ( open ( my $ fh , '>>' , "$module-stderr" ) ) {
print $ fh $ stderr ;
close $ fh ;
} else {
$ self - > { pbot } - > { logger } - > log ( "Failed to open $module-stderr: $!\n" ) ;
}
2020-02-14 18:52:41 -08:00
}
2020-05-01 20:59:51 -07:00
$ context - > { result } = $ stdout ;
chomp $ context - > { result } ;
2020-02-14 18:52:41 -08:00
}
1 ;