2021-11-20 03:05:50 +01:00
# File: Applets.pm
2021-06-19 06:23:34 +02:00
#
2021-11-20 03:05:50 +01:00
# Purpose: Applets are command-line programs and scripts that can be loaded
2021-06-07 04:12:14 +02:00
# via PBot factoids. Command arguments are passed as command-line arguments.
# The standard output from the script is returned as the bot command result.
2021-11-20 03:05:50 +01:00
# The standard error output is stored in a file named <applet>-stderr in the
# applets/ directory.
2021-06-07 04:12:14 +02:00
2023-02-21 06:31:52 +01:00
# SPDX-FileCopyrightText: 2007-2023 Pragmatic Software <pragma78@gmail.com>
2021-07-11 00:00:22 +02:00
# SPDX-License-Identifier: MIT
2020-02-15 03:52:41 +01:00
2021-11-20 03:05:50 +01:00
package PBot::Core::Applets ;
2021-07-21 07:44:51 +02:00
use parent 'PBot::Core::Class' ;
2020-02-15 03:52:41 +01:00
2021-06-19 06:23:34 +02:00
use PBot::Imports ;
2020-02-15 03:52:41 +01:00
use IPC::Run qw/run timeout/ ;
use Encode ;
2024-10-22 18:50:10 +02:00
use Time::HiRes qw/gettimeofday/ ;
2020-02-15 03:52:41 +01:00
sub initialize {
2021-07-23 16:24:30 +02:00
# nothing to do here
2020-02-15 03:52:41 +01:00
}
2023-04-14 06:04:12 +02:00
sub execute_applet ($self, $context) {
2020-02-15 23:38:32 +01:00
if ( $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'debugcontext' ) ) {
use Data::Dumper ;
2024-11-03 21:05:39 +01:00
$ Data:: Dumper:: Sortkeys = 1 ;
2024-10-22 18:50:10 +02:00
$ Data:: Dumper:: Indent = 2 ;
2021-11-20 03:05:50 +01:00
$ self - > { pbot } - > { logger } - > log ( "execute_applet\n" ) ;
2020-05-02 05:59:51 +02:00
$ self - > { pbot } - > { logger } - > log ( Dumper $ context ) ;
2020-02-15 23:38:32 +01:00
}
2020-02-15 03:52:41 +01:00
2021-11-20 03:05:50 +01:00
$ self - > { pbot } - > { process_manager } - > execute_process ( $ context , sub { $ self - > launch_applet ( @ _ ) } ) ;
2020-02-15 03:52:41 +01:00
}
2023-04-14 06:04:12 +02:00
sub launch_applet ($self, $context) {
2021-06-07 04:12:14 +02:00
$ context - > { arguments } // = '' ;
2021-07-27 06:39:44 +02:00
my @ factoids = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ context - > { from } , $ context - > { keyword } , exact_channel = > 2 , exact_trigger = > 2 ) ;
2021-06-07 04:12:14 +02:00
2020-02-15 23:38:32 +01:00
if ( not @ factoids or not $ factoids [ 0 ] ) {
2020-05-02 05:59:51 +02:00
$ context - > { checkflood } = 1 ;
2021-11-20 03:05:50 +01:00
$ self - > { pbot } - > { interpreter } - > handle_result ( $ context , "/msg $context->{nick} Failed to find applet for '$context->{keyword}' in channel $context->{from}\n" ) ;
2020-02-15 23:38:32 +01:00
return ;
2020-02-15 03:52:41 +01:00
}
2020-02-15 23:38:32 +01:00
my ( $ channel , $ trigger ) = ( $ factoids [ 0 ] - > [ 0 ] , $ factoids [ 0 ] - > [ 1 ] ) ;
2021-06-07 04:12:14 +02:00
2020-05-02 05:59:51 +02:00
$ context - > { channel } = $ channel ;
$ context - > { keyword } = $ trigger ;
$ context - > { trigger } = $ trigger ;
2020-02-15 23:38:32 +01:00
2021-11-20 03:05:50 +01:00
my $ applet = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , 'action' ) ;
2021-06-07 04:12:14 +02:00
2020-05-22 04:23:30 +02:00
$ self - > { pbot } - > { logger } - > log (
2021-06-07 04:12:14 +02:00
'(' . ( defined $ context - > { from } ? $ context - > { from } : "(undef)" ) . '): '
2021-11-20 03:05:50 +01:00
. "$context->{hostmask}: Executing applet [$context->{command}] $applet $context->{arguments}\n"
2020-05-22 04:23:30 +02:00
) ;
2021-07-27 06:39:44 +02:00
$ context - > { arguments } = $ self - > { pbot } - > { factoids } - > { variables } - > expand_factoid_vars ( $ context , $ context - > { arguments } ) ;
2020-02-15 23:38:32 +01:00
2021-11-20 03:05:50 +01:00
my $ applet_dir = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'applet_dir' ) ;
2021-06-07 04:12:14 +02:00
2021-11-20 03:05:50 +01:00
if ( not chdir $ applet_dir ) {
$ self - > { pbot } - > { logger } - > log ( "Could not chdir to '$applet_dir': $!\n" ) ;
Carp:: croak ( "Could not chdir to '$applet_dir': $!" ) ;
2020-02-15 03:52:41 +01:00
}
2020-02-15 23:38:32 +01:00
2021-07-27 06:39:44 +02: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 23:38:32 +01:00
}
2021-11-20 03:05:50 +01:00
# FIXME -- add check to ensure $applet exists
2021-06-07 04:12:14 +02:00
2020-02-15 23:38:32 +01:00
my ( $ exitval , $ stdout , $ stderr ) = eval {
2020-05-02 05:59:51 +02:00
my $ args = $ context - > { arguments } ;
2021-06-07 04:12:14 +02:00
2024-05-27 02:44:59 +02:00
my $ strip_quotes = 1 ;
my $ preserve_escapes = 0 ;
2022-06-28 04:52:38 +02:00
2024-05-27 02:44:59 +02:00
$ strip_quotes = 0 if $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , 'keep-quotes' ) ;
$ preserve_escapes = 1 if $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , 'keep-escapes' ) ;
2022-06-28 04:52:38 +02:00
2024-05-27 02:44:59 +02:00
my @ cmdline = ( "./$applet" , $ self - > { pbot } - > { interpreter } - > split_line ( $ args , strip_quotes = > $ strip_quotes , preserve_escapes = > $ preserve_escapes ) ) ;
2021-06-07 04:12:14 +02:00
2021-11-20 03:05:50 +01:00
my $ timeout = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'applet_timeout' ) // 30 ;
2021-06-07 04:12:14 +02:00
2020-02-15 23:38:32 +01:00
my ( $ stdin , $ stdout , $ stderr ) ;
2021-06-07 04:12:14 +02:00
2023-03-13 00:06:04 +01:00
# encode as UTF-8 if not already encoded (e.g. by encode_json)
if ( not $ context - > { args_utf8 } ) {
@ cmdline = map { encode ( 'UTF-8' , $ _ ) } @ cmdline ;
}
2024-10-22 18:50:10 +02:00
my $ start = gettimeofday ;
$ self - > { pbot } - > { logger } - > log ( "Starting applet run @cmdline\n" ) ;
2024-11-06 08:57:48 +01:00
2020-02-15 23:38:32 +01:00
run \ @ cmdline , \ $ stdin , \ $ stdout , \ $ stderr , timeout ( $ timeout ) ;
2024-11-06 08:57:48 +01:00
2024-10-22 18:50:10 +02:00
my $ duration = sprintf "%0.3f" , gettimeofday - $ start ;
$ self - > { pbot } - > { logger } - > log ( "Finished applet run @cmdline; duration: $duration\n" ) ;
2021-06-07 04:12:14 +02:00
2020-02-15 23:38:32 +01:00
my $ exitval = $? >> 8 ;
2021-06-07 04:12:14 +02:00
2023-03-13 00:06:04 +01:00
$ stdout = decode ( 'UTF-8' , $ stdout ) ;
$ stderr = decode ( 'UTF-8' , $ stderr ) ;
2021-06-07 04:12:14 +02:00
2020-02-15 23:38:32 +01:00
return ( $ exitval , $ stdout , $ stderr ) ;
} ;
if ( $@ ) {
my $ error = $@ ;
2021-02-07 22:41:22 +01:00
if ( $ error =~ m/timeout on timer/ ) {
( $ exitval , $ stdout , $ stderr ) = ( - 1 , "$context->{trigger}: timed-out" , '' ) ;
} else {
( $ exitval , $ stdout , $ stderr ) = ( - 1 , '' , $ error ) ;
2021-11-20 03:05:50 +01:00
$ self - > { pbot } - > { logger } - > log ( "$context->{trigger}: error executing applet: $error\n" ) ;
2021-02-07 22:41:22 +01:00
}
2020-02-15 23:38:32 +01:00
}
if ( length $ stderr ) {
2022-02-28 01:58:01 +01:00
if ( open ( my $ fh , '>>:encoding(UTF-8)' , "$applet-stderr" ) ) {
2020-02-15 23:38:32 +01:00
print $ fh $ stderr ;
close $ fh ;
} else {
2021-11-20 03:05:50 +01:00
$ self - > { pbot } - > { logger } - > log ( "Failed to open $applet-stderr: $!\n" ) ;
2020-02-15 23:38:32 +01:00
}
2020-02-15 03:52:41 +01:00
}
2020-05-02 05:59:51 +02:00
$ context - > { result } = $ stdout ;
chomp $ context - > { result } ;
2020-02-15 03:52:41 +01:00
}
1 ;