2010-03-22 08:33:44 +01:00
# File: FactoidModuleLauncher.pm
2010-03-24 07:47:40 +01:00
# Author: pragma_
2010-03-22 08:33:44 +01:00
#
# Purpose: Handles forking and execution of module processes
2017-03-05 22:33:31 +01:00
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
2010-03-22 08:33:44 +01:00
package PBot::FactoidModuleLauncher ;
use warnings ;
use strict ;
use POSIX qw( WNOHANG ) ; # for children process reaping
use Carp ( ) ;
2010-05-14 00:12:04 +02:00
use Text::Balanced qw( extract_delimited ) ;
2017-10-11 02:19:02 +02:00
use JSON ;
2010-03-22 08:33:44 +01:00
# automatically reap children processes in background
$ SIG { CHLD } = sub { while ( waitpid ( - 1 , WNOHANG ) > 0 ) { } } ;
sub new {
if ( ref ( $ _ [ 1 ] ) eq 'HASH' ) {
Carp:: croak ( "Options to Commands should be key/value pairs, not hash reference" ) ;
}
my ( $ class , % conf ) = @ _ ;
my $ self = bless { } , $ class ;
$ self - > initialize ( % conf ) ;
return $ self ;
}
sub initialize {
my ( $ self , % conf ) = @ _ ;
my $ pbot = delete $ conf { pbot } ;
if ( not defined $ pbot ) {
Carp:: croak ( "Missing pbot reference to PBot::FactoidModuleLauncher" ) ;
}
$ self - > { pbot } = $ pbot ;
}
sub execute_module {
2017-09-11 04:53:29 +02:00
my ( $ self , $ from , $ tonick , $ nick , $ user , $ host , $ command , $ root_channel , $ root_keyword , $ keyword , $ arguments , $ preserve_whitespace , $ referenced ) = @ _ ;
2010-03-22 08:33:44 +01:00
my $ text ;
$ arguments = "" if not defined $ arguments ;
2017-10-11 02:19:02 +02:00
my % stuff = ( from = > $ from , tonick = > $ tonick , nick = > $ nick , user = > $ user , host = > $ host , command = > $ command , root_channel = > $ root_channel ,
root_keyword = > $ root_keyword , keyword = > $ keyword , arguments = > $ arguments , preserve_whitespace = > $ preserve_whitespace , referenced = > $ referenced ) ;
2015-10-25 12:01:45 +01:00
my @ factoids = $ self - > { pbot } - > { factoids } - > find_factoid ( $ from , $ keyword , undef , 2 , 2 ) ;
2015-07-22 00:07:56 +02:00
2015-09-19 09:27:15 +02:00
if ( not @ factoids or not $ factoids [ 0 ] ) {
2014-03-14 11:05:11 +01:00
$ self - > { pbot } - > { interpreter } - > handle_result ( $ from , $ nick , $ user , $ host , $ command , "$keyword $arguments" , "/msg $nick Failed to find module for '$keyword' in channel $from\n" , 1 , 0 ) ;
return ;
2010-06-20 08:16:48 +02:00
}
2010-03-22 08:33:44 +01:00
2015-07-22 00:07:56 +02:00
my ( $ channel , $ trigger ) = ( $ factoids [ 0 ] - > [ 0 ] , $ factoids [ 0 ] - > [ 1 ] ) ;
2017-10-11 02:19:02 +02:00
$ stuff { channel } = $ channel ;
$ stuff { trigger } = $ trigger ;
2014-05-18 22:09:05 +02:00
my $ module = $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ channel } - > { $ trigger } - > { action } ;
2014-05-17 22:08:19 +02:00
my $ module_dir = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'module_dir' ) ;
2010-05-14 01:28:38 +02:00
2017-09-19 06:36:40 +02:00
$ self - > { pbot } - > { logger } - > log ( "(" . ( defined $ from ? $ from : "(undef)" ) . "): $nick!$user\@$host: Executing module [$command] $module $arguments\n" ) ;
2010-03-22 08:33:44 +01:00
2017-10-10 04:39:54 +02:00
$ arguments = $ self - > { pbot } - > { factoids } - > expand_special_vars ( $ from , $ nick , $ root_keyword , $ arguments ) ;
2010-05-14 01:28:38 +02:00
$ arguments = quotemeta ( $ arguments ) ;
2017-09-12 14:50:49 +02:00
if ( $ command eq 'code-factoid' or exists $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ channel } - > { $ trigger } - > { unquote_spaces } ) {
2014-04-19 12:32:49 +02:00
$ arguments =~ s/\\ / /g ;
}
2014-05-18 22:09:05 +02:00
if ( exists $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ channel } - > { $ trigger } - > { modulelauncher_subpattern } ) {
if ( $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ channel } - > { $ trigger } - > { modulelauncher_subpattern } =~ m/s\/(.*?)\/(.*)\/(.*)/ ) {
2014-04-19 12:32:49 +02:00
my ( $ p1 , $ p2 , $ p3 ) = ( $ 1 , $ 2 , $ 3 ) ;
my ( $ a , $ b , $ c , $ d , $ e , $ f , $ g , $ h , $ i , $ before , $ after ) ;
if ( $ p3 eq 'g' ) {
$ arguments =~ s/$p1/$p2/g ;
( $ a , $ b , $ c , $ d , $ e , $ f , $ g , $ h , $ i , $ before , $ after ) = ( $ 1 , $ 2 , $ 3 , $ 4 , $ 5 , $ 6 , $ 7 , $ 8 , $ 9 , $` , $' ) ;
} else {
$ arguments =~ s/$p1/$p2/ ;
( $ a , $ b , $ c , $ d , $ e , $ f , $ g , $ h , $ i , $ before , $ after ) = ( $ 1 , $ 2 , $ 3 , $ 4 , $ 5 , $ 6 , $ 7 , $ 8 , $ 9 , $` , $' ) ;
}
2014-04-26 17:19:55 +02:00
$ arguments =~ s/\$1/$a/g if defined $ a ;
$ arguments =~ s/\$2/$b/g if defined $ b ;
$ arguments =~ s/\$3/$c/g if defined $ c ;
$ arguments =~ s/\$4/$d/g if defined $ d ;
$ arguments =~ s/\$5/$e/g if defined $ e ;
$ arguments =~ s/\$6/$f/g if defined $ f ;
$ arguments =~ s/\$7/$g/g if defined $ g ;
$ arguments =~ s/\$8/$h/g if defined $ h ;
$ arguments =~ s/\$9/$i/g if defined $ i ;
$ arguments =~ s/\$`/$before/g if defined $ before ;
$ arguments =~ s/\$'/$after/g if defined $ after ;
2014-05-18 22:09:05 +02:00
#$self->{pbot}->{logger}->log("arguments subpattern: $arguments\n");
2010-05-14 00:12:04 +02:00
} else {
2014-05-18 22:09:05 +02:00
$ self - > { pbot } - > { logger } - > log ( "Invalid module substitution pattern [" . $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ channel } - > { $ trigger } - > { modulelauncher_subpattern } . "], ignoring.\n" ) ;
2010-05-14 00:12:04 +02:00
}
}
2010-05-14 01:28:38 +02:00
my $ argsbuf = $ arguments ;
2010-05-14 00:12:04 +02:00
$ arguments = "" ;
2010-05-14 01:28:38 +02:00
my $ lr ;
while ( 1 ) {
my ( $ e , $ r , $ p ) = extract_delimited ( $ argsbuf , "'" , "[^']+" ) ;
2010-05-14 00:12:04 +02:00
2010-05-14 02:08:52 +02:00
$ lr = $ r if not defined $ lr ;
2010-05-14 01:28:38 +02:00
if ( defined $ e ) {
2010-05-25 07:47:13 +02:00
$ e =~ s/\\([^\w])/$1/g ;
2010-05-14 01:28:38 +02:00
$ e =~ s/'/'\\''/g ;
$ e =~ s/^'\\''/'/ ;
$ e =~ s/'\\''$/'/ ;
$ arguments . = $ p ;
2010-05-14 00:12:04 +02:00
$ arguments . = $ e ;
2010-05-14 01:28:38 +02:00
$ lr = $ r ;
} else {
$ arguments . = $ lr ;
last ;
2010-05-14 00:12:04 +02:00
}
}
2014-03-14 11:05:11 +01:00
pipe ( my $ reader , my $ writer ) ;
2010-03-22 08:33:44 +01:00
my $ pid = fork ;
2014-03-14 11:05:11 +01:00
2010-03-22 08:33:44 +01:00
if ( not defined $ pid ) {
2014-05-18 22:09:05 +02:00
$ self - > { pbot } - > { logger } - > log ( "Could not fork module: $!\n" ) ;
2014-03-14 11:05:11 +01:00
close $ reader ;
close $ writer ;
$ self - > { pbot } - > { interpreter } - > handle_result ( $ from , $ nick , $ user , $ host , $ command , "$keyword $arguments" , "/me groans loudly.\n" , 1 , 0 ) ;
return ;
2010-03-22 08:33:44 +01:00
}
2014-03-14 11:05:11 +01:00
# FIXME -- add check to ensure $module exists
2010-03-22 08:33:44 +01:00
if ( $ pid == 0 ) { # start child block
2014-03-14 11:05:11 +01:00
close $ reader ;
2010-03-22 08:33:44 +01:00
2011-01-22 09:35:31 +01:00
# don't quit the IRC client when the child dies
2010-05-27 11:19:11 +02:00
no warnings ;
2011-01-22 09:35:31 +01:00
* PBot::IRC::Connection:: DESTROY = sub { return ; } ;
2010-05-27 11:19:11 +02:00
use warnings ;
2010-05-27 11:29:17 +02:00
if ( not chdir $ module_dir ) {
2014-05-18 22:09:05 +02:00
$ self - > { pbot } - > { logger } - > log ( "Could not chdir to '$module_dir': $!\n" ) ;
2010-05-27 11:29:17 +02:00
Carp:: croak ( "Could not chdir to '$module_dir': $!" ) ;
}
2015-01-28 08:49:30 +01:00
if ( exists $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ channel } - > { $ trigger } - > { workdir } ) {
chdir $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ channel } - > { $ trigger } - > { workdir } ;
}
2014-02-24 01:58:00 +01:00
2017-10-11 02:19:02 +02:00
$ stuff { result } = `./$module $arguments 2>> $module-stderr` ;
chomp $ stuff { result } ;
2010-03-22 08:33:44 +01:00
2017-10-11 02:19:02 +02:00
my $ json = encode_json \ % stuff ;
print $ writer "$json\n" ;
2014-03-14 11:05:11 +01:00
exit 0 ;
2010-03-22 08:33:44 +01:00
} # end child block
2010-03-23 04:09:03 +01:00
else {
2014-03-14 11:05:11 +01:00
close $ writer ;
$ self - > { pbot } - > { select_handler } - > add_reader ( $ reader , sub { $ self - > module_pipe_reader ( @ _ ) } ) ;
return "" ;
2010-03-23 04:09:03 +01:00
}
2014-03-14 11:05:11 +01:00
}
sub module_pipe_reader {
my ( $ self , $ buf ) = @ _ ;
2017-10-11 02:19:02 +02:00
my $ stuff = decode_json $ buf or return ;
if ( not defined $ stuff - > { result } or not length $ stuff - > { result } ) {
$ self - > { pbot } - > { logger } - > log ( "No result from module.\n" ) ;
return ;
}
if ( $ stuff - > { referenced } ) {
return if $ stuff - > { result } =~ m/(?:no results)/i ;
}
if ( $ stuff - > { command } eq 'code-factoid' ) {
$ stuff - > { result } =~ s/\s+$//g ;
$ self - > { pbot } - > { logger } - > log ( "No text result from code-factoid.\n" ) and return if not length $ stuff - > { result } ;
$ stuff - > { result } = $ self - > { pbot } - > { factoids } - > handle_action ( $ stuff - > { nick } , $ stuff - > { user } , $ stuff - > { host } ,
$ stuff - > { from } , $ stuff - > { root_channel } , $ stuff - > { root_keyword } , $ stuff - > { root_keyword } , $ stuff - > { arguments } ,
$ stuff - > { result } , $ stuff - > { tonick } , 0 , $ stuff - > { referenced } , undef , $ stuff - > { root_keyword } ) ;
}
if ( defined $ stuff - > { tonick } ) {
$ self - > { pbot } - > { logger } - > log ( "($stuff->{from}): $stuff->{nick}!$stuff->{user}\@$stuff->{host}) sent to $stuff->{tonick}\n" ) ;
# get rid of original caller's nick
$ stuff - > { result } =~ s/^\/([^ ]+) \Q$stuff->{nick}\E:\s+/\/$1 / ;
$ stuff - > { result } =~ s/^\Q$stuff->{nick}\E:\s+// ;
$ self - > { pbot } - > { interpreter } - > handle_result ( $ stuff - > { from } , $ stuff - > { nick } , $ stuff - > { user } , $ stuff - > { host } , $ stuff - > { command } , "$stuff->{keyword} $stuff->{arguments}" , "$stuff->{tonick}: $stuff->{result}" , 0 , $ stuff - > { preserve_whitespace } ) ;
} else {
if ( $ stuff - > { command } ne 'code-factoid' and exists $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ stuff - > { channel } } - > { $ stuff - > { trigger } } - > { add_nick } and $ self - > { pbot } - > { factoids } - > { factoids } - > hash - > { $ stuff - > { channel } } - > { $ stuff - > { trigger } } - > { add_nick } != 0 ) {
$ self - > { pbot } - > { interpreter } - > handle_result ( $ stuff - > { from } , $ stuff - > { nick } , $ stuff - > { user } , $ stuff - > { host } , $ stuff - > { command } , "$stuff->{keyword} $stuff->{arguments}" , "$stuff->{nick}: $stuff->{result}" , 0 , $ stuff - > { preserve_whitespace } ) ;
} else {
$ self - > { pbot } - > { interpreter } - > handle_result ( $ stuff - > { from } , $ stuff - > { nick } , $ stuff - > { user } , $ stuff - > { host } , $ stuff - > { command } , "$stuff->{keyword} $stuff->{arguments}" , $ stuff - > { result } , 0 , $ stuff - > { preserve_whitespace } ) ;
}
}
my $ text = $ self - > { pbot } - > { interpreter } - > truncate_result ( $ stuff - > { channel } , $ self - > { pbot } - > { registry } - > get_value ( 'irc' , 'botnick' ) , 'undef' , $ stuff - > { result } , $ stuff - > { result } , 0 ) ;
$ self - > { pbot } - > { antiflood } - > check_flood ( $ stuff - > { channel } , $ self - > { pbot } - > { registry } - > get_value ( 'irc' , 'botnick' ) , $ self - > { pbot } - > { registry } - > get_value ( 'irc' , 'username' ) , 'localhost' , $ text , 0 , 0 , 0 ) ;
2010-03-22 08:33:44 +01:00
}
1 ;