2021-07-21 06:38:07 +02:00
# File: Factoids.pm
2010-03-22 08:33:44 +01:00
#
2021-07-21 06:38:07 +02:00
# Purpose: Factoids command subroutines.
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 08:06:03 +02:00
package PBot::Core::Commands::Factoids ;
2010-03-22 08:33:44 +01:00
2021-06-19 06:23:34 +02:00
use PBot::Imports ;
2021-07-23 16:24:30 +02:00
use parent 'PBot::Core::Class' ;
2019-07-11 03:40:53 +02:00
2010-06-21 12:44:15 +02:00
use Time::Duration ;
use Time::HiRes qw( gettimeofday ) ;
2015-12-15 01:13:43 +01:00
use POSIX qw( strftime ) ;
2017-08-26 16:03:01 +02:00
use Storable ;
2020-01-20 05:00:01 +01:00
use LWP::UserAgent ;
2019-07-01 07:19:04 +02:00
use JSON ;
2010-03-22 08:33:44 +01:00
2021-07-24 04:22:25 +02:00
use PBot::Core::Utils::SafeFilename ;
2017-09-05 04:07:10 +02:00
2020-02-04 02:19:04 +01:00
our % factoid_metadata_capabilities = (
2020-02-15 23:38:32 +01:00
created_on = > 'botowner' ,
enabled = > 'chanop' ,
last_referenced_in = > 'botowner' ,
last_referenced_on = > 'botowner' ,
owner = > 'botowner' ,
rate_limit = > 'chanop' ,
ref_count = > 'botowner' ,
ref_user = > 'botowner' ,
type = > 'botowner' ,
edited_by = > 'botowner' ,
edited_on = > 'botowner' ,
locked = > 'chanop' ,
add_nick = > 'chanop' ,
nooverride = > 'chanop' ,
'cap-override' = > 'botowner' ,
'persist-key' = > 'admin' ,
# all others are allowed to be factset by anybody
2011-01-25 23:40:22 +01:00
) ;
2023-04-14 06:04:12 +02:00
sub initialize ($self, %conf) {
2021-11-20 03:05:50 +01:00
$ self - > { pbot } - > { registry } - > add_default ( 'text' , 'general' , 'applet_repo' , $ conf { applet_repo }
// 'https://github.com/pragma-/pbot/blob/master/applets/' ) ;
2020-02-15 23:38:32 +01:00
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factadd ( @ _ ) } , "learn" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factadd ( @ _ ) } , "factadd" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factrem ( @ _ ) } , "forget" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factrem ( @ _ ) } , "factrem" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factshow ( @ _ ) } , "factshow" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factinfo ( @ _ ) } , "factinfo" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factlog ( @ _ ) } , "factlog" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factundo ( @ _ ) } , "factundo" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factredo ( @ _ ) } , "factredo" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factset ( @ _ ) } , "factset" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factunset ( @ _ ) } , "factunset" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factchange ( @ _ ) } , "factchange" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factalias ( @ _ ) } , "factalias" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factmove ( @ _ ) } , "factmove" , 0 ) ;
2024-11-03 02:27:51 +01:00
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factcopy ( @ _ ) } , "factcopy" , 0 ) ;
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_call_factoid ( @ _ ) } , "fact" , 0 ) ;
2021-08-26 06:25:18 +02:00
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_as_factoid ( @ _ ) } , "factoid" , 0 ) ;
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_factfind ( @ _ ) } , "factfind" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_top20 ( @ _ ) } , "top20" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_histogram ( @ _ ) } , "histogram" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_count ( @ _ ) } , "count" , 0 ) ;
$ self - > { pbot } - > { commands } - > register ( sub { $ self - > cmd_add_regex ( @ _ ) } , "regex" , 1 ) ;
2010-06-21 15:54:27 +02:00
}
2023-04-14 06:04:12 +02:00
sub cmd_call_factoid ($self, $context) {
2020-05-02 05:59:51 +02:00
my ( $ chan , $ keyword , $ args ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ context - > { arglist } , 3 , 0 , 1 ) ;
2010-06-21 15:54:27 +02:00
2021-10-20 06:05:16 +02:00
if ( not defined $ chan or not defined $ keyword ) {
return "Usage: fact <channel> <keyword> [arguments]" ;
}
2010-06-21 15:54:27 +02:00
2021-07-27 06:39:44 +02:00
my ( $ channel , $ trigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ chan , $ keyword , arguments = > $ args , exact_channel = > 1 , exact_trigger = > 1 ) ;
2010-06-21 15:54:27 +02:00
2021-10-20 06:05:16 +02:00
if ( not defined $ trigger ) {
return "No such factoid $keyword exists for $chan" ;
}
2010-06-21 15:54:27 +02:00
2020-05-02 05:59:51 +02:00
$ context - > { keyword } = $ trigger ;
$ context - > { trigger } = $ trigger ;
$ context - > { ref_from } = $ channel ;
2024-11-22 05:21:14 +01:00
$ context - > { no_ref_from } = 1 ;
2021-10-20 06:05:16 +02:00
$ context - > { arguments } = $ args // '' ;
2020-05-02 05:59:51 +02:00
$ context - > { root_keyword } = $ trigger ;
2017-11-23 23:11:54 +01:00
2021-07-27 06:39:44 +02:00
return $ self - > { pbot } - > { factoids } - > { interpreter } - > interpreter ( $ context ) ;
2010-06-20 08:16:48 +02:00
}
2023-04-14 06:04:12 +02:00
sub cmd_as_factoid ($self, $context) {
2021-08-26 06:25:18 +02:00
my $ arguments = $ context - > { arguments } ;
my $ usage = "Usage: factoid <text to interpret as a factoid> [--args 'arguments passed to factoid']" ;
return $ usage if not length $ arguments ;
my ( $ args ) ;
my % opts = (
args = > \ $ args ,
) ;
my ( $ opt_args , $ opt_error ) = $ self - > { pbot } - > { interpreter } - > getopt (
$ arguments ,
\ % opts ,
[ 'bundling' ] ,
'args=s' ,
) ;
return "/say $opt_error -- $usage" if defined $ opt_error ;
return $ usage if not @$ opt_args ;
my $ action = "@$opt_args" ;
my $ trigger = "__anon-$context->{nick}-" . $ self - > { pbot } - > random_nick ( 4 ) ;
$ self - > { pbot } - > { factoids } - > { data } - > add ( 'text' , $ context - > { from } , $ context - > { hostmask } , $ trigger , $ action ) ;
$ context - > { keyword } = $ trigger ;
$ context - > { arguments } = $ args // '' ;
my $ result = $ self - > { pbot } - > { factoids } - > { interpreter } - > interpreter ( $ context ) ;
$ self - > { pbot } - > { factoids } - > { data } - > remove ( $ context - > { from } , $ trigger ) ;
return $ result ;
}
2023-04-14 06:04:12 +02:00
sub cmd_factundo ($self, $context) {
2020-02-15 23:38:32 +01:00
my $ usage = "Usage: factundo [-l [N]] [-r N] [channel] <keyword> (-l list undo history, optionally starting from N; -r jump to revision N)" ;
2017-08-27 06:35:46 +02:00
2020-05-04 22:21:35 +02:00
my $ arguments = $ context - > { arguments } ;
2020-02-15 23:38:32 +01:00
my ( $ list_undos , $ goto_revision ) ;
2021-07-31 00:01:38 +02:00
my % opts = (
l = > \ $ list_undos ,
r = > \ $ goto_revision ,
2020-02-15 23:38:32 +01:00
) ;
2017-08-26 16:03:01 +02:00
2021-07-31 00:01:38 +02:00
my ( $ opt_args , $ opt_error ) = $ self - > { pbot } - > { interpreter } - > getopt (
$ arguments ,
\ % opts ,
[ 'bundling' ] ,
'l:i' ,
'r=i' ,
) ;
2020-01-15 03:10:53 +01:00
2021-07-31 00:01:38 +02:00
return "/say $opt_error -- $usage" if defined $ opt_error ;
return $ usage if @$ opt_args > 2 ;
return $ usage if not @$ opt_args ;
2021-08-26 06:25:18 +02:00
$ arguments = join ( ' ' , map { $ _ = "'$_'" if $ _ =~ m/ / ; $ _ } @$ opt_args ) ;
2020-02-15 23:38:32 +01:00
my $ arglist = $ self - > { pbot } - > { interpreter } - > make_args ( $ arguments ) ;
2017-08-26 16:03:01 +02:00
2020-05-04 22:21:35 +02:00
my ( $ channel , $ trigger ) = $ self - > find_factoid_with_optional_channel (
$ context - > { from } , $ context - > { arguments } , 'factundo' , explicit = > 1 , exact_channel = > 1
) ;
2020-02-15 23:38:32 +01:00
my $ deleted ;
2017-12-12 12:38:45 +01:00
2020-02-15 23:38:32 +01:00
if ( not defined $ trigger ) {
# factoid not found or some error, try to continue and load undo file if it exists
$ deleted = 1 ;
( $ channel , $ trigger ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ arglist , 2 ) ;
if ( not defined $ trigger ) {
$ trigger = $ channel ;
2020-05-04 22:21:35 +02:00
$ channel = $ context - > { from } ;
2020-02-15 23:38:32 +01:00
}
$ channel = '.*' if $ channel !~ m/^#/ ;
2017-09-05 11:18:02 +02:00
}
2020-02-15 23:38:32 +01:00
my $ channel_path = $ channel ;
$ channel_path = 'global' if $ channel_path eq '.*' ;
my $ channel_path_safe = safe_filename $ channel_path ;
my $ trigger_safe = safe_filename $ trigger ;
2020-02-17 00:56:43 +01:00
my $ path = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'data_dir' ) . '/factlog' ;
2020-02-15 23:38:32 +01:00
my $ undos = eval { retrieve ( "$path/$trigger_safe.$channel_path_safe.undo" ) ; } ;
2021-07-27 06:39:44 +02:00
my $ channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , '_name' ) ;
2020-02-15 23:38:32 +01:00
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
if ( not $ undos ) { return "There are no undos available for [$channel_name] $trigger_name." ; }
if ( defined $ list_undos ) {
$ list_undos = 1 if $ list_undos == 0 ;
return $ self - > list_undo_history ( $ undos , $ list_undos ) ;
2017-12-12 12:38:45 +01:00
}
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-05-04 22:21:35 +02:00
my $ userinfo = $ self - > { pbot } - > { users } - > loggedin ( $ channel , $ context - > { hostmask } ) ;
2020-02-15 23:38:32 +01:00
if ( $ factoids - > get_data ( $ channel , $ trigger , 'locked' ) ) {
return "/say $trigger_name is locked and cannot be reverted." if not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'admin' ) ;
if ( $ factoids - > exists ( $ channel , $ trigger , 'cap-override' ) and not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'botowner' ) ) {
return "/say $trigger_name is locked with a cap-override and cannot be reverted. Unlock the factoid first." ;
}
2017-12-12 12:38:45 +01:00
}
2020-02-15 23:38:32 +01:00
if ( defined $ goto_revision ) {
return "Don't be absurd." if $ goto_revision < 1 ;
if ( $ goto_revision > @ { $ undos - > { list } } ) {
if ( @ { $ undos - > { list } } == 1 ) { return "There is only one revision available for [$channel_name] $trigger_name." ; }
else { return "There are " . @ { $ undos - > { list } } . " revisions available for [$channel_name] $trigger_name." ; }
}
if ( $ goto_revision == $ undos - > { idx } + 1 ) { return "[$channel_name] $trigger_name is already at revision $goto_revision." ; }
$ undos - > { idx } = $ goto_revision - 1 ;
eval { store $ undos , "$path/$trigger_safe.$channel_path_safe.undo" ; } ;
$ self - > { pbot } - > { logger } - > log ( "Error storing undo: $@\n" ) if $@ ;
} else {
unless ( $ deleted ) {
return "There are no more undos remaining for [$channel_name] $trigger_name." if not $ undos - > { idx } ;
$ undos - > { idx } - - ;
eval { store $ undos , "$path/$trigger_safe.$channel_path_safe.undo" ; } ;
$ self - > { pbot } - > { logger } - > log ( "Error storing undo: $@\n" ) if $@ ;
}
2017-12-12 12:38:45 +01:00
}
2017-08-27 06:35:46 +02:00
2023-04-17 19:33:02 +02:00
$ self - > { pbot } - > { factoids } - > { data } - > { storage } - > add ( $ channel , $ trigger , $ undos - > { list } - > [ $ undos - > { idx } ] ) ;
2017-08-27 06:35:46 +02:00
2020-02-15 23:38:32 +01:00
my $ changes = $ self - > hash_differences_as_string ( $ undos - > { list } - > [ $ undos - > { idx } + 1 ] , $ undos - > { list } - > [ $ undos - > { idx } ] ) ;
2020-05-04 22:21:35 +02:00
$ self - > log_factoid ( $ channel , $ trigger , $ context - > { hostmask } , "reverted (undo): $changes" , 1 ) ;
2020-02-15 23:38:32 +01:00
return "[$channel_name] $trigger_name reverted (revision " . ( $ undos - > { idx } + 1 ) . "): $changes\n" ;
2017-08-26 16:03:01 +02:00
}
2023-04-14 06:04:12 +02:00
sub cmd_factredo ($self, $context) {
2020-02-15 23:38:32 +01:00
my $ usage = "Usage: factredo [-l [N]] [-r N] [channel] <keyword> (-l list undo history, optionally starting from N; -r jump to revision N)" ;
2017-12-12 12:38:45 +01:00
2020-05-04 22:21:35 +02:00
my $ arguments = $ context - > { arguments } ;
2020-02-15 23:38:32 +01:00
my ( $ list_undos , $ goto_revision ) ;
2021-07-31 00:01:38 +02:00
my % opts = (
l = > \ $ list_undos ,
r = > \ $ goto_revision ,
) ;
my ( $ opt_args , $ opt_error ) = $ self - > { pbot } - > { interpreter } - > getopt (
$ arguments ,
\ % opts ,
[ 'bundling' ] ,
'l:i' ,
'r=i' ,
2020-02-15 23:38:32 +01:00
) ;
2017-12-12 12:38:45 +01:00
2021-07-31 00:01:38 +02:00
return "/say $opt_error -- $usage" if defined $ opt_error ;
return $ usage if @$ opt_args > 2 ;
return $ usage if not @$ opt_args ;
2017-08-26 16:03:01 +02:00
2021-08-26 06:25:18 +02:00
$ arguments = join ( ' ' , map { $ _ = "'$_'" if $ _ =~ m/ / ; $ _ } @$ opt_args ) ;
2017-08-27 06:35:46 +02:00
2020-05-04 22:21:35 +02:00
my ( $ channel , $ trigger ) = $ self - > find_factoid_with_optional_channel (
$ context - > { from } , $ context - > { arguments } , 'factredo' , explicit = > 1 , exact_channel = > 1
) ;
return $ channel if not defined $ trigger ; # if $trigger is not defined, $channel is an error message
2017-09-05 04:07:10 +02:00
2020-02-15 23:38:32 +01:00
my $ channel_path = $ channel ;
$ channel_path = 'global' if $ channel_path eq '.*' ;
2020-01-15 03:10:53 +01:00
2020-02-15 23:38:32 +01:00
my $ channel_path_safe = safe_filename $ channel_path ;
my $ trigger_safe = safe_filename $ trigger ;
2017-08-26 16:03:01 +02:00
2021-07-27 06:39:44 +02:00
my $ channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , '_name' ) ;
2020-02-15 23:38:32 +01:00
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
2017-08-26 16:03:01 +02:00
2020-02-15 23:38:32 +01:00
my $ path = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'data_dir' ) . '/factlog' ;
my $ undos = eval { retrieve ( "$path/$trigger_safe.$channel_path_safe.undo" ) ; } ;
2017-12-12 12:38:45 +01:00
2020-02-15 23:38:32 +01:00
if ( not $ undos ) { return "There are no redos available for [$channel_name] $trigger_name." ; }
2017-09-05 11:18:02 +02:00
2020-02-15 23:38:32 +01:00
if ( defined $ list_undos ) {
$ list_undos = 1 if $ list_undos == 0 ;
return $ self - > list_undo_history ( $ undos , $ list_undos ) ;
2017-09-05 11:18:02 +02:00
}
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-05-04 22:21:35 +02:00
my $ userinfo = $ self - > { pbot } - > { users } - > loggedin ( $ channel , $ context - > { hostmask } ) ;
2020-02-15 23:38:32 +01:00
if ( $ factoids - > get_data ( $ channel , $ trigger , 'locked' ) ) {
return "/say $trigger_name is locked and cannot be reverted." if not defined $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'admin' ) ;
2017-08-26 16:03:01 +02:00
2020-02-15 23:38:32 +01:00
if ( $ factoids - > exists ( $ channel , $ trigger , 'cap-override' ) and not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'botowner' ) ) {
return "/say $trigger_name is locked with a cap-override and cannot be reverted. Unlock the factoid first." ;
}
2017-12-12 12:38:45 +01:00
}
2020-02-15 23:38:32 +01:00
if ( not defined $ goto_revision and $ undos - > { idx } + 1 == @ { $ undos - > { list } } ) { return "There are no more redos remaining for [$channel_name] $trigger_name." ; }
2017-12-12 12:38:45 +01:00
2020-02-15 23:38:32 +01:00
if ( defined $ goto_revision ) {
return "Don't be absurd." if $ goto_revision < 1 ;
if ( $ goto_revision > @ { $ undos - > { list } } ) {
if ( @ { $ undos - > { list } } == 1 ) { return "There is only one revision available for [$channel_name] $trigger_name." ; }
else { return "There are " . @ { $ undos - > { list } } . " revisions available for [$channel_name] $trigger_name." ; }
}
if ( $ goto_revision == $ undos - > { idx } + 1 ) { return "[$channel_name] $trigger_name is already at revision $goto_revision." ; }
$ undos - > { idx } = $ goto_revision - 1 ;
eval { store $ undos , "$path/$trigger_safe.$channel_path_safe.undo" ; } ;
$ self - > { pbot } - > { logger } - > log ( "Error storing undo: $@\n" ) if $@ ;
} else {
$ undos - > { idx } + + ;
eval { store $ undos , "$path/$trigger_safe.$channel_path_safe.undo" ; } ;
$ self - > { pbot } - > { logger } - > log ( "Error storing undo: $@\n" ) if $@ ;
}
2017-08-27 06:40:27 +02:00
2023-04-17 19:33:02 +02:00
$ self - > { pbot } - > { factoids } - > { data } - > { storage } - > add ( $ channel , $ trigger , $ undos - > { list } - > [ $ undos - > { idx } ] ) ;
2017-08-27 06:40:27 +02:00
2020-02-15 23:38:32 +01:00
my $ changes = $ self - > hash_differences_as_string ( $ undos - > { list } - > [ $ undos - > { idx } - 1 ] , $ undos - > { list } - > [ $ undos - > { idx } ] ) ;
2021-07-26 16:46:50 +02:00
2020-05-04 22:21:35 +02:00
$ self - > log_factoid ( $ channel , $ trigger , $ context - > { hostmask } , "reverted (redo): $changes" , 1 ) ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
return "[$channel_name] $trigger_name restored (revision " . ( $ undos - > { idx } + 1 ) . "): $changes\n" ;
2015-12-13 22:58:01 +01:00
}
2023-04-14 06:04:12 +02:00
sub cmd_factset ($self, $context) {
2020-05-04 22:21:35 +02:00
my ( $ channel , $ trigger , $ arguments ) = $ self - > find_factoid_with_optional_channel (
$ context - > { from } , $ context - > { arguments } , 'factset' , usage = > 'Usage: factset [channel] <factoid> [key [value]]' , explicit = > 1
) ;
2020-02-15 23:38:32 +01:00
return $ channel if not defined $ trigger ; # if $trigger is not defined, $channel is an error message
2021-07-27 06:39:44 +02:00
my $ trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , '_name' ) ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
2017-08-27 09:56:55 +02:00
2020-02-15 23:38:32 +01:00
my $ arglist = $ self - > { pbot } - > { interpreter } - > make_args ( $ arguments ) ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
my ( $ key , $ value ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ arglist , 2 ) ;
2017-08-27 09:56:55 +02:00
2020-02-15 23:38:32 +01:00
$ channel = '.*' if $ channel !~ /^#/ ;
2021-07-26 16:46:50 +02:00
2021-07-27 06:39:44 +02:00
my ( $ owner_channel , $ owner_trigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ channel , $ trigger , exact_channel = > 1 , exact_trigger = > 1 ) ;
2020-01-15 03:10:53 +01:00
2020-02-15 23:38:32 +01:00
my $ userinfo ;
2021-07-26 16:46:50 +02:00
if ( defined $ owner_channel ) {
$ userinfo = $ self - > { pbot } - > { users } - > loggedin ( $ owner_channel , $ context - > { hostmask } ) ;
} else {
$ userinfo = $ self - > { pbot } - > { users } - > loggedin ( $ channel , $ context - > { hostmask } ) ;
}
2017-08-27 09:56:55 +02:00
2020-02-15 23:38:32 +01:00
my $ meta_cap ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
if ( defined $ key ) {
2021-07-26 16:46:50 +02:00
if ( defined $ factoid_metadata_capabilities { $ key } ) {
$ meta_cap = $ factoid_metadata_capabilities { $ key } ;
}
2015-06-06 07:26:02 +02:00
2020-02-15 23:38:32 +01:00
if ( defined $ meta_cap ) {
2021-07-26 16:46:50 +02:00
if ( not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , $ meta_cap ) ) {
return "Your user account must have the $meta_cap capability to set $key." ;
}
2020-02-15 23:38:32 +01:00
}
2011-01-25 23:40:22 +01:00
2021-07-26 16:46:50 +02:00
if ( defined $ value and ! $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'admin' )
2021-07-27 06:39:44 +02:00
and $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , 'locked' ) )
2021-07-26 16:46:50 +02:00
{
2020-02-15 23:38:32 +01:00
return "/say $trigger_name is locked; unlock before setting." ;
}
if ( lc $ key eq 'cap-override' and defined $ value ) {
2021-07-26 16:46:50 +02:00
if ( not $ self - > { pbot } - > { capabilities } - > exists ( $ value ) ) {
return "No such capability $value." ;
}
if ( not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , $ value ) ) {
return "Your user account must have the $value capability to set cap-override to $value." ;
}
2021-07-27 06:39:44 +02:00
$ self - > { pbot } - > { factoids } - > { data } - > { storage } - > set ( $ channel , $ trigger , 'locked' , '1' ) ;
2020-02-15 23:38:32 +01:00
}
2021-07-27 06:39:44 +02:00
if ( lc $ key eq 'locked' and $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > exists ( $ channel , $ trigger , 'cap-override' ) ) {
2020-02-15 23:38:32 +01:00
if ( not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'botowner' ) ) {
return "/say $trigger_name has a cap-override and cannot be unlocked until the override is removed." ;
}
}
2011-01-25 23:40:22 +01:00
}
2021-07-27 06:39:44 +02:00
my $ result = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > set ( $ channel , $ trigger , $ key , $ value ) ;
2020-02-15 23:38:32 +01:00
2024-11-14 10:21:48 +01:00
if ( defined $ value and $ result =~ m/set to/ ) {
$ self - > log_factoid ( $ channel , $ trigger , $ context - > { hostmask } , "set $key to $value" ) ;
}
2020-02-15 23:38:32 +01:00
return $ result ;
}
2023-04-14 06:04:12 +02:00
sub cmd_factunset ($self, $context) {
2020-02-15 23:38:32 +01:00
my $ usage = 'Usage: factunset [channel] <factoid> <key>' ;
2020-05-04 22:21:35 +02:00
my ( $ channel , $ trigger , $ arguments ) = $ self - > find_factoid_with_optional_channel (
$ context - > { from } , $ context - > { arguments } , 'factunset' , usage = > $ usage , explicit = > 1
) ;
2021-07-26 16:46:50 +02:00
return $ channel if not defined $ trigger ; # if $trigger is not defined, $channel is an error message
2020-05-04 22:21:35 +02:00
2020-02-15 23:38:32 +01:00
my ( $ key ) = $ self - > { pbot } - > { interpreter } - > split_line ( $ arguments , strip_quotes = > 1 ) ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
return $ usage if not length $ key ;
2021-07-27 06:39:44 +02:00
my ( $ owner_channel , $ owner_trigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ channel , $ trigger , exact_channel = > 1 , exact_trigger = > 1 ) ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
my $ userinfo ;
2021-07-26 16:46:50 +02:00
if ( defined $ owner_channel ) {
$ userinfo = $ self - > { pbot } - > { users } - > loggedin ( $ owner_channel , $ context - > { hostmask } ) ;
} else {
$ userinfo = $ self - > { pbot } - > { users } - > loggedin ( $ channel , $ context - > { hostmask } ) ;
}
2020-02-15 23:38:32 +01:00
my $ meta_cap ;
2021-07-26 16:46:50 +02:00
if ( exists $ factoid_metadata_capabilities { $ key } ) {
$ meta_cap = $ factoid_metadata_capabilities { $ key } ;
}
2020-02-15 23:38:32 +01:00
if ( defined $ meta_cap ) {
2021-07-26 16:46:50 +02:00
if ( not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , $ meta_cap ) ) {
return "Your user account must have the $meta_cap capability to unset $key." ;
}
2019-05-28 04:40:24 +02:00
}
2021-07-27 06:39:44 +02:00
if ( $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > exists ( $ channel , $ trigger , 'cap-override' ) ) {
2020-02-15 23:38:32 +01:00
if ( lc $ key eq 'locked' ) {
2021-07-26 16:46:50 +02:00
if ( $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'botowner' ) ) {
2024-11-06 01:55:53 +01:00
$ self - > { pbot } - > { factoids } - > { data } - > { storage } - > unset ( $ channel , $ trigger , 'cap-override' ) ;
2021-07-26 16:46:50 +02:00
} else {
return "You cannot unlock this factoid because it has a cap-override. Remove the override first." ;
}
2020-02-15 23:38:32 +01:00
}
2016-11-17 04:07:01 +01:00
}
2021-07-27 06:39:44 +02:00
my $ channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , '_name' ) ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
2024-11-14 10:21:48 +01:00
my $ oldvalue = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , $ key ) ;
2011-01-25 23:40:22 +01:00
2021-07-26 16:46:50 +02:00
if ( not defined $ oldvalue ) {
return "[$channel_name] $trigger_name: key '$key' does not exist." ;
}
if ( $ key eq 'cap-override' ) {
if ( not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , $ oldvalue ) ) {
return "Your user account must have the $oldvalue capability to unset this cap-override." ;
}
}
2021-07-27 06:39:44 +02:00
my $ result = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > unset ( $ channel , $ trigger , $ key ) ;
2021-07-26 16:46:50 +02:00
if ( $ result =~ m/unset/ ) {
$ self - > log_factoid ( $ channel , $ trigger , $ context - > { hostmask } , "unset $key (value: $oldvalue)" ) ;
}
2020-02-15 23:38:32 +01:00
return $ result ;
}
2011-01-25 23:40:22 +01:00
2023-04-14 06:04:12 +02:00
sub cmd_factmove ($self, $context) {
2020-05-02 05:59:51 +02:00
my ( $ src_channel , $ source , $ target_channel , $ target ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ context - > { arglist } , 5 ) ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
my $ usage = "Usage: factmove <source channel> <source factoid> <target channel/factoid> [target factoid]" ;
2021-07-26 16:46:50 +02:00
2020-02-15 23:38:32 +01:00
return $ usage if not defined $ target_channel ;
2024-11-03 02:27:51 +01:00
if ( $ target_channel !~ /^#/ && ( $ target_channel ne '.*' && $ target_channel ne 'global' ) ) {
2021-07-26 16:46:50 +02:00
if ( defined $ target ) {
return "Unexpected argument '$target' when renaming to '$target_channel'. Perhaps '$target_channel' is missing #s? $usage" ;
}
2024-11-03 02:27:51 +01:00
$ target_channel = '.*' if $ target_channel eq 'global' ;
2020-02-15 23:38:32 +01:00
$ target = $ target_channel ;
$ target_channel = $ src_channel ;
2019-08-22 18:49:14 +02:00
} else {
2021-07-26 16:46:50 +02:00
if ( not defined $ target ) {
$ target = $ source ;
}
2019-08-22 18:49:14 +02:00
}
2013-07-31 15:29:37 +02:00
2021-07-26 16:46:50 +02:00
if ( length $ target > $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_name_length' ) ) {
return "/say $context->{nick}: I don't think the factoid name needs to be that long." ;
}
2020-02-15 23:38:32 +01:00
if ( length $ target_channel > $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_channel_length' ) ) {
2020-05-04 22:21:35 +02:00
return "/say $context->{nick}: I don't think the channel name needs to be that long." ;
2011-01-25 23:40:22 +01:00
}
2015-12-13 22:58:01 +01:00
2021-07-27 06:39:44 +02:00
my ( $ found_src_channel , $ found_source ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ src_channel , $ source , exact_channel = > 1 , exact_trigger = > 1 ) ;
2015-12-13 22:58:01 +01:00
2021-07-26 16:46:50 +02:00
if ( not defined $ found_src_channel ) {
return "Source factoid $source not found in channel $src_channel" ;
}
2015-12-13 22:58:01 +01:00
2021-07-27 06:39:44 +02:00
my $ source_channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ found_src_channel , '_name' ) ;
my $ source_trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ found_src_channel , $ found_source , '_name' ) ;
2024-11-03 02:27:51 +01:00
2020-02-15 23:38:32 +01:00
$ source_channel_name = 'global' if $ source_channel_name eq '.*' ;
$ source_trigger_name = "\"$source_trigger_name\"" if $ source_trigger_name =~ / / ;
2010-06-20 08:16:48 +02:00
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-02-15 23:38:32 +01:00
my ( $ owner ) = $ factoids - > get_data ( $ found_src_channel , $ found_source , 'owner' ) =~ m/([^!]+)/ ;
2010-03-22 08:33:44 +01:00
2020-05-04 22:21:35 +02:00
if ( ( lc $ context - > { nick } ne lc $ owner ) and ( not $ self - > { pbot } - > { users } - > loggedin_admin ( $ found_src_channel , $ context - > { hostmask } ) ) ) {
$ self - > { pbot } - > { logger } - > log ( "$context->{hostmask} attempted to move [$found_src_channel] $found_source (not owner)\n" ) ;
2020-02-15 23:38:32 +01:00
my $ chan = ( $ found_src_channel eq '.*' ? 'the global channel' : $ found_src_channel ) ;
return "You are not the owner of $source_trigger_name for $source_channel_name." ;
}
2021-07-26 16:46:50 +02:00
if ( $ factoids - > get_data ( $ found_src_channel , $ found_source , 'locked' ) ) {
return "/say $source_trigger_name is locked; unlock before moving." ;
}
2020-02-15 23:38:32 +01:00
2021-07-27 06:39:44 +02:00
my ( $ found_target_channel , $ found_target ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ target_channel , $ target , exact_channel = > 1 , exact_trigger = > 1 ) ;
2020-02-15 23:38:32 +01:00
if ( defined $ found_target_channel ) {
my $ target_channel_name = $ factoids - > get_data ( $ found_target_channel , '_name' ) ;
my $ target_trigger_name = $ factoids - > get_data ( $ found_target_channel , $ found_target , '_name' ) ;
2024-11-03 02:27:51 +01:00
2020-02-15 23:38:32 +01:00
$ target_channel_name = 'global' if $ target_channel_name eq '.*' ;
$ target_trigger_name = "\"$target_trigger_name\"" if $ target_trigger_name =~ / / ;
2024-11-03 02:27:51 +01:00
2020-02-15 23:38:32 +01:00
return "Target factoid $target_trigger_name already exists in channel $target_channel_name." ;
}
2021-07-27 06:39:44 +02:00
my ( $ overchannel , $ overtrigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( '.*' , $ target , exact_channel = > 1 , exact_trigger = > 1 ) ;
2020-02-15 23:38:32 +01:00
if ( defined $ overtrigger and $ factoids - > get_data ( '.*' , $ overtrigger , 'nooverride' ) ) {
my $ override_channel_name = $ factoids - > get_data ( $ overchannel , '_name' ) ;
my $ override_trigger_name = $ factoids - > get_data ( $ overchannel , $ overtrigger , '_name' ) ;
2024-11-03 02:27:51 +01:00
2020-02-15 23:38:32 +01:00
$ override_channel_name = 'global' if $ override_channel_name eq '.*' ;
$ override_trigger_name = "\"$override_trigger_name\"" if $ override_trigger_name =~ / / ;
2024-11-03 02:27:51 +01:00
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { logger } - > log ( "$context->{hostmask} attempt to override $target\n" ) ;
2024-11-03 02:27:51 +01:00
2020-02-15 23:38:32 +01:00
return
"/say $override_trigger_name already exists for the global channel and cannot be overridden for "
. ( $ target_channel eq '.*' ? 'the global channel' : $ target_channel ) . "." ;
}
if ( $ self - > { pbot } - > { commands } - > exists ( $ target ) ) { return "/say $target already exists as a built-in command." ; }
$ target_channel = '.*' if $ target_channel !~ /^#/ ;
my $ data = $ factoids - > get_data ( $ found_src_channel , $ found_source ) ;
$ factoids - > remove ( $ found_src_channel , $ found_source , undef , 1 ) ;
2023-04-17 19:33:02 +02:00
$ factoids - > add ( $ target_channel , $ target , $ data ) ;
2020-02-15 23:38:32 +01:00
$ found_src_channel = 'global' if $ found_src_channel eq '.*' ;
$ target_channel = 'global' if $ target_channel eq '.*' ;
if ( $ src_channel eq lc $ target_channel ) {
2020-05-04 22:21:35 +02:00
$ self - > log_factoid ( $ found_src_channel , $ found_source , $ context - > { hostmask } , "renamed from $source_trigger_name to $target" ) ;
$ self - > log_factoid ( $ target_channel , $ target , $ context - > { hostmask } , "renamed from $source_trigger_name to $target" ) ;
2020-02-15 23:38:32 +01:00
return "[$source_channel_name] $source_trigger_name renamed to $target" ;
} else {
2020-05-04 22:21:35 +02:00
$ self - > log_factoid ( $ found_src_channel , $ found_source , $ context - > { hostmask } , "moved from $source_channel_name/$source_trigger_name to $target_channel/$target" ) ;
$ self - > log_factoid ( $ target_channel , $ target , $ context - > { hostmask } , "moved from $source_channel_name/$source_trigger_name to $target_channel/$target" ) ;
2020-02-15 23:38:32 +01:00
return "[$source_channel_name] $source_trigger_name moved to [$target_channel] $target" ;
}
2014-05-23 14:42:23 +02:00
}
2024-11-03 02:27:51 +01:00
sub cmd_factcopy ($self, $context) {
my ( $ src_channel , $ source , $ target_channel , $ target ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ context - > { arglist } , 5 ) ;
my $ usage = "Usage: factcopy <source channel> <source factoid> <target channel/factoid> [target factoid]" ;
return $ usage if not defined $ target_channel ;
if ( $ target_channel !~ /^#/ && ( $ target_channel ne '.*' && $ target_channel ne 'global' ) ) {
if ( defined $ target ) {
return "Unexpected argument '$target' when renaming to '$target_channel'. Perhaps '$target_channel' is missing #s? $usage" ;
}
$ target_channel = '.*' if $ target_channel eq 'global' ;
$ target = $ target_channel ;
$ target_channel = $ src_channel ;
} else {
if ( not defined $ target ) {
$ target = $ source ;
}
}
if ( length $ target > $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_name_length' ) ) {
return "/say $context->{nick}: I don't think the factoid name needs to be that long." ;
}
if ( length $ target_channel > $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_channel_length' ) ) {
return "/say $context->{nick}: I don't think the channel name needs to be that long." ;
}
my ( $ found_src_channel , $ found_source ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ src_channel , $ source , exact_channel = > 1 , exact_trigger = > 1 ) ;
if ( not defined $ found_src_channel ) {
return "Source factoid $source not found in channel $src_channel" ;
}
my $ source_channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ found_src_channel , '_name' ) ;
my $ source_trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ found_src_channel , $ found_source , '_name' ) ;
$ source_channel_name = 'global' if $ source_channel_name eq '.*' ;
$ source_trigger_name = "\"$source_trigger_name\"" if $ source_trigger_name =~ / / ;
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
my ( $ owner ) = $ factoids - > get_data ( $ found_src_channel , $ found_source , 'owner' ) =~ m/([^!]+)/ ;
if ( $ factoids - > get_data ( $ found_src_channel , $ found_source , 'locked' ) ) {
return "/say $source_trigger_name is locked; unlock before copying." ;
}
my ( $ found_target_channel , $ found_target ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ target_channel , $ target , exact_channel = > 1 , exact_trigger = > 1 ) ;
if ( defined $ found_target_channel ) {
my $ target_channel_name = $ factoids - > get_data ( $ found_target_channel , '_name' ) ;
my $ target_trigger_name = $ factoids - > get_data ( $ found_target_channel , $ found_target , '_name' ) ;
$ target_channel_name = 'global' if $ target_channel_name eq '.*' ;
$ target_trigger_name = "\"$target_trigger_name\"" if $ target_trigger_name =~ / / ;
return "Target factoid $target_trigger_name already exists in channel $target_channel_name." ;
}
my ( $ overchannel , $ overtrigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( '.*' , $ target , exact_channel = > 1 , exact_trigger = > 1 ) ;
if ( defined $ overtrigger and $ factoids - > get_data ( '.*' , $ overtrigger , 'nooverride' ) ) {
my $ override_channel_name = $ factoids - > get_data ( $ overchannel , '_name' ) ;
my $ override_trigger_name = $ factoids - > get_data ( $ overchannel , $ overtrigger , '_name' ) ;
$ override_channel_name = 'global' if $ override_channel_name eq '.*' ;
$ override_trigger_name = "\"$override_trigger_name\"" if $ override_trigger_name =~ / / ;
$ self - > { pbot } - > { logger } - > log ( "$context->{hostmask} attempt to override $target\n" ) ;
return
"/say $override_trigger_name already exists for the global channel and cannot be overridden for "
. ( $ target_channel eq '.*' ? 'the global channel' : $ target_channel ) . "." ;
}
if ( $ self - > { pbot } - > { commands } - > exists ( $ target ) ) { return "/say $target already exists as a built-in command." ; }
$ target_channel = '.*' if $ target_channel !~ /^#/ ;
my $ data = $ factoids - > get_data ( $ found_src_channel , $ found_source ) ;
$ data - > { owner } = $ context - > { hostmask } ;
$ data - > { created_on } = scalar gettimeofday ;
$ data - > { ref_count } = 0 ;
$ data - > { ref_user } = "nobody" ;
$ data - > { rate_limit } = $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'default_rate_limit' ) ;
$ data - > { last_referenced_in } = '' ;
delete $ data - > { last_referenced_on } ;
delete $ data - > { edited_on } ;
delete $ data - > { edited_by } ;
$ factoids - > add ( $ target_channel , $ target , $ data ) ;
$ found_src_channel = 'global' if $ found_src_channel eq '.*' ;
$ target_channel = 'global' if $ target_channel eq '.*' ;
$ self - > log_factoid ( $ found_src_channel , $ found_source , $ context - > { hostmask } , "copied from $source_channel_name/$source_trigger_name to $target_channel/$target" ) ;
$ self - > log_factoid ( $ target_channel , $ target , $ context - > { hostmask } , "copied from $source_channel_name/$source_trigger_name to $target_channel/$target" ) ;
return "[$source_channel_name] $source_trigger_name copied to [$target_channel] $target" ;
}
2023-04-14 06:04:12 +02:00
sub cmd_factalias ($self, $context) {
2020-05-02 05:59:51 +02:00
my ( $ chan , $ alias , $ command ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ context - > { arglist } , 3 , 0 , 1 ) ;
2020-02-15 23:38:32 +01:00
if ( defined $ chan and not ( $ chan eq '.*' or $ chan =~ m/^#/ ) ) {
# $chan doesn't look like a channel, so shift everything to the right
# and replace $chan with $from
if ( defined $ command and length $ command ) { $ command = "$alias $command" ; }
else { $ command = $ alias ; }
$ alias = $ chan ;
2020-05-04 22:21:35 +02:00
$ chan = $ context - > { from } ;
2020-02-15 23:38:32 +01:00
}
$ chan = '.*' if $ chan !~ /^#/ ;
return "Usage: factalias [channel] <keyword> <command>" if not length $ alias or not length $ command ;
2020-05-04 22:21:35 +02:00
if ( length $ alias > $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_name_length' ) ) { return "/say $context->{nick}: I don't think the factoid name needs to be that long." ; }
2020-02-15 23:38:32 +01:00
2020-05-04 22:21:35 +02:00
if ( length $ chan > $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_channel_length' ) ) { return "/say $context->{nick}: I don't think the channel name needs to be that long." ; }
2020-02-15 23:38:32 +01:00
2021-07-27 06:39:44 +02:00
my ( $ channel , $ alias_trigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ chan , $ alias , exact_channel = > 1 , exact_trigger = > 1 ) ;
2020-02-15 23:38:32 +01:00
if ( defined $ alias_trigger ) {
2021-07-27 06:39:44 +02:00
my $ alias_channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , '_name' ) ;
my $ alias_trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ alias_trigger , '_name' ) ;
2020-02-15 23:38:32 +01:00
$ alias_channel_name = 'global' if $ alias_channel_name eq '.*' ;
$ alias_trigger_name = "\"$alias_trigger_name\"" if $ alias_trigger_name =~ / / ;
return "$alias_trigger_name already exists for $alias_channel_name." ;
}
2021-07-27 06:39:44 +02:00
my ( $ overchannel , $ overtrigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( '.*' , $ alias , exact_channel = > 1 , exact_trigger = > 1 ) ;
if ( defined $ overtrigger and $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( '.*' , $ overtrigger , 'nooverride' ) ) {
my $ override_trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ overchannel , $ overtrigger , '_name' ) ;
2020-02-15 23:38:32 +01:00
$ override_trigger_name = "\"$override_trigger_name\"" if $ override_trigger_name =~ / / ;
return "/say $override_trigger_name already exists for the global channel and cannot be overridden for " . ( $ chan eq '.*' ? 'the global channel' : $ chan ) . "." ;
}
if ( $ self - > { pbot } - > { commands } - > exists ( $ alias ) ) { return "/say $alias already exists as a built-in command." ; }
2021-07-27 06:39:44 +02:00
$ self - > { pbot } - > { factoids } - > { data } - > add ( 'text' , $ chan , $ context - > { hostmask } , $ alias , "/call $command" ) ;
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { logger } - > log ( "$context->{hostmask} [$chan] aliased $alias => $command\n" ) ;
2020-02-21 22:42:47 +01:00
return "/say $alias aliases `$command` for " . ( $ chan eq '.*' ? 'the global channel' : $ chan ) ;
2010-03-22 08:33:44 +01:00
}
2023-04-14 06:04:12 +02:00
sub cmd_add_regex ($self, $context) {
2020-05-02 05:59:51 +02:00
my ( $ keyword , $ text ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ context - > { arglist } , 2 ) ;
2010-03-22 08:33:44 +01:00
2020-05-04 22:21:35 +02:00
my $ channel = $ context - > { from } ;
$ channel = '.*' if not defined $ channel or $ channel !~ /^#/ ;
2010-06-20 08:16:48 +02:00
2020-03-03 16:41:07 +01:00
if ( not defined $ keyword ) { return "Usage: regex <regex> <command> | regex <channel>" ; }
if ( not defined $ text ) {
my @ regexes ;
2021-07-27 06:39:44 +02:00
my $ iter = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_each ( 'type = regex' , "index1 = $keyword" , 'index2' , '_sort = index2' ) ;
while ( defined ( my $ factoid = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_next ( $ iter ) ) ) {
2020-03-03 16:41:07 +01:00
push @ regexes , $ factoid - > { index2 } ;
2020-02-15 23:38:32 +01:00
}
2020-03-03 16:41:07 +01:00
$ text = join '; ' , @ regexes ;
$ text = 'none' if not length $ text ;
return "Regex factoids for channel $keyword: $text" ;
2010-03-22 08:33:44 +01:00
}
2020-05-04 22:21:35 +02:00
my $ trigger ;
2021-07-27 06:39:44 +02:00
( $ channel , $ trigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ channel , $ keyword , exact_channel = > 1 , exact_trigger = > 1 ) ;
2010-06-20 08:16:48 +02:00
2020-02-15 23:38:32 +01:00
if ( defined $ trigger ) {
return "/say $trigger already exists for channel $channel." ;
}
2010-03-22 08:33:44 +01:00
2021-07-27 06:39:44 +02:00
$ self - > { pbot } - > { factoids } - > { data } - > add ( 'regex' , $ channel , $ context - > { hostmask } , $ keyword , $ text ) ;
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { logger } - > log ( "$context->{hostmask} added regex [$keyword] => [$text]\n" ) ;
2020-02-15 23:38:32 +01:00
return "/say $keyword added." ;
2010-03-22 08:33:44 +01:00
}
2022-12-26 03:17:14 +01:00
# FIXME: use registry array instead
my @ valid_pastesites = (
'https?://sprunge.us' ,
'https?://ix.io' ,
'https?://dpaste.com' ,
'https?://0x0.st' ,
) ;
2023-04-14 06:04:12 +02:00
sub cmd_factadd ($self, $context) {
2020-02-15 23:38:32 +01:00
my ( $ from_chan , $ keyword , $ text , $ force ) ;
2017-08-02 06:33:57 +02:00
2020-05-02 05:59:51 +02:00
my @ arglist = @ { $ context - > { arglist } } ;
2017-09-05 09:27:28 +02:00
2020-02-15 23:38:32 +01:00
if ( @ arglist ) {
# check for -f since we allow it to be before optional channel argument
if ( $ arglist [ 0 ] eq '-f' ) {
$ force = 1 ;
$ self - > { pbot } - > { interpreter } - > shift_arg ( \ @ arglist ) ;
}
2019-06-03 07:34:17 +02:00
2020-02-15 23:38:32 +01:00
# check if this is an optional channel argument
2021-07-27 06:39:44 +02:00
if ( $ arglist [ 0 ] =~ m/(?:^#|^global$|^\.\*$)/i ) {
$ from_chan = $ self - > { pbot } - > { interpreter } - > shift_arg ( \ @ arglist ) ;
} else {
$ from_chan = $ context - > { from } ;
}
2019-06-03 07:34:17 +02:00
2020-02-15 23:38:32 +01:00
# check for -f again since we also allow it to appear after the channel argument
if ( $ arglist [ 0 ] eq '-f' ) {
$ force = 1 ;
$ self - > { pbot } - > { interpreter } - > shift_arg ( \ @ arglist ) ;
}
2019-06-03 07:34:17 +02:00
2020-02-15 23:38:32 +01:00
# now this is the keyword
$ keyword = $ self - > { pbot } - > { interpreter } - > shift_arg ( \ @ arglist ) ;
2019-06-03 07:34:17 +02:00
2020-02-15 23:38:32 +01:00
# check for -url
if ( $ arglist [ 0 ] eq '-url' ) {
# discard it
$ self - > { pbot } - > { interpreter } - > shift_arg ( \ @ arglist ) ;
2019-07-01 02:45:05 +02:00
2020-02-15 23:38:32 +01:00
# the URL is the remaining arguments
my ( $ url ) = $ self - > { pbot } - > { interpreter } - > split_args ( \ @ arglist , 1 ) ;
2019-07-01 02:45:05 +02:00
2020-02-15 23:38:32 +01:00
# FIXME: move this to registry
2022-12-26 03:17:14 +01:00
if ( not grep { $ url =~ m/^$_/i } @ valid_pastesites ) {
return "Invalid URL: acceptable URLs are: " . join ( ', ' , sort @ valid_pastesites ) ;
2021-07-27 06:39:44 +02:00
}
2019-07-01 02:45:05 +02:00
2020-02-15 23:38:32 +01:00
# create a UserAgent
my $ ua = LWP::UserAgent - > new ( timeout = > 10 ) ;
# get the factoid's text from the URL
my $ response = $ ua - > get ( $ url ) ;
2019-07-01 02:45:05 +02:00
2020-02-15 23:38:32 +01:00
# process the response
2021-07-27 06:39:44 +02:00
if ( $ response - > is_success ) {
$ text = $ response - > decoded_content ;
} else {
return "Failed to get URL: " . $ response - > status_line ;
}
2024-10-22 18:50:10 +02:00
if ( length $ text > 1024 * 250 ) {
return "Factoid content cannot be larger than 250Kb" ;
}
2020-02-15 23:38:32 +01:00
} else {
# check for optional "is" and discard
2021-07-27 06:39:44 +02:00
if ( lc $ arglist [ 0 ] eq 'is' ) {
$ self - > { pbot } - > { interpreter } - > shift_arg ( \ @ arglist ) ;
}
2020-02-15 23:38:32 +01:00
# and the text is the remaining arguments with quotes preserved
( $ text ) = $ self - > { pbot } - > { interpreter } - > split_args ( \ @ arglist , 1 , 0 , 1 ) ;
}
2019-07-01 02:45:05 +02:00
}
2010-03-22 08:33:44 +01:00
2020-02-15 23:38:32 +01:00
if ( not defined $ from_chan or not defined $ text or not defined $ keyword ) {
return "Usage: factadd [-f] [channel] <keyword> (<factoid> | -url <paste site>); -f to force overwrite; -url to download from paste site" ;
}
2010-03-22 08:33:44 +01:00
2020-02-15 23:38:32 +01:00
$ from_chan = '.*' if $ from_chan !~ /^#/ ;
2017-01-30 03:01:26 +01:00
2021-07-27 06:39:44 +02:00
if ( length $ keyword > $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_name_length' ) ) {
return "/say $context->{nick}: I don't think the factoid name needs to be that long." ;
}
2017-09-05 09:27:28 +02:00
2021-07-27 06:39:44 +02:00
if ( length $ from_chan > $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_channel_length' ) ) {
return "/say $context->{nick}: I don't think the channel needs to be that long." ;
}
2017-09-05 09:27:28 +02:00
2020-02-15 23:38:32 +01:00
$ from_chan = '.*' if lc $ from_chan eq 'global' ;
$ from_chan = '.*' if not $ from_chan =~ m/^#/ ;
2010-06-29 07:48:46 +02:00
2020-02-15 23:38:32 +01:00
my $ keyword_text = $ keyword =~ / / ? "\"$keyword\"" : $ keyword ;
2018-08-09 02:58:53 +02:00
2021-07-27 06:39:44 +02:00
my ( $ channel , $ trigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ from_chan , $ keyword , exact_channel = > 1 , exact_trigger = > 1 ) ;
2020-02-15 23:38:32 +01:00
if ( defined $ trigger ) {
2021-07-27 06:39:44 +02:00
my $ channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , '_name' ) ;
2020-02-15 23:38:32 +01:00
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
2020-01-15 03:10:53 +01:00
2020-07-02 06:02:57 +02:00
if ( not $ force ) {
return "/say $trigger_name already exists for $channel_name." ;
} else {
2021-07-27 06:39:44 +02:00
if ( $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , 'locked' ) ) {
return "/say $trigger_name is locked; unlock before overwriting." ;
}
2020-02-15 23:38:32 +01:00
}
2019-06-03 07:34:17 +02:00
}
2014-07-11 14:56:17 +02:00
2021-07-27 06:39:44 +02:00
( $ channel , $ trigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( '.*' , $ keyword , exact_channel = > 1 , exact_trigger = > 1 ) ;
if ( defined $ trigger and $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( '.*' , $ trigger , 'nooverride' ) ) {
my $ trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , '_name' ) ;
2020-02-15 23:38:32 +01:00
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
return "/say $trigger_name already exists for the global channel and cannot be overridden for " . ( $ from_chan eq '.*' ? 'the global channel' : $ from_chan ) . "." ;
}
2010-03-22 08:33:44 +01:00
2020-02-15 23:38:32 +01:00
if ( $ self - > { pbot } - > { commands } - > exists ( $ keyword ) ) { return "/say $keyword_text already exists as a built-in command." ; }
2017-12-03 00:05:56 +01:00
2024-10-24 06:36:21 +02:00
my $ exists = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > exists ( $ from_chan , $ keyword ) ;
2021-07-27 06:39:44 +02:00
$ self - > { pbot } - > { factoids } - > { data } - > add ( 'text' , $ from_chan , $ context - > { hostmask } , $ keyword , $ text ) ;
2024-10-24 06:36:21 +02:00
if ( $ force && $ exists ) {
$ self - > { pbot } - > { factoids } - > { data } - > { storage } - > set ( $ from_chan , $ keyword , 'edited_by' , $ context - > { hostmask } ) ;
$ self - > { pbot } - > { factoids } - > { data } - > { storage } - > set ( $ from_chan , $ keyword , 'edited_on' , scalar gettimeofday ) ;
$ self - > log_factoid ( $ from_chan , $ keyword , $ context - > { hostmask } , "force created: $text" ) ;
} else {
$ self - > log_factoid ( $ from_chan , $ keyword , $ context - > { hostmask } , "created: $text" ) ;
}
2021-07-27 06:39:44 +02:00
2024-10-24 06:36:21 +02:00
$ self - > { pbot } - > { logger } - > log ( "$context->{hostmask} added [$from_chan] $keyword_text => $text\n" ) ;
2021-07-27 06:39:44 +02:00
2020-02-15 23:38:32 +01:00
return "/say $keyword_text added to " . ( $ from_chan eq '.*' ? 'global channel' : $ from_chan ) . "." ;
2010-03-22 08:33:44 +01:00
}
2023-04-14 06:04:12 +02:00
sub cmd_factrem ($self, $context) {
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2010-06-20 08:16:48 +02:00
2020-05-02 05:59:51 +02:00
my ( $ from_chan , $ from_trig ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ context - > { arglist } , 2 ) ;
2010-06-20 08:16:48 +02:00
2020-02-15 23:38:32 +01:00
if ( not defined $ from_trig ) {
$ from_trig = $ from_chan ;
2020-05-04 22:21:35 +02:00
$ from_chan = $ context - > { from } ;
2020-02-15 23:38:32 +01:00
}
2015-07-24 02:46:01 +02:00
2020-05-04 22:21:35 +02:00
my ( $ channel , $ trigger ) = $ self - > find_factoid_with_optional_channel ( $ context - > { from } , $ context - > { arguments } , 'factrem' , explicit = > 1 ) ;
2020-02-15 23:38:32 +01:00
return $ channel if not defined $ trigger ; # if $trigger is not defined, $channel is an error message
2010-06-20 08:16:48 +02:00
2020-02-15 23:38:32 +01:00
$ channel = '.*' if $ channel eq 'global' ;
$ from_chan = '.*' if $ channel eq 'global' ;
2015-12-13 22:58:01 +01:00
2020-02-15 23:38:32 +01:00
my $ channel_name = $ factoids - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ factoids - > get_data ( $ channel , $ trigger , '_name' ) ;
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
2018-08-09 02:58:53 +02:00
2021-11-20 03:05:50 +01:00
if ( $ factoids - > get_data ( $ channel , $ trigger , 'type' ) eq 'applet' ) { return "/say $trigger_name is not a factoid." ; }
2010-06-20 08:16:48 +02:00
2020-06-05 23:50:33 +02:00
if ( $ channel =~ /^#/ and $ from_chan =~ /^#/ and lc $ channel ne lc $ from_chan ) {
2020-02-15 23:38:32 +01:00
return "/say $trigger_name belongs to $channel_name, but this is $from_chan. Please switch to $channel_name or use /msg to remove this factoid." ;
}
2017-08-02 06:33:57 +02:00
2020-02-15 23:38:32 +01:00
my ( $ owner ) = $ factoids - > get_data ( $ channel , $ trigger , 'owner' ) =~ m/([^!]+)/ ;
2013-07-31 15:29:37 +02:00
2020-05-04 22:21:35 +02:00
if ( ( lc $ context - > { nick } ne lc $ owner ) and ( not $ self - > { pbot } - > { users } - > loggedin_admin ( $ channel , $ context - > { hostmask } ) ) ) {
2020-02-15 23:38:32 +01:00
return "You are not the owner of $trigger_name for $channel_name." ;
}
2010-06-20 08:16:48 +02:00
2020-02-15 23:38:32 +01:00
if ( $ factoids - > get_data ( $ channel , $ trigger , 'locked' ) ) { return "/say $trigger_name is locked; unlock before deleting." ; }
2013-09-13 23:48:19 +02:00
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { logger } - > log ( "$context->{hostmask} removed [$channel][$trigger][" . $ factoids - > get_data ( $ channel , $ trigger , 'action' ) . "]\n" ) ;
$ self - > log_factoid ( $ channel , $ trigger , $ context - > { hostmask } , "deleted" , 1 ) ;
2021-07-27 06:39:44 +02:00
return '/say ' . $ self - > { pbot } - > { factoids } - > { data } - > remove ( $ channel , $ trigger ) ;
2010-06-20 08:16:48 +02:00
}
2023-04-14 06:04:12 +02:00
sub cmd_factshow ($self, $context) {
2020-02-15 23:38:32 +01:00
my $ usage = "Usage: factshow [-p] [channel] <keyword>; -p to paste" ;
2020-05-04 22:21:35 +02:00
return $ usage if not length $ context - > { arguments } ;
2020-02-15 23:38:32 +01:00
2021-07-31 00:01:38 +02:00
my % opts ;
my ( $ opt_args , $ opt_error ) = $ self - > { pbot } - > { interpreter } - > getopt (
$ context - > { arguments } ,
\ % opts ,
[ 'bundling' ] ,
'p' ,
2020-02-15 23:38:32 +01:00
) ;
2021-07-31 00:01:38 +02:00
return "/say $opt_error -- $usage" if defined $ opt_error ;
return "Too many arguments -- $usage" if @$ opt_args > 2 ;
return "Missing argument -- $usage" if not @$ opt_args ;
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
my ( $ chan , $ trig ) = @$ opt_args ;
2020-05-04 22:21:35 +02:00
$ chan = $ context - > { from } if not defined $ trig ;
2021-08-26 06:25:18 +02:00
my $ args = join ( ' ' , map { $ _ = "'$_'" if $ _ =~ m/ / ; $ _ } @$ opt_args ) ;
2020-02-15 23:38:32 +01:00
2020-05-04 22:21:35 +02:00
my ( $ channel , $ trigger ) = $ self - > find_factoid_with_optional_channel ( $ context - > { from } , $ args , 'factshow' , usage = > $ usage ) ;
2020-02-15 23:38:32 +01:00
return $ channel if not defined $ trigger ; # if $trigger is not defined, $channel is an error message
my $ channel_name = $ factoids - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ factoids - > get_data ( $ channel , $ trigger , '_name' ) ;
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
my $ result = "$trigger_name: " ;
2021-07-31 00:01:38 +02:00
if ( $ opts { p } ) {
2020-02-15 23:38:32 +01:00
$ result . = $ self - > { pbot } - > { webpaste } - > paste ( $ factoids - > get_data ( $ channel , $ trigger , 'action' ) , no_split = > 1 ) ;
$ result = "[$channel_name] $result" if $ channel ne lc $ chan ;
return $ result ;
}
$ result . = $ factoids - > get_data ( $ channel , $ trigger , 'action' ) ;
2021-11-20 03:05:50 +01:00
$ result . = ' [applet]' if $ factoids - > get_data ( $ channel , $ trigger , 'type' ) eq 'applet' ;
2020-01-15 03:10:53 +01:00
$ result = "[$channel_name] $result" if $ channel ne lc $ chan ;
2019-07-01 05:21:09 +02:00
return $ result ;
2010-03-22 08:33:44 +01:00
}
2023-04-14 06:04:12 +02:00
sub cmd_factlog ($self, $context) {
2020-02-15 23:38:32 +01:00
my $ usage = "Usage: factlog [-h] [-t] [channel] <keyword>; -h show full hostmask; -t show actual timestamp instead of relative" ;
2015-12-15 01:13:43 +01:00
2020-05-04 22:21:35 +02:00
return $ usage if not length $ context - > { arguments } ;
2015-12-15 01:13:43 +01:00
2020-02-15 23:38:32 +01:00
my ( $ show_hostmask , $ actual_timestamp ) ;
2021-07-31 00:01:38 +02:00
my % opts = (
h = > \ $ show_hostmask ,
t = > \ $ actual_timestamp ,
) ;
my ( $ opt_args , $ opt_error ) = $ self - > { pbot } - > { interpreter } - > getopt (
$ context - > { arguments } ,
\ % opts ,
[ 'bundling' ] ,
qw( h t ) ,
2020-02-15 23:38:32 +01:00
) ;
2015-12-15 01:13:43 +01:00
2021-07-31 00:01:38 +02:00
return "/say $opt_error -- $usage" if defined $ opt_error ;
return "Too many arguments -- $usage" if @$ opt_args > 2 ;
return "Missing argument -- $usage" if not @$ opt_args ;
2015-12-15 01:13:43 +01:00
2021-08-26 06:25:18 +02:00
my $ args = join ( ' ' , map { $ _ = "'$_'" if $ _ =~ m/ / ; $ _ } @$ opt_args ) ;
2019-06-10 06:50:40 +02:00
2020-05-04 22:21:35 +02:00
my ( $ channel , $ trigger ) = $ self - > find_factoid_with_optional_channel ( $ context - > { from } , $ args , 'factlog' , usage = > $ usage , exact_channel = > 1 ) ;
2017-09-02 10:05:11 +02:00
if ( not defined $ trigger ) {
2020-02-15 23:38:32 +01:00
# factoid not found or some error, try to continue and load factlog file if it exists
my $ arglist = $ self - > { pbot } - > { interpreter } - > make_args ( $ args ) ;
( $ channel , $ trigger ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ arglist , 2 ) ;
2021-08-22 18:55:50 +02:00
2020-02-15 23:38:32 +01:00
if ( not defined $ trigger ) {
$ trigger = $ channel ;
2020-05-04 22:21:35 +02:00
$ channel = $ context - > { from } ;
2020-02-15 23:38:32 +01:00
}
2021-08-22 18:55:50 +02:00
2020-02-15 23:38:32 +01:00
$ channel = '.*' if $ channel !~ m/^#/ ;
2017-09-02 10:05:11 +02:00
}
2015-12-13 23:24:25 +01:00
2020-02-15 23:38:32 +01:00
my $ path = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'data_dir' ) . '/factlog' ;
2015-12-13 23:24:25 +01:00
2020-02-15 23:38:32 +01:00
$ channel = 'global' if $ channel eq '.*' ;
2017-09-05 04:07:10 +02:00
2020-02-15 23:38:32 +01:00
my $ channel_safe = safe_filename $ channel ;
my $ trigger_safe = safe_filename $ trigger ;
2017-09-05 04:07:10 +02:00
2020-02-15 23:38:32 +01:00
open my $ fh , "< $path/$trigger_safe.$channel_safe" or do {
$ self - > { pbot } - > { logger } - > log ( "Could not open $path/$trigger_safe.$channel_safe: $!\n" ) ;
$ channel = 'the global channel' if $ channel eq 'global' ;
return "No factlog available for $trigger in $channel." ;
} ;
2015-12-13 23:24:25 +01:00
2020-02-15 23:38:32 +01:00
my @ entries ;
2021-08-22 18:55:50 +02:00
2020-02-15 23:38:32 +01:00
while ( my $ line = <$fh> ) {
my ( $ timestamp , $ hostmask , $ msg ) ;
2019-07-01 07:19:04 +02:00
2020-02-15 23:38:32 +01:00
( $ timestamp , $ hostmask , $ msg ) = eval {
my $ h = decode_json $ line ;
return ( $ h - > { ts } , $ h - > { hm } , $ h - > { msg } ) ;
} ;
2019-07-01 07:19:04 +02:00
2020-02-15 23:38:32 +01:00
( $ timestamp , $ hostmask , $ msg ) = split /\s+/ , $ line , 3 if $@ ;
2021-08-22 18:55:50 +02:00
2020-02-15 23:38:32 +01:00
$ hostmask =~ s/!.*$// if not $ show_hostmask ;
2015-12-15 01:13:43 +01:00
2021-08-22 18:55:50 +02:00
if ( $ actual_timestamp ) {
$ timestamp = strftime "%a %b %e %H:%M:%S %Z %Y" , localtime $ timestamp ;
} else {
$ timestamp = concise ago gettimeofday - $ timestamp ;
}
2020-02-15 23:38:32 +01:00
push @ entries , "[$timestamp] $hostmask $msg\n" ;
2015-12-15 01:13:43 +01:00
}
2021-08-22 18:55:50 +02:00
2020-02-15 23:38:32 +01:00
close $ fh ;
2021-08-22 18:55:50 +02:00
my $ result = join "\n" , reverse @ entries ;
2020-02-15 23:38:32 +01:00
return $ result ;
2015-12-13 23:24:25 +01:00
}
2023-04-14 06:04:12 +02:00
sub cmd_factinfo ($self, $context) {
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-05-02 05:59:51 +02:00
my ( $ chan , $ trig ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ context - > { arglist } , 2 ) ;
2020-02-15 23:38:32 +01:00
if ( not defined $ trig ) {
$ trig = $ chan ;
2020-05-04 22:21:35 +02:00
$ chan = $ context - > { from } ;
2020-02-15 23:38:32 +01:00
}
2020-05-04 22:21:35 +02:00
my ( $ channel , $ trigger ) = $ self - > find_factoid_with_optional_channel ( $ context - > { from } , $ context - > { arguments } , 'factinfo' ) ;
2020-02-15 23:38:32 +01:00
return $ channel if not defined $ trigger ; # if $trigger is not defined, $channel is an error message
my $ channel_name = $ factoids - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ factoids - > get_data ( $ channel , $ trigger , '_name' ) ;
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
2021-07-21 06:38:07 +02:00
my $ created_ago = concise ago ( gettimeofday - $ factoids - > get_data ( $ channel , $ trigger , 'created_on' ) ) ;
my $ ref_ago = concise ago ( gettimeofday - $ factoids - > get_data ( $ channel , $ trigger , 'last_referenced_on' ) ) if defined $ factoids - > get_data ( $ channel , $ trigger , 'last_referenced_on' ) ;
2020-02-15 23:38:32 +01:00
# factoid
if ( $ factoids - > get_data ( $ channel , $ trigger , 'type' ) eq 'text' ) {
return
"/say $trigger_name: Factoid submitted by "
. $ factoids - > get_data ( $ channel , $ trigger , 'owner' )
. " for $channel_name on "
. localtime ( $ factoids - > get_data ( $ channel , $ trigger , 'created_on' ) )
. " [$created_ago], "
. (
defined $ factoids - > get_data ( $ channel , $ trigger , 'edited_by' )
? 'last edited by '
. $ factoids - > get_data ( $ channel , $ trigger , 'edited_by' ) . ' on '
. localtime ( $ factoids - > get_data ( $ channel , $ trigger , 'edited_on' ) ) . " ["
2021-07-21 06:38:07 +02:00
. concise ago ( gettimeofday - $ factoids - > get_data ( $ channel , $ trigger , 'edited_on' ) ) . "], "
2020-02-15 23:38:32 +01:00
: ""
)
. "referenced "
. $ factoids - > get_data ( $ channel , $ trigger , 'ref_count' )
. ' times (last by '
. $ factoids - > get_data ( $ channel , $ trigger , 'ref_user' )
. ( $ factoids - > exists ( $ channel , $ trigger , 'last_referenced_on' ) ? ' on ' . localtime ( $ factoids - > get_data ( $ channel , $ trigger , 'last_referenced_on' ) ) . " [$ref_ago]" : '' )
. ')' ;
}
2021-11-20 03:05:50 +01:00
# applet
if ( $ factoids - > get_data ( $ channel , $ trigger , 'type' ) eq 'applet' ) {
my $ applet_repo = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'applet_repo' ) ;
$ applet_repo . = $ factoids - > get_data ( $ channel , $ trigger , 'workdir' ) . '/' if $ factoids - > exists ( $ channel , $ trigger , 'workdir' ) ;
2020-02-15 23:38:32 +01:00
return
2022-04-08 18:13:57 +02:00
"/say $trigger_name: Applet loaded by "
2020-02-15 23:38:32 +01:00
. $ factoids - > get_data ( $ channel , $ trigger , 'owner' )
. " for $channel_name on "
. localtime ( $ factoids - > get_data ( $ channel , $ trigger , 'created_on' ) )
2021-11-20 03:05:50 +01:00
. " [$created_ago] -> $applet_repo"
2020-02-15 23:38:32 +01:00
. $ factoids - > get_data ( $ channel , $ trigger , 'action' )
. ', used '
. $ factoids - > get_data ( $ channel , $ trigger , 'ref_count' )
. ' times (last by '
. $ factoids - > get_data ( $ channel , $ trigger , 'ref_user' )
. ( $ factoids - > exists ( $ channel , $ trigger , 'last_referenced_on' ) ? ' on ' . localtime ( $ factoids - > get_data ( $ channel , $ trigger , 'last_referenced_on' ) ) . " [$ref_ago]" : '' )
. ')' ;
}
# regex
if ( $ factoids - > get_data ( $ channel , $ trigger , 'type' ) eq 'regex' ) {
return
"/say $trigger_name: Regex created by "
. $ factoids - > get_data ( $ channel , $ trigger , 'owner' )
. " for $channel_name on "
. localtime ( $ factoids - > get_data ( $ channel , $ trigger , 'created_on' ) )
. " [$created_ago], "
. (
defined $ factoids - > get_data ( $ channel , $ trigger , 'edited_by' )
? 'last edited by '
. $ factoids - > get_data ( $ channel , $ trigger , 'edited_by' ) . ' on '
. localtime ( $ factoids - > get_data ( $ channel , $ trigger , 'edited_on' ) ) . " ["
2021-07-21 06:38:07 +02:00
. concise ago ( gettimeofday - $ factoids - > get_data ( $ channel , $ trigger , 'edited_on' ) ) . "], "
2020-02-15 23:38:32 +01:00
: ""
)
. ' used '
. $ factoids - > get_data ( $ channel , $ trigger , 'ref_count' )
. ' times (last by '
. $ factoids - > get_data ( $ channel , $ trigger , 'ref_user' )
. ( $ factoids - > exists ( $ channel , $ trigger , 'last_referenced_on' ) ? ' on ' . localtime ( $ factoids - > get_data ( $ channel , $ trigger , 'last_referenced_on' ) ) . " [$ref_ago]" : '' )
. ')' ;
}
2021-11-20 03:05:50 +01:00
return "/say $context->{arguments} is not a factoid or an applet." ;
2010-03-22 08:33:44 +01:00
}
2023-04-14 06:04:12 +02:00
sub quotemeta2 ($text) {
2022-06-26 17:42:01 +02:00
$ text =~ s/(?<!\\) ([\[ \\ \| () { ^ \$ * + ? . ])/\\$1/gx ;
2022-06-26 02:34:06 +02:00
return $ text ;
}
2023-04-14 06:04:12 +02:00
sub cmd_factfind ($self, $context) {
2020-05-04 22:21:35 +02:00
my $ arguments = $ context - > { arguments } ;
2020-02-15 23:38:32 +01:00
2020-05-04 22:21:35 +02:00
my $ usage = "Usage: factfind [-channel channel] [-owner regex] [-editby regex] [-refby regex] [-regex] [text]" ;
return $ usage if not length $ arguments ;
2019-05-13 09:07:08 +02:00
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-02-15 23:38:32 +01:00
my ( $ channel , $ owner , $ refby , $ editby , $ use_regex ) ;
$ channel = $ 1 if $ arguments =~ s/\s*-channel\s+([^\b\s]+)//i ;
$ owner = $ 1 if $ arguments =~ s/\s*-owner\s+([^\b\s]+)//i ;
$ refby = $ 1 if $ arguments =~ s/\s*-refby\s+([^\b\s]+)//i ;
$ editby = $ 1 if $ arguments =~ s/\s*-editby\s+([^\b\s]+)//i ;
$ use_regex = 1 if $ arguments =~ s/\s*-regex\b//i ;
2010-03-26 09:58:25 +01:00
2020-02-15 23:38:32 +01:00
$ arguments =~ s/^\s+// ;
$ arguments =~ s/\s+$// ;
$ arguments =~ s/\s+/ /g ;
2010-03-26 09:58:25 +01:00
2020-02-15 23:38:32 +01:00
$ arguments = substr ( $ arguments , 0 , 30 ) ;
2021-08-28 04:41:51 +02:00
2020-02-15 23:38:32 +01:00
my $ argtype = undef ;
2010-03-26 09:58:25 +01:00
2020-04-17 05:03:59 +02:00
$ argtype = "owned by $owner" if defined $ owner and $ owner ne '.*' ;
2010-03-26 09:58:25 +01:00
2020-03-24 00:35:58 +01:00
if ( defined $ refby ) {
2020-02-15 23:38:32 +01:00
if ( not defined $ argtype ) { $ argtype = "last referenced by $refby" ; }
else { $ argtype . = " and last referenced by $refby" ; }
2014-06-01 23:31:54 +02:00
}
2020-03-24 00:35:58 +01:00
if ( defined $ editby ) {
2020-02-15 23:38:32 +01:00
if ( not defined $ argtype ) { $ argtype = "last edited by $editby" ; }
else { $ argtype . = " and last edited by $editby" ; }
2010-03-26 09:58:25 +01:00
}
2020-02-15 23:38:32 +01:00
if ( $ arguments ne "" ) {
my $ unquoted_args = $ arguments ;
$ unquoted_args =~ s/(?:\\(?!\\))//g ;
$ unquoted_args =~ s/(?:\\\\)/\\/g ;
2021-09-10 20:03:20 +02:00
if ( not defined $ argtype ) {
$ argtype = "containing '$unquoted_args'" ;
} else {
$ argtype . = " and containing '$unquoted_args'" ;
}
2020-02-15 23:38:32 +01:00
}
if ( not defined $ argtype ) { return $ usage ; }
2021-08-28 04:41:51 +02:00
if ( $ channel eq 'global' ) {
$ channel = '\.\*' ;
}
2020-02-15 23:38:32 +01:00
my ( $ text , $ last_trigger , $ last_chan , $ i ) ;
$ last_chan = "" ;
$ i = 0 ;
eval {
use re::engine::RE2 - strict = > 1 ;
my $ regex ;
2021-09-10 20:03:20 +02:00
if ( $ use_regex ) {
$ regex = $ arguments ;
} else {
2020-02-15 23:38:32 +01:00
$ regex = ( $ arguments =~ m/^\w/ ) ? '\b' : '\B' ;
2022-06-26 02:34:06 +02:00
$ regex . = quotemeta2 $ arguments ;
2020-02-15 23:38:32 +01:00
$ regex . = ( $ arguments =~ m/\w$/ ) ? '\b' : '\B' ;
}
foreach my $ chan ( sort $ factoids - > get_keys ) {
next if defined $ channel and $ chan !~ /^$channel$/i ;
2020-03-03 16:40:23 +01:00
foreach my $ factoid ( $ factoids - > get_all ( "index1 = $chan" , 'index2' , 'owner' , 'ref_user' , 'edited_by' , 'action' ) ) {
2020-02-26 11:32:52 +01:00
my $ match = 0 ;
2020-03-24 00:35:58 +01:00
if ( defined $ owner ) {
2020-02-26 11:32:52 +01:00
$ match = 1 if $ factoid - > { owner } =~ /^$owner/i ;
}
2020-03-24 00:35:58 +01:00
if ( defined $ refby ) {
2020-02-26 11:32:52 +01:00
$ match = 1 if $ factoid - > { ref_user } =~ /^$refby/i ;
}
2020-03-24 00:35:58 +01:00
if ( defined $ editby ) {
2020-02-26 11:32:52 +01:00
$ match = 1 if $ factoid - > { edited_by } =~ /^$editby/i ;
}
2020-03-24 00:35:58 +01:00
if ( $ arguments ne "" && ( $ factoid - > { action } =~ /$regex/i || $ factoid - > { index2 } =~ /$regex/i ) ) {
$ match = 1 ;
}
2020-02-26 11:32:52 +01:00
if ( $ match ) {
$ i + + ;
if ( $ chan ne $ last_chan ) {
$ text . = $ chan eq '.*' ? '[global channel] ' : '[' . $ factoids - > get_data ( $ chan , '_name' ) . '] ' ;
$ last_chan = $ chan ;
2020-02-15 23:38:32 +01:00
}
2020-02-26 11:32:52 +01:00
my $ trigger_name = $ factoids - > get_data ( $ chan , $ factoid - > { index2 } , '_name' ) ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
$ text . = "$trigger_name " ;
$ last_trigger = $ trigger_name ;
2020-02-15 23:38:32 +01:00
}
2010-06-29 06:33:27 +02:00
}
2010-03-22 08:33:44 +01:00
}
2020-02-15 23:38:32 +01:00
} ;
2010-03-26 09:58:25 +01:00
2020-05-04 22:21:35 +02:00
return "/msg $context->{nick} $context->{arguments}: $@" if $@ ;
2010-03-26 09:58:25 +01:00
2020-02-15 23:38:32 +01:00
if ( $ i == 1 ) {
chop $ text ;
return
2021-07-05 20:41:25 +02:00
"Found one factoid in "
. ( $ last_chan eq '.*' ? 'global' : $ factoids - > get_data ( $ last_chan , '_name' ) ) . ' '
2020-02-15 23:38:32 +01:00
. $ argtype
. ": $last_trigger is "
. $ factoids - > get_data ( $ last_chan , $ last_trigger , 'action' ) ;
} else {
return "Found $i factoids " . $ argtype . ": $text" unless $ i == 0 ;
2021-07-05 20:41:25 +02:00
my $ chans = ( defined $ channel ? ( $ channel eq '.*' ? 'global' : $ channel ) : 'any channels' ) ;
2020-02-15 23:38:32 +01:00
return "No factoids " . $ argtype . " submitted for $chans." ;
}
2010-03-22 08:33:44 +01:00
}
2023-04-14 06:04:12 +02:00
sub cmd_factchange ($self, $context) {
2021-07-27 06:39:44 +02:00
my $ factoids_data = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-02-15 23:38:32 +01:00
my ( $ channel , $ trigger , $ keyword , $ delim , $ tochange , $ changeto , $ modifier , $ url ) ;
my $ needs_disambig ;
2020-05-04 22:21:35 +02:00
if ( length $ context - > { arguments } ) {
2020-05-02 05:59:51 +02:00
my $ args = $ context - > { arglist } ;
2020-02-15 23:38:32 +01:00
my $ sub ;
my $ arg_count = $ self - > { pbot } - > { interpreter } - > arglist_size ( $ args ) ;
if ( $ arg_count >= 4 and ( $ args - > [ 0 ] =~ m/^#/ or $ args - > [ 0 ] eq '.*' or lc $ args - > [ 0 ] eq 'global' ) and ( $ args - > [ 2 ] eq '-url' ) ) {
$ channel = $ args - > [ 0 ] ;
$ keyword = $ args - > [ 1 ] ;
$ url = $ args - > [ 3 ] ;
$ needs_disambig = 0 ;
} elsif ( $ arg_count >= 3 and $ args - > [ 1 ] eq '-url' ) {
$ keyword = $ args - > [ 0 ] ;
$ url = $ args - > [ 2 ] ;
2020-05-04 22:21:35 +02:00
$ channel = $ context - > { from } ;
2020-02-15 23:38:32 +01:00
$ needs_disambig = 1 ;
} elsif ( $ arg_count >= 3 and ( $ args - > [ 0 ] =~ m/^#/ or $ args - > [ 0 ] eq '.*' or lc $ args - > [ 0 ] eq 'global' ) and ( $ args - > [ 2 ] =~ m/^s([[:punct:]])/ ) ) {
$ delim = $ 1 ;
$ channel = $ args - > [ 0 ] ;
$ keyword = $ args - > [ 1 ] ;
( $ sub ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ args , 1 , 2 , 1 ) ;
$ needs_disambig = 0 ;
} elsif ( $ arg_count >= 2 and $ args - > [ 1 ] =~ m/^s([[:punct:]])/ ) {
$ delim = $ 1 ;
$ keyword = $ args - > [ 0 ] ;
2020-05-04 22:21:35 +02:00
$ channel = $ context - > { from } ;
2020-02-15 23:38:32 +01:00
( $ sub ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ args , 1 , 1 , 1 ) ;
$ needs_disambig = 1 ;
}
if ( defined $ sub ) {
$ delim = quotemeta $ delim ;
2023-02-23 22:16:17 +01:00
if ( $ sub =~ /^s(?<!\\)$delim(.*?)(?<!\\)$delim(.*)(?<!\\)$delim(.*)$/ ) {
2020-02-15 23:38:32 +01:00
$ tochange = $ 1 ;
$ changeto = $ 2 ;
$ modifier = $ 3 ;
2023-02-23 22:16:17 +01:00
} elsif ( $ sub =~ /^s(?<!\\)$delim(.*?)(?<!\\)$delim(.*)$/ ) {
2020-02-15 23:38:32 +01:00
$ tochange = $ 1 ;
$ changeto = $ 2 ;
$ modifier = '' ;
}
2017-08-09 06:18:00 +02:00
}
}
2010-03-22 08:33:44 +01:00
2020-02-15 23:38:32 +01:00
if ( not defined $ channel or ( not defined $ changeto and not defined $ url ) ) {
return "Usage: factchange [channel] <keyword> (s/<pattern>/<replacement>/ | -url <paste site>)" ;
}
2010-03-22 08:33:44 +01:00
2020-02-15 23:38:32 +01:00
my ( $ from_trigger , $ from_chan ) = ( $ keyword , $ channel ) ;
2021-07-27 06:39:44 +02:00
my @ factoids = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ from_chan , $ keyword , exact_trigger = > 1 ) ;
2020-01-15 03:10:53 +01:00
2020-02-15 23:38:32 +01:00
if ( not @ factoids or not $ factoids [ 0 ] ) {
$ from_chan = 'global channel' if $ from_chan eq '.*' ;
return "/say $keyword not found in $from_chan" ;
}
2017-08-09 06:18:00 +02:00
2020-02-15 23:38:32 +01:00
if ( @ factoids > 1 ) {
2020-06-06 00:23:16 +02:00
if ( not grep { lc $ _ - > [ 0 ] eq $ from_chan } @ factoids ) {
2020-02-15 23:38:32 +01:00
return
"/say $from_trigger found in multiple channels: "
. ( join ', ' , sort map { $ _ - > [ 0 ] eq '.*' ? 'global' : $ _ - > [ 0 ] } @ factoids )
. "; use `factchange <channel> $from_trigger` to disambiguate." ;
} else {
foreach my $ factoid ( @ factoids ) {
if ( $ factoid - > [ 0 ] eq $ from_chan ) {
( $ channel , $ trigger ) = ( $ factoid - > [ 0 ] , $ factoid - > [ 1 ] ) ;
last ;
}
}
}
} else {
( $ channel , $ trigger ) = ( $ factoids [ 0 ] - > [ 0 ] , $ factoids [ 0 ] - > [ 1 ] ) ;
}
if ( not defined $ trigger ) { return "/say $keyword not found in channel $from_chan." ; }
2021-07-27 06:39:44 +02:00
my $ channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , '_name' ) ;
2020-02-15 23:38:32 +01:00
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
2017-08-02 06:33:57 +02:00
2020-02-15 23:38:32 +01:00
$ from_chan = '.*' if $ from_chan eq 'global' ;
2016-11-17 04:07:01 +01:00
2020-06-05 23:50:33 +02:00
if ( $ channel =~ /^#/ and $ from_chan =~ /^#/ and lc $ channel ne lc $ from_chan ) {
2020-02-15 23:38:32 +01:00
return "/say $trigger_name belongs to $channel_name, but this is $from_chan. Please switch to $channel_name or use /msg to change this factoid." ;
2016-11-17 04:07:01 +01:00
}
2013-09-13 23:48:19 +02:00
2020-05-04 22:21:35 +02:00
my $ userinfo = $ self - > { pbot } - > { users } - > loggedin ( $ channel , $ context - > { hostmask } ) ;
2020-02-15 23:38:32 +01:00
if ( $ factoids_data - > get_data ( $ channel , $ trigger , 'locked' ) ) {
return "/say $trigger_name is locked and cannot be changed." if not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'admin' ) ;
2019-07-01 07:19:04 +02:00
2020-02-15 23:38:32 +01:00
if ( $ factoids_data - > exists ( $ channel , $ trigger , 'cap-override' ) and not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'botowner' ) ) {
return "/say $trigger_name is locked with a cap-override set and cannot be changed until the override is removed." ;
}
2019-07-01 07:19:04 +02:00
}
2020-02-15 23:38:32 +01:00
my $ action = $ factoids_data - > get_data ( $ channel , $ trigger , 'action' ) ;
2019-07-01 07:19:04 +02:00
2020-02-15 23:38:32 +01:00
if ( defined $ url ) {
# FIXME: move this to registry
2022-12-26 03:17:14 +01:00
if ( not grep { $ url =~ m/^$_/i } @ valid_pastesites ) {
return "Invalid URL: acceptable URLs are: " . join ( ', ' , sort @ valid_pastesites ) ;
}
2020-02-15 23:38:32 +01:00
my $ ua = LWP::UserAgent - > new ( timeout = > 10 ) ;
my $ response = $ ua - > get ( $ url ) ;
if ( $ response - > is_success ) { $ action = $ response - > decoded_content ; }
else { return "Failed to get URL: " . $ response - > status_line ; }
2019-07-01 07:19:04 +02:00
} else {
2020-02-15 23:38:32 +01:00
my $ ret = eval {
use re::engine::RE2 - strict = > 1 ;
my $ changed ;
if ( $ modifier eq 'gi' or $ modifier eq 'ig' or $ modifier eq 'g' ) {
my @ chars = ( "A" .. "Z" , "a" .. "z" , "0" .. "9" ) ;
my $ magic = '' ;
$ magic . = $ chars [ rand @ chars ] for 1 .. ( 10 * rand ) + 10 ;
my $ insensitive = index ( $ modifier , 'i' ) + 1 ;
my $ count = 0 ;
my $ max = 50 ;
while ( 1 ) {
if ( $ count == 0 ) {
if ( $ insensitive ) { $ changed = $ action =~ s | $ tochange | $ changeto $ magic | i ; }
else { $ changed = $ action =~ s | $ tochange | $ changeto $ magic | ; }
} else {
if ( $ insensitive ) { $ changed = $ action =~ s | $ tochange | $ 1 $ changeto $ magic | i ; }
else { $ changed = $ action =~ s | $ tochange | $ 1 $ changeto $ magic | ; }
}
if ( $ changed ) {
$ count + + ;
if ( $ count == $ max ) {
$ action =~ s/$magic// ;
last ;
}
$ tochange = "$magic(.*?)$tochange" if $ count == 1 ;
} else {
$ changed = $ count ;
$ action =~ s/$magic// if $ changed ;
last ;
}
}
} elsif ( $ modifier eq 'i' ) {
$ changed = $ action =~ s | $ tochange | $ changeto | i ;
2019-07-01 07:19:04 +02:00
} else {
2020-02-15 23:38:32 +01:00
$ changed = $ action =~ s | $ tochange | $ changeto | ;
2019-07-01 07:19:04 +02:00
}
2018-02-19 22:45:24 +01:00
2020-02-15 23:38:32 +01:00
if ( not $ changed ) {
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { logger } - > log ( "($context->{from}) $context->{hostmask}: failed to change '$trigger' 's$delim$tochange$delim$changeto$delim\n" ) ;
2020-02-15 23:38:32 +01:00
return "Change $trigger failed." ;
2019-07-01 07:19:04 +02:00
}
2020-02-15 23:38:32 +01:00
return "" ;
} ;
2017-08-25 00:18:41 +02:00
2020-02-15 23:38:32 +01:00
if ( $@ ) {
my $ err = $@ ;
2021-07-21 06:38:07 +02:00
$ err =~ s/ at PBot\/.*$// ;
2020-05-04 22:21:35 +02:00
return "/msg $context->{nick} Change $trigger_name failed: $err" ;
2020-02-15 23:38:32 +01:00
}
return $ ret if length $ ret ;
2020-01-15 03:10:53 +01:00
}
2017-08-25 00:18:41 +02:00
2020-02-15 23:38:32 +01:00
if ( length $ action > 8000 and not $ self - > { pbot } - > { capabilities } - > userhas ( $ userinfo , 'admin' ) ) { return "Change $trigger_name failed; result is too long." ; }
2017-08-25 00:18:41 +02:00
2020-02-15 23:38:32 +01:00
if ( not length $ action ) { return "Change $trigger_name failed; factoids cannot be empty." ; }
2019-07-01 07:19:04 +02:00
2020-05-04 22:21:35 +02:00
$ self - > { pbot } - > { logger } - > log ( "($context->{from}) $context->{hostmask} changed '$trigger' 's/$tochange/$changeto/\n" ) ;
2019-07-01 07:19:04 +02:00
2023-04-22 00:56:43 +02:00
$ factoids_data - > set ( $ channel , $ trigger , 'action' , $ action ) ;
$ factoids_data - > set ( $ channel , $ trigger , 'edited_by' , $ context - > { hostmask } ) ;
$ factoids_data - > set ( $ channel , $ trigger , 'edited_on' , scalar gettimeofday ) ;
2020-05-04 22:21:35 +02:00
$ self - > log_factoid ( $ channel , $ trigger , $ context - > { hostmask } , "changed to $action" ) ;
2020-02-15 23:38:32 +01:00
return "Changed: $trigger_name is $action" ;
2010-03-22 08:33:44 +01:00
}
2023-04-14 06:04:12 +02:00
sub cmd_top20 ($self, $context) {
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-05-04 22:21:35 +02:00
my % hash = ( ) ;
my $ text = "" ;
my $ i = 0 ;
my ( $ channel , $ args ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ context - > { arglist } , 2 ) ;
2024-10-24 06:36:21 +02:00
if ( not defined $ channel ) { return "Usage: top20 <channel> [nick or 'added' or 'edited']" ; }
2020-05-04 22:21:35 +02:00
if ( not defined $ args ) {
my $ iter = $ factoids - > get_each ( 'type = text' , "index1 = $channel" , 'index2' , 'ref_count > 0' , '_sort = -ref_count' ) ;
while ( defined ( my $ factoid = $ factoids - > get_next ( $ iter ) ) ) {
$ text . = $ factoids - > get_data ( $ factoid - > { index1 } , $ factoid - > { index2 } , '_name' ) . " ($factoid->{ref_count}) " ;
$ i + + ;
last if $ i >= 20 ;
}
2023-04-14 06:04:12 +02:00
$ channel = "the global channel" if $ channel eq '.*' ;
2020-05-04 22:21:35 +02:00
if ( $ i > 0 ) {
return "Top $i referenced factoids for $channel: $text" ;
} else {
return "No factoids referenced in $channel." ;
}
}
2024-10-24 06:36:21 +02:00
if ( lc $ args eq 'recent' || lc $ args eq 'added' ) {
2020-05-04 22:21:35 +02:00
my $ iter = $ factoids - > get_each ( 'type = text' , "index1 = $channel" , 'index2' , 'created_on' , 'owner' , '_sort = -created_on' ) ;
while ( defined ( my $ factoid = $ factoids - > get_next ( $ iter ) ) ) {
my $ ago = concise ago gettimeofday - $ factoid - > { 'created_on' } ;
my ( $ owner ) = $ factoid - > { 'owner' } =~ /^([^!]+)/ ;
$ text . = ' ' . $ factoids - > get_data ( $ factoid - > { index1 } , $ factoid - > { index2 } , '_name' ) . " [$ago by $owner]\n" ;
$ i + + ;
last if $ i >= 50 ;
}
2024-10-24 06:36:21 +02:00
$ channel = 'global channel' if $ channel eq '.*' ;
2020-05-04 22:21:35 +02:00
$ text = "$i most recent $channel submissions:\n\n$text" if $ i > 0 ;
2024-10-24 06:36:21 +02:00
return $ text ;
}
if ( lc $ args eq 'edited' ) {
my $ iter = $ factoids - > get_each ( 'type = text' , "index1 = $channel" , 'index2' , 'edited_on' , 'edited_by' , '_sort = -edited_on' ) ;
while ( defined ( my $ factoid = $ factoids - > get_next ( $ iter ) ) ) {
2024-10-24 07:32:27 +02:00
last if not defined $ factoid - > { 'edited_on' } ;
2024-10-24 06:36:21 +02:00
my $ ago = concise ago gettimeofday - $ factoid - > { 'edited_on' } ;
my ( $ editor ) = $ factoid - > { 'edited_by' } =~ /^([^!]+)/ ;
$ text . = ' ' . $ factoids - > get_data ( $ factoid - > { index1 } , $ factoid - > { index2 } , '_name' ) . " [$ago by $editor]\n" ;
$ i + + ;
last if $ i >= 50 ;
}
$ channel = 'global channel' if $ channel eq '.*' ;
$ text = "$i most recent $channel edits:\n\n$text" if $ i > 0 ;
2020-05-04 22:21:35 +02:00
return $ text ;
}
my $ iter = $ factoids - > get_each ( 'type = text' , "index1 = $channel" , 'index2' , 'ref_user' , 'last_referenced_on' , '_sort = -last_referenced_on' ) ;
while ( defined ( my $ factoid = $ factoids - > get_next ( $ iter ) ) ) {
my ( $ ref_user ) = $ factoid - > { ref_user } =~ /^([^!]+)/ ;
if ( $ ref_user =~ /^\Q$args\E/i ) {
my $ ago = $ factoid - > { 'last_referenced_on' } ? concise ago ( gettimeofday - $ factoid - > { 'last_referenced_on' } ) : "unknown" ;
$ text . = ' ' . $ factoids - > get_data ( $ factoid - > { index1 } , $ factoid - > { index2 } , '_name' ) . " [$ago]\n" ;
$ i + + ;
last if $ i >= 20 ;
}
}
if ( $ i > 0 ) {
return "$i $channel factoids last referenced by $args:\n\n$text" ;
} else {
return "No factoids last referenced by $args in $channel." ;
}
}
2023-04-14 06:04:12 +02:00
sub cmd_histogram ($self, $context) {
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-05-04 22:21:35 +02:00
my % owners ;
my $ factoid_count = 0 ;
my $ iter = $ factoids - > get_each ( 'type = text' , 'owner' ) ;
while ( defined ( my $ factoid = $ factoids - > get_next ( $ iter ) ) ) {
my ( $ owner ) = $ factoid - > { owner } =~ m/^([^!]+)/ ;
$ owners { $ owner } + + ;
$ factoid_count + + ;
}
my $ top = 15 ;
my $ text ;
my $ i = 0 ;
foreach my $ owner ( sort { $ owners { $ b } <=> $ owners { $ a } } keys % owners ) {
my $ percent = int ( $ owners { $ owner } / $ factoid_count * 100 ) ;
$ text . = "$owner: $owners{$owner} ($percent" . "%)\n" ;
$ i + + ;
last if $ i >= $ top ;
}
return "/say $factoid_count factoids, top $top submitters:\n$text" ;
}
2023-04-14 06:04:12 +02:00
sub cmd_count ($self, $context) {
2021-07-27 06:39:44 +02:00
my $ factoids = $ self - > { pbot } - > { factoids } - > { data } - > { storage } ;
2020-05-04 22:21:35 +02:00
my $ i = 0 ;
my $ total = 0 ;
my $ arguments = $ context - > { arguments } ;
if ( not length $ arguments ) { return "Usage: count <nick|factoids>" ; }
$ arguments = ".*" if ( $ arguments =~ /^factoids$/ ) ;
eval {
my $ iter = $ factoids - > get_each ( 'type = text' , 'owner' ) ;
while ( defined ( my $ factoid = $ factoids - > get_next ( $ iter ) ) ) {
$ total + + ;
my ( $ owner ) = $ factoid - > { owner } =~ /^([^!]+)/ ;
if ( $ owner =~ /^$arguments$/i ) { $ i + + ; }
}
} ;
return "/msg $context->{nick} Error counting $context->{arguments}: $@" if $@ ;
return "I have $i text factoids." if $ arguments eq ".*" ;
if ( $ i > 0 ) {
my $ percent = int ( $ i / $ total * 100 ) ;
$ percent = 1 if $ percent == 0 ;
return "/say $arguments has submitted $i factoids out of $total ($percent" . "%)" ;
} else {
return "/say $arguments hasn't submitted any factoids" ;
}
}
2023-04-14 06:04:12 +02:00
sub log_factoid ($self, $channel, $trigger, $hostmask, $msg, $dont_save_undo = 0) {
2020-05-04 22:21:35 +02:00
$ channel = lc $ channel ;
$ trigger = lc $ trigger ;
my $ channel_path = $ channel ;
$ channel_path = 'global' if $ channel_path eq '.*' ;
my $ channel_path_safe = safe_filename $ channel_path ;
my $ trigger_safe = safe_filename $ trigger ;
my $ path = $ self - > { pbot } - > { registry } - > get_value ( 'general' , 'data_dir' ) . '/factlog' ;
open my $ fh , ">> $path/$trigger_safe.$channel_path_safe" or do {
$ self - > { pbot } - > { logger } - > log ( "Failed to open factlog for $channel/$trigger: $!\n" ) ;
return ;
} ;
my $ now = gettimeofday ;
my $ h = { ts = > $ now , hm = > $ hostmask , msg = > $ msg } ;
my $ json = encode_json $ h ;
print $ fh "$json\n" ;
close $ fh ;
return if $ dont_save_undo ;
my $ undos = eval { retrieve ( "$path/$trigger_safe.$channel_path_safe.undo" ) ; } ;
if ( not $ undos ) {
$ undos = {
idx = > - 1 ,
list = > []
} ;
}
my $ max_undos = $ self - > { pbot } - > { registry } - > get_value ( 'factoids' , 'max_undos' ) // 20 ;
if ( @ { $ undos - > { list } } > $ max_undos ) {
shift @ { $ undos - > { list } } ;
$ undos - > { idx } - - ;
}
if ( $ undos - > { idx } > - 1 and @ { $ undos - > { list } } > $ undos - > { idx } + 1 ) { splice @ { $ undos - > { list } } , $ undos - > { idx } + 1 ; }
2021-07-27 06:39:44 +02:00
push @ { $ undos - > { list } } , $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger ) ;
2020-05-04 22:21:35 +02:00
$ undos - > { idx } + + ;
eval { store $ undos , "$path/$trigger_safe.$channel_path_safe.undo" ; } ;
$ self - > { pbot } - > { logger } - > log ( "Error storing undo: $@\n" ) if $@ ;
}
2023-04-14 06:04:12 +02:00
sub find_factoid_with_optional_channel ($self, $from, $arguments, $command, %opts) {
2020-05-04 22:21:35 +02:00
my % default_opts = (
usage = > undef ,
explicit = > 0 ,
exact_channel = > 0
) ;
% opts = ( % default_opts , % opts ) ;
my $ arglist = $ self - > { pbot } - > { interpreter } - > make_args ( $ arguments ) ;
my ( $ from_chan , $ from_trigger , $ remaining_args ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ arglist , 3 , 0 , 1 ) ;
if ( not defined $ from_chan or ( not defined $ from_chan and not defined $ from_trigger ) ) {
return "Usage: $command [channel] <keyword>" if not $ opts { usage } ;
return $ opts { usage } ;
}
my $ needs_disambig ;
if ( not defined $ from_trigger ) {
# cmd arg1, so insert $from as channel
$ from_trigger = $ from_chan ;
$ from_chan = $ from ;
$ remaining_args = "" ;
#$needs_disambig = 1;
} else {
# cmd arg1 arg2 [...?]
if ( $ from_chan !~ /^#/ and lc $ from_chan ne 'global' and $ from_chan ne '.*' ) {
# not a channel or global, so must be a keyword
my $ keyword = $ from_chan ;
$ from_chan = $ from ;
$ from_trigger = $ keyword ;
( undef , $ remaining_args ) = $ self - > { pbot } - > { interpreter } - > split_args ( $ arglist , 2 , 0 , 1 ) ;
}
}
$ from_chan = '.*' if $ from_chan !~ /^#/ ;
$ from_chan = lc $ from_chan ;
my ( $ channel , $ trigger ) ;
if ( $ opts { exact_channel } == 1 ) {
2021-07-27 06:39:44 +02:00
( $ channel , $ trigger ) = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ from_chan , $ from_trigger , exact_channel = > 1 , exact_trigger = > 1 ) ;
2020-05-04 22:21:35 +02:00
if ( not defined $ channel ) {
$ from_chan = 'the global channel' if $ from_chan eq '.*' ;
return "/say $from_trigger not found in $from_chan." ;
}
} else {
2021-07-27 06:39:44 +02:00
my @ factoids = $ self - > { pbot } - > { factoids } - > { data } - > find ( $ from_chan , $ from_trigger , exact_trigger = > 1 ) ;
2020-05-04 22:21:35 +02:00
if ( not @ factoids or not $ factoids [ 0 ] ) {
if ( $ needs_disambig ) { return "/say $from_trigger not found" ; }
else {
$ from_chan = 'global channel' if $ from_chan eq '.*' ;
return "/say $from_trigger not found in $from_chan" ;
}
}
if ( @ factoids > 1 ) {
2020-06-06 00:12:52 +02:00
if ( $ needs_disambig or not grep { lc $ _ - > [ 0 ] eq $ from_chan } @ factoids ) {
2020-05-04 22:21:35 +02:00
unless ( $ opts { explicit } ) {
foreach my $ factoid ( @ factoids ) {
if ( $ factoid - > [ 0 ] eq '.*' ) { ( $ channel , $ trigger ) = ( $ factoid - > [ 0 ] , $ factoid - > [ 1 ] ) ; }
}
}
if ( not defined $ channel ) {
return
"/say $from_trigger found in multiple channels: "
. ( join ', ' , sort map { $ _ - > [ 0 ] eq '.*' ? 'global' : $ _ - > [ 0 ] } @ factoids )
. "; use `$command <channel> $from_trigger` to disambiguate." ;
}
} else {
foreach my $ factoid ( @ factoids ) {
2020-06-06 00:23:16 +02:00
if ( lc $ factoid - > [ 0 ] eq $ from_chan ) {
2020-05-04 22:21:35 +02:00
( $ channel , $ trigger ) = ( $ factoid - > [ 0 ] , $ factoid - > [ 1 ] ) ;
last ;
}
}
}
} else {
( $ channel , $ trigger ) = ( $ factoids [ 0 ] - > [ 0 ] , $ factoids [ 0 ] - > [ 1 ] ) ;
}
}
$ channel = '.*' if $ channel eq 'global' ;
$ from_chan = '.*' if $ channel eq 'global' ;
2020-06-06 00:12:52 +02:00
if ( $ opts { explicit } and $ channel =~ /^#/ and $ from_chan =~ /^#/ and lc $ channel ne $ from_chan ) {
2021-07-27 06:39:44 +02:00
my $ channel_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , '_name' ) ;
my $ trigger_name = $ self - > { pbot } - > { factoids } - > { data } - > { storage } - > get_data ( $ channel , $ trigger , '_name' ) ;
2020-05-04 22:21:35 +02:00
$ channel_name = 'global' if $ channel_name eq '.*' ;
$ trigger_name = "\"$trigger_name\"" if $ trigger_name =~ / / ;
return "/say $trigger_name belongs to $channel_name, not $from_chan. Please switch to or explicitly specify $channel_name." ;
}
return ( $ channel , $ trigger , $ remaining_args ) ;
}
2023-04-14 06:04:12 +02:00
sub hash_differences_as_string ($self, $old, $new) {
2020-05-04 22:21:35 +02:00
my @ exclude = qw/created_on last_referenced_in last_referenced_on ref_count ref_user edited_by edited_on/ ;
my % diff ;
foreach my $ key ( keys %$ new ) {
next if grep { $ key eq $ _ } @ exclude ;
if ( not exists $ old - > { $ key } or $ old - > { $ key } ne $ new - > { $ key } ) { $ diff { $ key } = $ new - > { $ key } ; }
}
foreach my $ key ( keys %$ old ) {
next if grep { $ key eq $ _ } @ exclude ;
if ( not exists $ new - > { $ key } ) { $ diff { "deleted $key" } = undef ; }
}
return "No change." if not keys % diff ;
my $ changes = "" ;
my $ comma = "" ;
foreach my $ key ( sort keys % diff ) {
2023-04-14 06:04:12 +02:00
if ( defined $ diff { $ key } ) {
$ changes . = "$comma$key => $diff{$key}" ;
} else {
$ changes . = "$comma$key" ;
}
2020-05-04 22:21:35 +02:00
$ comma = ", " ;
}
return $ changes ;
}
2023-04-14 06:04:12 +02:00
sub list_undo_history ($self, $undos, $start_from = undef) {
2020-05-04 22:21:35 +02:00
$ start_from - - if defined $ start_from ;
$ start_from = 0 if not defined $ start_from or $ start_from < 0 ;
my $ result = "" ;
if ( $ start_from > @ { $ undos - > { list } } ) {
2023-04-14 06:04:12 +02:00
if ( @ { $ undos - > { list } } == 1 ) {
return "But there is only one revision available." ;
} else {
return "But there are only " . @ { $ undos - > { list } } . " revisions available." ;
}
2020-05-04 22:21:35 +02:00
}
if ( $ start_from == 0 ) {
2023-04-14 06:04:12 +02:00
if ( $ undos - > { idx } == 0 ) {
$ result . = "*1*: " ;
} else {
$ result . = "1: " ;
}
2020-05-04 22:21:35 +02:00
$ result . = $ self - > hash_differences_as_string ( { } , $ undos - > { list } - > [ 0 ] ) . ";\n\n" ;
$ start_from + + ;
}
for ( my $ i = $ start_from ; $ i < @ { $ undos - > { list } } ; $ i + + ) {
2023-04-14 06:04:12 +02:00
if ( $ i == $ undos - > { idx } ) {
$ result . = "*" . ( $ i + 1 ) . "*: " ;
} else {
$ result . = ( $ i + 1 ) . ": " ;
}
2020-05-04 22:21:35 +02:00
$ result . = $ self - > hash_differences_as_string ( $ undos - > { list } - > [ $ i - 1 ] , $ undos - > { list } - > [ $ i ] ) ;
$ result . = ";\n\n" ;
}
return $ result ;
}
2010-03-22 08:33:44 +01:00
1 ;