From c75be8b4b012e550bd0fd8f3b819a7a55ca9bb70 Mon Sep 17 00:00:00 2001 From: Pragmatic Software Date: Mon, 26 Jul 2021 21:39:44 -0700 Subject: [PATCH] Refactor PBot::Core::Factoids into PBot::Core::Factoids::* --- data/commands | 1 - lib/PBot/Core.pm | 2 +- lib/PBot/Core/Commands.pm | 2 +- lib/PBot/Core/Commands/Factoids.pm | 176 ++-- lib/PBot/Core/Commands/Help.pm | 8 +- lib/PBot/Core/Commands/Misc.pm | 20 +- lib/PBot/Core/Commands/Modules.pm | 6 +- lib/PBot/Core/Commands/Reload.pm | 2 +- lib/PBot/Core/Factoids.pm | 1498 +--------------------------- lib/PBot/Core/Interpreter.pm | 10 +- lib/PBot/Core/Modules.pm | 10 +- lib/PBot/Core/ProcessManager.pm | 6 +- lib/PBot/Plugin/Plang.pm | 10 +- 13 files changed, 159 insertions(+), 1592 deletions(-) diff --git a/data/commands b/data/commands index 708492cb..2285b8bd 100644 --- a/data/commands +++ b/data/commands @@ -153,7 +153,6 @@ "requires_cap" : "1" }, "export" : { - "background-process" : "1", "help" : "Exports specified list to HTML file. See https://github.com/pragma-/pbot/blob/master/doc/Admin.md#export", "requires_cap" : 1 }, diff --git a/lib/PBot/Core.pm b/lib/PBot/Core.pm index eb0e0b9b..3294ca86 100644 --- a/lib/PBot/Core.pm +++ b/lib/PBot/Core.pm @@ -207,7 +207,7 @@ sub initialize { # register command/factoid interpreters $self->{interpreter}->register(sub { $self->{commands}->interpreter(@_) }); - $self->{interpreter}->register(sub { $self->{factoids}->interpreter(@_) }); + $self->{interpreter}->register(sub { $self->{factoids}->{interpreter}->interpreter(@_) }); # give botowner all capabilities # -- this must happen last after all modules have registered their capabilities -- diff --git a/lib/PBot/Core/Commands.pm b/lib/PBot/Core/Commands.pm index 0d08a8e3..045bde8f 100644 --- a/lib/PBot/Core/Commands.pm +++ b/lib/PBot/Core/Commands.pm @@ -169,7 +169,7 @@ sub interpreter { } unless ($self->get_meta($keyword, 'dont-replace-pronouns')) { - $context->{arguments} = $self->{pbot}->{factoids}->expand_factoid_vars($context, $context->{arguments}); + $context->{arguments} = $self->{pbot}->{factoids}->{variables}->expand_factoid_vars($context, $context->{arguments}); $context->{arglist} = $self->{pbot}->{interpreter}->make_args($context->{arguments}); } diff --git a/lib/PBot/Core/Commands/Factoids.pm b/lib/PBot/Core/Commands/Factoids.pm index 78b72b91..45769513 100644 --- a/lib/PBot/Core/Commands/Factoids.pm +++ b/lib/PBot/Core/Commands/Factoids.pm @@ -75,7 +75,7 @@ sub cmd_call_factoid { if (not defined $chan or not defined $keyword) { return "Usage: fact [arguments]"; } - my ($channel, $trigger) = $self->{pbot}->{factoids}->find_factoid($chan, $keyword, arguments => $args, exact_channel => 1, exact_trigger => 1); + my ($channel, $trigger) = $self->{pbot}->{factoids}->{data}->find($chan, $keyword, arguments => $args, exact_channel => 1, exact_trigger => 1); if (not defined $trigger) { return "No such factoid $keyword exists for $chan"; } @@ -85,7 +85,7 @@ sub cmd_call_factoid { $context->{arguments} = $args; $context->{root_keyword} = $trigger; - return $self->{pbot}->{factoids}->interpreter($context); + return $self->{pbot}->{factoids}->{interpreter}->interpreter($context); } sub cmd_factundo { @@ -141,8 +141,8 @@ sub cmd_factundo { my $path = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/factlog'; my $undos = eval { retrieve("$path/$trigger_safe.$channel_path_safe.undo"); }; - my $channel_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, '_name'); - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, '_name'); + 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'); $channel_name = 'global' if $channel_name eq '.*'; $trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /; @@ -154,7 +154,7 @@ sub cmd_factundo { return $self->list_undo_history($undos, $list_undos); } - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; my $userinfo = $self->{pbot}->{users}->loggedin($channel, $context->{hostmask}); 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'); @@ -185,7 +185,7 @@ sub cmd_factundo { } } - $self->{pbot}->{factoids}->{storage}->add($channel, $trigger, $undos->{list}->[$undos->{idx}], 0, 1); + $self->{pbot}->{factoids}->{data}->{storage}->add($channel, $trigger, $undos->{list}->[$undos->{idx}], 0, 1); my $changes = $self->hash_differences_as_string($undos->{list}->[$undos->{idx} + 1], $undos->{list}->[$undos->{idx}]); $self->log_factoid($channel, $trigger, $context->{hostmask}, "reverted (undo): $changes", 1); @@ -231,8 +231,8 @@ sub cmd_factredo { my $channel_path_safe = safe_filename $channel_path; my $trigger_safe = safe_filename $trigger; - my $channel_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, '_name'); - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, '_name'); + 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'); $channel_name = 'global' if $channel_name eq '.*'; $trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /; @@ -246,7 +246,7 @@ sub cmd_factredo { return $self->list_undo_history($undos, $list_undos); } - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; my $userinfo = $self->{pbot}->{users}->loggedin($channel, $context->{hostmask}); 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'); @@ -276,7 +276,7 @@ sub cmd_factredo { $self->{pbot}->{logger}->log("Error storing undo: $@\n") if $@; } - $self->{pbot}->{factoids}->{storage}->add($channel, $trigger, $undos->{list}->[$undos->{idx}], 0, 1); + $self->{pbot}->{factoids}->{data}->{storage}->add($channel, $trigger, $undos->{list}->[$undos->{idx}], 0, 1); my $changes = $self->hash_differences_as_string($undos->{list}->[$undos->{idx} - 1], $undos->{list}->[$undos->{idx}]); @@ -294,7 +294,7 @@ sub cmd_factset { return $channel if not defined $trigger; # if $trigger is not defined, $channel is an error message - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, '_name'); + my $trigger_name = $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger, '_name'); $trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /; @@ -304,7 +304,7 @@ sub cmd_factset { $channel = '.*' if $channel !~ /^#/; - my ($owner_channel, $owner_trigger) = $self->{pbot}->{factoids}->find_factoid($channel, $trigger, exact_channel => 1, exact_trigger => 1); + my ($owner_channel, $owner_trigger) = $self->{pbot}->{factoids}->{data}->find($channel, $trigger, exact_channel => 1, exact_trigger => 1); my $userinfo; @@ -328,7 +328,7 @@ sub cmd_factset { } if (defined $value and !$self->{pbot}->{capabilities}->userhas($userinfo, 'admin') - and $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, 'locked')) + and $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger, 'locked')) { return "/say $trigger_name is locked; unlock before setting."; } @@ -342,10 +342,10 @@ sub cmd_factset { return "Your user account must have the $value capability to set cap-override to $value."; } - $self->{pbot}->{factoids}->{storage}->set($channel, $trigger, 'locked', '1'); + $self->{pbot}->{factoids}->{data}->{storage}->set($channel, $trigger, 'locked', '1'); } - if (lc $key eq 'locked' and $self->{pbot}->{factoids}->{storage}->exists($channel, $trigger, 'cap-override')) { + if (lc $key eq 'locked' and $self->{pbot}->{factoids}->{data}->{storage}->exists($channel, $trigger, 'cap-override')) { 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."; } @@ -353,7 +353,7 @@ sub cmd_factset { } if (defined $owner_channel) { - my $factoid = $self->{pbot}->{factoids}->{storage}->get_data($owner_channel, $owner_trigger); + my $factoid = $self->{pbot}->{factoids}->{data}->{storage}->get_data($owner_channel, $owner_trigger); my $owner; my $mask; @@ -376,7 +376,7 @@ sub cmd_factset { } } - my $result = $self->{pbot}->{factoids}->{storage}->set($channel, $trigger, $key, $value); + my $result = $self->{pbot}->{factoids}->{data}->{storage}->set($channel, $trigger, $key, $value); if (defined $value and $result =~ m/set to/) { $self->log_factoid($channel, $trigger, $context->{hostmask}, "set $key to $value"); } @@ -397,7 +397,7 @@ sub cmd_factunset { return $usage if not length $key; - my ($owner_channel, $owner_trigger) = $self->{pbot}->{factoids}->find_factoid($channel, $trigger, exact_channel => 1, exact_trigger => 1); + my ($owner_channel, $owner_trigger) = $self->{pbot}->{factoids}->{data}->find($channel, $trigger, exact_channel => 1, exact_trigger => 1); my $userinfo; @@ -419,18 +419,18 @@ sub cmd_factunset { } } - if ($self->{pbot}->{factoids}->{storage}->exists($channel, $trigger, 'cap-override')) { + if ($self->{pbot}->{factoids}->{data}->{storage}->exists($channel, $trigger, 'cap-override')) { if (lc $key eq 'locked') { if ($self->{pbot}->{capabilities}->userhas($userinfo, 'botowner')) { - $self->{pbot}->{factoids}->{storage}->unset($channel, $trigger, 'cap-override', 1); + $self->{pbot}->{factoids}->{data}->{storage}->unset($channel, $trigger, 'cap-override', 1); } else { return "You cannot unlock this factoid because it has a cap-override. Remove the override first."; } } } - my $channel_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, '_name'); - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, '_name'); + 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'); $channel_name = 'global' if $channel_name eq '.*'; $trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /; @@ -438,7 +438,7 @@ sub cmd_factunset { my $oldvalue; if (defined $owner_channel) { - my $factoid = $self->{pbot}->{factoids}->{storage}->get_data($owner_channel, $owner_trigger); + my $factoid = $self->{pbot}->{factoids}->{data}->{storage}->get_data($owner_channel, $owner_trigger); my ($owner) = $factoid->{'owner'} =~ m/([^!]+)/; if ($key ne 'action_with_args' and lc $context->{nick} ne lc $owner @@ -447,7 +447,7 @@ sub cmd_factunset { return "You are not the owner of $trigger_name."; } - $oldvalue = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, $key); + $oldvalue = $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger, $key); } if (not defined $oldvalue) { @@ -460,7 +460,7 @@ sub cmd_factunset { } } - my $result = $self->{pbot}->{factoids}->{storage}->unset($channel, $trigger, $key); + my $result = $self->{pbot}->{factoids}->{data}->{storage}->unset($channel, $trigger, $key); if ($result =~ m/unset/) { $self->log_factoid($channel, $trigger, $context->{hostmask}, "unset $key (value: $oldvalue)"); @@ -499,18 +499,18 @@ sub cmd_factmove { 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}->find_factoid($src_channel, $source, exact_channel => 1, exact_trigger => 1); + 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}->{storage}->get_data($found_src_channel, '_name'); - my $source_trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($found_src_channel, $found_source, '_name'); + 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}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; my ($owner) = $factoids->get_data($found_src_channel, $found_source, 'owner') =~ m/([^!]+)/; if ((lc $context->{nick} ne lc $owner) and (not $self->{pbot}->{users}->loggedin_admin($found_src_channel, $context->{hostmask}))) { @@ -523,7 +523,7 @@ sub cmd_factmove { return "/say $source_trigger_name is locked; unlock before moving."; } - my ($found_target_channel, $found_target) = $self->{pbot}->{factoids}->find_factoid($target_channel, $target, exact_channel => 1, exact_trigger => 1); + 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'); @@ -533,7 +533,7 @@ sub cmd_factmove { return "Target factoid $target_trigger_name already exists in channel $target_channel_name."; } - my ($overchannel, $overtrigger) = $self->{pbot}->{factoids}->find_factoid('.*', $target, exact_channel => 1, exact_trigger => 1); + 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'); @@ -587,25 +587,25 @@ sub cmd_factalias { 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."; } - my ($channel, $alias_trigger) = $self->{pbot}->{factoids}->find_factoid($chan, $alias, exact_channel => 1, exact_trigger => 1); + my ($channel, $alias_trigger) = $self->{pbot}->{factoids}->{data}->find($chan, $alias, exact_channel => 1, exact_trigger => 1); if (defined $alias_trigger) { - my $alias_channel_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, '_name'); - my $alias_trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $alias_trigger, '_name'); + 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'); $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."; } - my ($overchannel, $overtrigger) = $self->{pbot}->{factoids}->find_factoid('.*', $alias, exact_channel => 1, exact_trigger => 1); - if (defined $overtrigger and $self->{pbot}->{factoids}->{storage}->get_data('.*', $overtrigger, 'nooverride')) { - my $override_trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($overchannel, $overtrigger, '_name'); + 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'); $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."; } - $self->{pbot}->{factoids}->add_factoid('text', $chan, $context->{hostmask}, $alias, "/call $command"); + $self->{pbot}->{factoids}->{data}->add('text', $chan, $context->{hostmask}, $alias, "/call $command"); $self->{pbot}->{logger}->log("$context->{hostmask} [$chan] aliased $alias => $command\n"); return "/say $alias aliases `$command` for " . ($chan eq '.*' ? 'the global channel' : $chan); } @@ -621,8 +621,8 @@ sub cmd_add_regex { if (not defined $text) { my @regexes; - my $iter = $self->{pbot}->{factoids}->{storage}->get_each('type = regex', "index1 = $keyword", 'index2', '_sort = index2'); - while (defined (my $factoid = $self->{pbot}->{factoids}->{storage}->get_next($iter))) { + 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))) { push @regexes, $factoid->{index2}; } $text = join '; ', @regexes; @@ -631,19 +631,20 @@ sub cmd_add_regex { } my $trigger; - ($channel, $trigger) = $self->{pbot}->{factoids}->find_factoid($channel, $keyword, exact_channel => 1, exact_trigger => 1); + ($channel, $trigger) = $self->{pbot}->{factoids}->{data}->find($channel, $keyword, exact_channel => 1, exact_trigger => 1); if (defined $trigger) { return "/say $trigger already exists for channel $channel."; } - $self->{pbot}->{factoids}->add_factoid('regex', $channel, $context->{hostmask}, $keyword, $text); + $self->{pbot}->{factoids}->{data}->add('regex', $channel, $context->{hostmask}, $keyword, $text); $self->{pbot}->{logger}->log("$context->{hostmask} added regex [$keyword] => [$text]\n"); return "/say $keyword added."; } sub cmd_factadd { my ($self, $context) = @_; + my ($from_chan, $keyword, $text, $force); my @arglist = @{$context->{arglist}}; @@ -656,8 +657,11 @@ sub cmd_factadd { } # check if this is an optional channel argument - if ($arglist[0] =~ m/(?:^#|^global$|^\.\*$)/i) { $from_chan = $self->{pbot}->{interpreter}->shift_arg(\@arglist); } - else { $from_chan = $context->{from}; } + if ($arglist[0] =~ m/(?:^#|^global$|^\.\*$)/i) { + $from_chan = $self->{pbot}->{interpreter}->shift_arg(\@arglist); + } else { + $from_chan = $context->{from}; + } # check for -f again since we also allow it to appear after the channel argument if ($arglist[0] eq '-f') { @@ -677,7 +681,9 @@ sub cmd_factadd { my ($url) = $self->{pbot}->{interpreter}->split_args(\@arglist, 1); # FIXME: move this to registry - if ($url !~ m/^https?:\/\/(?:sprunge.us|ix.io)\/\w+$/) { return "Invalid URL: acceptable URLs are: http://sprunge.us, http://ix.io"; } + if ($url !~ m/^https?:\/\/(?:sprunge.us|ix.io)\/\w+$/) { + return "Invalid URL: acceptable URLs are: http://sprunge.us, http://ix.io"; + } # create a UserAgent my $ua = LWP::UserAgent->new(timeout => 10); @@ -686,11 +692,16 @@ sub cmd_factadd { my $response = $ua->get($url); # process the response - if ($response->is_success) { $text = $response->decoded_content; } - else { return "Failed to get URL: " . $response->status_line; } + if ($response->is_success) { + $text = $response->decoded_content; + } else { + return "Failed to get URL: " . $response->status_line; + } } else { # check for optional "is" and discard - if (lc $arglist[0] eq 'is') { $self->{pbot}->{interpreter}->shift_arg(\@arglist); } + if (lc $arglist[0] eq 'is') { + $self->{pbot}->{interpreter}->shift_arg(\@arglist); + } # and the text is the remaining arguments with quotes preserved ($text) = $self->{pbot}->{interpreter}->split_args(\@arglist, 1, 0, 1); @@ -703,48 +714,59 @@ sub cmd_factadd { $from_chan = '.*' if $from_chan !~ /^#/; - 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."; } + 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."; + } - 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."; } + 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."; + } $from_chan = '.*' if lc $from_chan eq 'global'; $from_chan = '.*' if not $from_chan =~ m/^#/; my $keyword_text = $keyword =~ / / ? "\"$keyword\"" : $keyword; - my ($channel, $trigger) = $self->{pbot}->{factoids}->find_factoid($from_chan, $keyword, exact_channel => 1, exact_trigger => 1); + my ($channel, $trigger) = $self->{pbot}->{factoids}->{data}->find($from_chan, $keyword, exact_channel => 1, exact_trigger => 1); + if (defined $trigger) { - my $channel_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, '_name'); - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, '_name'); + 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'); + $channel_name = 'global' if $channel_name eq '.*'; $trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /; if (not $force) { return "/say $trigger_name already exists for $channel_name."; } else { - my $factoids = $self->{pbot}->{factoids}->{storage}; - if ($factoids->get_data($channel, $trigger, 'locked')) { return "/say $trigger_name is locked; unlock before overwriting."; } + if ($self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger, 'locked')) { + return "/say $trigger_name is locked; unlock before overwriting."; + } } } - ($channel, $trigger) = $self->{pbot}->{factoids}->find_factoid('.*', $keyword, exact_channel => 1, exact_trigger => 1); - if (defined $trigger and $self->{pbot}->{factoids}->{storage}->get_data('.*', $trigger, 'nooverride')) { - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, '_name'); + ($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'); $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) . "."; } if ($self->{pbot}->{commands}->exists($keyword)) { return "/say $keyword_text already exists as a built-in command."; } - $self->{pbot}->{factoids}->add_factoid('text', $from_chan, $context->{hostmask}, $keyword, $text); + $self->{pbot}->{factoids}->{data}->add('text', $from_chan, $context->{hostmask}, $keyword, $text); + $self->{pbot}->{logger}->log("$context->{hostmask} added [$from_chan] $keyword_text => $text\n"); - $self->log_factoid($channel, $trigger, $context->{hostmask}, "created"); + + $self->log_factoid($from_chan, $keyword, $context->{hostmask}, "created"); + return "/say $keyword_text added to " . ($from_chan eq '.*' ? 'global channel' : $from_chan) . "."; } sub cmd_factrem { my ($self, $context) = @_; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; my ($from_chan, $from_trig) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2); @@ -780,12 +802,12 @@ sub cmd_factrem { $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); - return '/say '. $self->{pbot}->{factoids}->remove_factoid($channel, $trigger); + return '/say '. $self->{pbot}->{factoids}->{data}->remove($channel, $trigger); } sub cmd_factshow { my ($self, $context) = @_; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; $context->{preserve_whitespace} = 1; my $usage = "Usage: factshow [-p] [channel] ; -p to paste"; return $usage if not length $context->{arguments}; @@ -909,7 +931,7 @@ sub cmd_factlog { sub cmd_factinfo { my ($self, $context) = @_; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; my ($chan, $trig) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2); if (not defined $trig) { @@ -1006,7 +1028,7 @@ sub cmd_factfind { my $usage = "Usage: factfind [-channel channel] [-owner regex] [-editby regex] [-refby regex] [-regex] [text]"; return $usage if not length $arguments; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; 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; @@ -1111,7 +1133,7 @@ sub cmd_factfind { sub cmd_factchange { my ($self, $context) = @_; - my $factoids_data = $self->{pbot}->{factoids}->{storage}; + my $factoids_data = $self->{pbot}->{factoids}->{data}->{storage}; my ($channel, $trigger, $keyword, $delim, $tochange, $changeto, $modifier, $url); $context->{preserve_whitespace} = 1; @@ -1168,7 +1190,7 @@ sub cmd_factchange { } my ($from_trigger, $from_chan) = ($keyword, $channel); - my @factoids = $self->{pbot}->{factoids}->find_factoid($from_chan, $keyword, exact_trigger => 1); + my @factoids = $self->{pbot}->{factoids}->{data}->find($from_chan, $keyword, exact_trigger => 1); if (not @factoids or not $factoids[0]) { $from_chan = 'global channel' if $from_chan eq '.*'; @@ -1195,8 +1217,8 @@ sub cmd_factchange { if (not defined $trigger) { return "/say $keyword not found in channel $from_chan."; } - my $channel_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, '_name'); - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, '_name'); + 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'); $channel_name = 'global' if $channel_name eq '.*'; $trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /; @@ -1297,7 +1319,7 @@ sub cmd_factchange { sub cmd_top20 { my ($self, $context) = @_; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; my %hash = (); my $text = ""; my $i = 0; @@ -1357,7 +1379,7 @@ sub cmd_top20 { sub cmd_histogram { my ($self, $context) = @_; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; my %owners; my $factoid_count = 0; @@ -1382,7 +1404,7 @@ sub cmd_histogram { sub cmd_count { my ($self, $context) = @_; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; my $i = 0; my $total = 0; @@ -1456,7 +1478,7 @@ sub log_factoid { if ($undos->{idx} > -1 and @{$undos->{list}} > $undos->{idx} + 1) { splice @{$undos->{list}}, $undos->{idx} + 1; } - push @{$undos->{list}}, $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger); + push @{$undos->{list}}, $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger); $undos->{idx}++; eval { store $undos, "$path/$trigger_safe.$channel_path_safe.undo"; }; @@ -1506,14 +1528,14 @@ sub find_factoid_with_optional_channel { my ($channel, $trigger); if ($opts{exact_channel} == 1) { - ($channel, $trigger) = $self->{pbot}->{factoids}->find_factoid($from_chan, $from_trigger, exact_channel => 1, exact_trigger => 1); + ($channel, $trigger) = $self->{pbot}->{factoids}->{data}->find($from_chan, $from_trigger, exact_channel => 1, exact_trigger => 1); if (not defined $channel) { $from_chan = 'the global channel' if $from_chan eq '.*'; return "/say $from_trigger not found in $from_chan."; } } else { - my @factoids = $self->{pbot}->{factoids}->find_factoid($from_chan, $from_trigger, exact_trigger => 1); + my @factoids = $self->{pbot}->{factoids}->{data}->find($from_chan, $from_trigger, exact_trigger => 1); if (not @factoids or not $factoids[0]) { if ($needs_disambig) { return "/say $from_trigger not found"; } @@ -1553,8 +1575,8 @@ sub find_factoid_with_optional_channel { $from_chan = '.*' if $channel eq 'global'; if ($opts{explicit} and $channel =~ /^#/ and $from_chan =~ /^#/ and lc $channel ne $from_chan) { - my $channel_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, '_name'); - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, '_name'); + 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'); $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."; diff --git a/lib/PBot/Core/Commands/Help.pm b/lib/PBot/Core/Commands/Help.pm index 5327bbff..d43ac012 100644 --- a/lib/PBot/Core/Commands/Help.pm +++ b/lib/PBot/Core/Commands/Help.pm @@ -67,7 +67,7 @@ sub cmd_help { } # find factoids - my @factoids = $self->{pbot}->{factoids}->find_factoid($channel_arg, $keyword, exact_trigger => 1); + my @factoids = $self->{pbot}->{factoids}->{data}->find($channel_arg, $keyword, exact_trigger => 1); if (not @factoids or not $factoids[0]) { # nothing found @@ -96,8 +96,8 @@ sub cmd_help { } # get canonical channel and trigger names with original typographical casing - my $channel_name = $self->{pbot}->{factoids}->{storage}->get_key_name($channel); - my $trigger_name = $self->{pbot}->{factoids}->{storage}->get_key_name($channel, $trigger); + my $channel_name = $self->{pbot}->{factoids}->{data}->{storage}->get_key_name($channel); + my $trigger_name = $self->{pbot}->{factoids}->{data}->{storage}->get_key_name($channel, $trigger); # prettify channel name if it's ".*" if ($channel_name eq '.*') { @@ -110,7 +110,7 @@ sub cmd_help { } # get factoid's `help` metadata - my $help = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, 'help'); + my $help = $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger, 'help'); # return immediately if no help text if (not defined $help or not length $help) { diff --git a/lib/PBot/Core/Commands/Misc.pm b/lib/PBot/Core/Commands/Misc.pm index a4ae0b73..7f350fba 100644 --- a/lib/PBot/Core/Commands/Misc.pm +++ b/lib/PBot/Core/Commands/Misc.pm @@ -81,11 +81,11 @@ sub cmd_list { if ($context->{arguments} =~ /^modules$/i) { $text = 'Loaded modules: '; - foreach my $channel (sort $self->{pbot}->{factoids}->{storage}->get_keys) { - foreach my $command (sort $self->{pbot}->{factoids}->{storage}->get_keys($channel)) { + foreach my $channel (sort $self->{pbot}->{factoids}->{data}->{storage}->get_keys) { + foreach my $command (sort $self->{pbot}->{factoids}->{data}->{storage}->get_keys($channel)) { next if $command eq '_name'; - if ($self->{pbot}->{factoids}->{storage}->get_data($channel, $command, 'type') eq 'module') { - $text .= $self->{pbot}->{factoids}->{storage}->get_data($channel, $command, '_name') . ' '; + if ($self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $command, 'type') eq 'module') { + $text .= $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $command, '_name') . ' '; } } } @@ -128,8 +128,16 @@ sub cmd_die { sub cmd_export { my ($self, $context) = @_; - return "Usage: export " if not length $context->{arguments}; - if ($context->{arguments} =~ /^factoids$/i) { return $self->{pbot}->{factoids}->export_factoids; } + + my $usage = "Usage: export factoids"; + + return $usage if not length $context->{arguments}; + + if ($context->{arguments} =~ /^factoids$/i) { + return $self->{pbot}->{factoids}->{exporter}->export; + } + + return $usage; } sub cmd_eval { diff --git a/lib/PBot/Core/Commands/Modules.pm b/lib/PBot/Core/Commands/Modules.pm index 542bbab4..fa9f7541 100644 --- a/lib/PBot/Core/Commands/Modules.pm +++ b/lib/PBot/Core/Commands/Modules.pm @@ -28,13 +28,13 @@ sub cmd_load { return "Usage: load " if not defined $module; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; if ($factoids->exists('.*', $keyword)) { return 'There is already a keyword named ' . $factoids->get_data('.*', $keyword, '_name') . '.'; } - $self->{pbot}->{factoids}->add_factoid('module', '.*', $context->{hostmask}, $keyword, $module, 1); + $self->{pbot}->{factoids}->{data}->add_factoid('module', '.*', $context->{hostmask}, $keyword, $module, 1); $factoids->set('.*', $keyword, 'add_nick', 1, 1); $factoids->set('.*', $keyword, 'nooverride', 1); @@ -51,7 +51,7 @@ sub cmd_unload { return "Usage: unload " if not defined $module; - my $factoids = $self->{pbot}->{factoids}->{storage}; + my $factoids = $self->{pbot}->{factoids}->{data}->{storage}; if (not $factoids->exists('.*', $module)) { return "/say $module not found."; diff --git a/lib/PBot/Core/Commands/Reload.pm b/lib/PBot/Core/Commands/Reload.pm index 76722368..b38680e0 100644 --- a/lib/PBot/Core/Commands/Reload.pm +++ b/lib/PBot/Core/Commands/Reload.pm @@ -72,7 +72,7 @@ sub cmd_reload { }, 'factoids' => sub { - $self->{pbot}->{factoids}->load_factoids; + $self->{pbot}->{factoids}->{data}->load; return "Factoids reloaded."; } ); diff --git a/lib/PBot/Core/Factoids.pm b/lib/PBot/Core/Factoids.pm index 0886029d..40d81342 100644 --- a/lib/PBot/Core/Factoids.pm +++ b/lib/PBot/Core/Factoids.pm @@ -1,6 +1,7 @@ # File: Factoids.pm # -# Purpose: Provides functionality for factoids. +# Purpose: Provides implementation of PBot factoids. Factoids provide the +# foundation for most user-submitted commands, as well as aliases, etc. # SPDX-FileCopyrightText: 2021 Pragmatic Software # SPDX-License-Identifier: MIT @@ -10,1495 +11,32 @@ use parent 'PBot::Core::Class'; use PBot::Imports; -use HTML::Entities; -use Time::HiRes qw(gettimeofday); -use Time::Duration qw(duration); -use POSIX qw(strftime); -use Text::ParseWords; -use JSON; - -use PBot::Core::Utils::Indefinite; -use PBot::Core::Utils::ValidateString; - -our %factoid_metadata = ( - 'action' => 'TEXT', - 'action_with_args' => 'TEXT', - 'add_nick' => 'INTEGER', - 'allow_empty_args' => 'INTEGER', - 'background-process' => 'INTEGER', - 'cap-override' => 'TEXT', - 'created_on' => 'NUMERIC', - 'dont-protect-self' => 'INTEGER', - 'dont-replace-pronouns' => 'INTEGER', - 'edited_by' => 'TEXT', - 'edited_on' => 'NUMERIC', - 'enabled' => 'INTEGER', - 'help' => 'TEXT', - 'interpolate' => 'INTEGER', - 'keyword_override' => 'TEXT', - 'last_referenced_in' => 'TEXT', - 'last_referenced_on' => 'NUMERIC', - 'locked' => 'INTEGER', - 'locked_to_channel' => 'INTEGER', - 'no_keyword_override' => 'INTEGER', - 'noembed' => 'INTEGER', - 'nooverride' => 'INTEGER', - 'owner' => 'TEXT', - 'persist-key' => 'INTEGER', - 'preserve_whitespace' => 'INTEGER', - 'process-timeout' => 'INTEGER', - 'rate_limit' => 'INTEGER', - 'ref_count' => 'INTEGER', - 'ref_user' => 'TEXT', - 'require_explicit_args' => 'INTEGER', - 'requires_arguments' => 'INTEGER', - 'type' => 'TEXT', - 'unquote_spaces' => 'INTEGER', - 'usage' => 'TEXT', - 'use_output_queue' => 'INTEGER', - 'workdir' => 'TEXT', -); +use PBot::Core::Factoids::Code; +use PBot::Core::Factoids::Data; +use PBot::Core::Factoids::Exporter; +use PBot::Core::Factoids::Interpreter; +use PBot::Core::Factoids::Modifiers; +use PBot::Core::Factoids::Selectors; +use PBot::Core::Factoids::Variables; sub initialize { my ($self, %conf) = @_; - my $filename = $conf{filename}; - $self->{pbot} = $self->{pbot}; + $self->{data} = PBot::Core::Factoids::Data->new(%conf); + $self->{data}->load; + + $self->{code} = PBot::Core::Factoids::Code->new (%conf); + $self->{exporter} = PBot::Core::Factoids::Exporter->new (%conf); + $self->{interpreter} = PBot::Core::Factoids::Interpreter->new (%conf); + $self->{modifiers} = PBot::Core::Factoids::Modifiers->new (%conf); + $self->{selectors} = PBot::Core::Factoids::Selectors->new (%conf); + $self->{variables} = PBot::Core::Factoids::Variables->new (%conf); - $self->{storage} = PBot::Core::Storage::DualIndexSQLiteObject->new( - pbot => $self->{pbot}, - name => 'Factoids', - filename => $filename, - ); $self->{pbot}->{registry}->add_default('text', 'factoids', 'default_rate_limit', 15); $self->{pbot}->{registry}->add_default('text', 'factoids', 'max_name_length', 100); $self->{pbot}->{registry}->add_default('text', 'factoids', 'max_content_length', 1024 * 8); $self->{pbot}->{registry}->add_default('text', 'factoids', 'max_channel_length', 20); - - $self->load_factoids; - - # save and export factoids at exit - $self->{pbot}->{atexit}->register(sub { $self->save_factoids(1) }); -} - -sub load_factoids { - my ($self) = @_; - $self->{storage}->load; - $self->{storage}->create_metadata(\%factoid_metadata); -} - -sub save_factoids { - my ($self, $export) = @_; - $self->{storage}->save; - $self->export_factoids if $export; -} - -sub get_meta { - my ($self, $channel, $trigger, $key) = @_; - $channel = lc $channel; - $trigger = lc $trigger; - my ($chan, $trig) = $self->find_factoid($channel, $trigger, exact_channel => 1, exact_trigger => 1); - return undef if not defined $chan; - return $self->{storage}->get_data($chan, $trig, $key); -} - -sub add_factoid { - my ($self, $type, $channel, $owner, $trigger, $action, $dont_save) = @_; - $type = lc $type; - $channel = '.*' if $channel !~ /^#/; - - my $data; - if ($self->{storage}->exists($channel, $trigger)) { - # only update action field if force-adding it through factadd -f - $data = $self->{storage}->get_data($channel, $trigger); - - $data->{action} = $action; - $data->{type} = $type; - } else { - $data = { - enabled => 1, - type => $type, - action => $action, - owner => $owner, - created_on => scalar gettimeofday, - ref_count => 0, - ref_user => "nobody", - rate_limit => $self->{pbot}->{registry}->get_value('factoids', 'default_rate_limit') - }; - } - - $self->{storage}->add($channel, $trigger, $data, $dont_save); - $self->{pbot}->{commands}->{modules}->{Factoids}->log_factoid($channel, $trigger, $owner, "created: $action") unless $dont_save; -} - -sub remove_factoid { - my $self = shift; - my ($channel, $trigger) = @_; - $channel = '.*' if $channel !~ /^#/; - return $self->{storage}->remove($channel, $trigger); -} - -sub export_factoids { - my $self = shift; - my $filename; - - if (@_) { $filename = shift; } - else { $filename = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/factoids.html'; } - return if not defined $filename; - - return if not defined $self->{storage}->{dbh}; - - $self->{pbot}->{logger}->log("Exporting factoids to $filename\n"); - - open FILE, "> $filename" or return "Could not open export path."; - - my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick'); - my $time = localtime; - print FILE "\n\n"; - print FILE '' . "\n"; - print FILE '' . "\n"; - print FILE '' . "\n"; - print FILE "\nLast updated at $time\n"; - print FILE "

$botnick\'s factoids

\n"; - - my $i = 0; - my $table_id = 1; - - foreach my $channel (sort $self->{storage}->get_keys) { - next if not $self->{storage}->get_keys($channel); - my $chan = $self->{storage}->get_data($channel, '_name'); - $chan = 'global' if $chan eq '.*'; - - print FILE "" . encode_entities($chan) . "
\n"; - } - - foreach my $channel (sort $self->{storage}->get_keys) { - next if not $self->{storage}->get_keys($channel); - my $chan = $self->{storage}->get_data($channel, '_name'); - $chan = 'global' if $chan eq '.*'; - print FILE "\n"; - print FILE "
\n

" . encode_entities($chan) . "

\n
\n"; - print FILE "\n"; - print FILE "\n\n"; - print FILE "\n"; - print FILE "\n"; - print FILE "\n"; - print FILE "\n"; - print FILE "\n"; - print FILE "\n"; - print FILE "\n"; - print FILE "\n"; - print FILE "\n\n\n"; - $table_id++; - - my $iter = $self->{storage}->get_each("index1 = $channel", '_everything', '_sort = index1'); - while (defined (my $factoid = $self->{storage}->get_next($iter))) { - my $trigger_name = $self->{storage}->get_data($factoid->{index1}, $factoid->{index2}, '_name'); - if ($factoid->{type} eq 'text') { - $i++; - if ($i % 2) { print FILE "\n"; } - else { print FILE "\n"; } - - print FILE "\n"; - print FILE "\n"; - - print FILE "\n"; - - my $action = $factoid->{'action'}; - - if ($action =~ m/https?:\/\/[^ ]+/) { - $action =~ s/(.*?)http(s?:\/\/[^ ]+)/encode_entities($1) . "http" . encode_entities($2) . "<\/a>"/ge; - $action =~ s/(.*)<\/a>(.*$)/"$1<\/a>" . encode_entities($2)/e; - } else { - $action = encode_entities($action); - } - - if (defined $factoid->{'action_with_args'}) { - my $with_args = $factoid->{'action_with_args'}; - $with_args =~ s/(.*?)http(s?:\/\/[^ ]+)/encode_entities($1) . "http" . encode_entities($2) . "<\/a>"/ge; - $with_args =~ s/(.*)<\/a>(.*$)/"$1<\/a>" . encode_entities($2)/e; - print FILE "\n"; - } else { - print FILE "\n"; - } - - if (defined $factoid->{'edited_by'}) { - print FILE "\n"; - print FILE "\n"; - } else { - print FILE "\n"; - print FILE "\n"; - } - - print FILE "\n"; - - if (defined $factoid->{'last_referenced_on'}) { - print FILE "\n"; - } else { - print FILE "\n"; - } - - print FILE "\n"; - } - } - print FILE "\n
ownercreated ontimes referencedfactoidlast edited byedited datelast referenced bylast referenced date
" . encode_entities($factoid->{'owner'}) . "" . encode_entities(strftime "%Y/%m/%d %H:%M:%S", localtime $factoid->{'created_on'}) . "" . $factoid->{'ref_count'} . "" . encode_entities($trigger_name) . " is $action

with_args: " . encode_entities($with_args) . "
" . encode_entities($trigger_name) . " is $action" . $factoid->{'edited_by'} . "" . encode_entities(strftime "%Y/%m/%d %H:%M:%S", localtime $factoid->{'edited_on'}) . "" . encode_entities($factoid->{'ref_user'}) . "" . encode_entities(strftime "%Y/%m/%d %H:%M:%S", localtime $factoid->{'last_referenced_on'}) . "
\n"; - } - - print FILE "
$i factoids memorized.
"; - print FILE "
Last updated at $time\n"; - - print FILE "\n"; - print FILE "\n\n"; - - close(FILE); - - return "/say $i factoids exported."; -} - -sub find_factoid { - my ($self, $from, $keyword, %opts) = @_; - - my %default_opts = ( - arguments => '', - exact_channel => 0, - exact_trigger => 0, - find_alias => 0 - ); - - %opts = (%default_opts, %opts); - - my $debug = 0; - - if ($debug) { - use Data::Dumper; - my $dump = Dumper \%opts; - $self->{pbot}->{logger}->log("+" x 32 . "\n"); - use Devel::StackTrace; - my $trace = Devel::StackTrace->new(indent => 1, ignore_class => ['PBot::PBot', 'PBot::Core::IRC']); - $self->{pbot}->{logger}->log("find_factoid stacktrace: " . $trace->as_string() . "\n"); - - $self->{pbot}->{logger}->log("find_factiod: from: $from, kw: $keyword, opts: $dump\n"); - } - - $from = '.*' if not defined $from or $from !~ /^#/; - $from = lc $from; - $keyword = lc $keyword; - - my $arguments = $opts{arguments}; - - my @result = eval { - my @results; - my ($channel, $trigger); - for (my $depth = 0; $depth < 15; $depth++) { - my $action; - my $string = $keyword . (length $arguments ? " $arguments" : ""); - $self->{pbot}->{logger}->log("string: $string\n") if $debug; - - if ($opts{exact_channel} and $opts{exact_trigger}) { - if ($self->{storage}->exists($from, $keyword)) { - ($channel, $trigger) = ($from, $keyword); - goto CHECK_ALIAS; - } - - if ($opts{exact_trigger} > 1 and $self->{storage}->exists('.*', $keyword)) { - ($channel, $trigger) = ('.*', $keyword); - goto CHECK_ALIAS; - } - - goto CHECK_REGEX; - } - - if ($opts{exact_channel} and not $opts{exact_trigger}) { - if (not $self->{storage}->exists($from, $keyword)) { - ($channel, $trigger) = ($from, $keyword); - goto CHECK_REGEX if $from eq '.*'; - goto CHECK_REGEX if not $self->{storage}->exists('.*', $keyword); - ($channel, $trigger) = ('.*', $keyword); - goto CHECK_ALIAS; - } - ($channel, $trigger) = ($from, $keyword); - goto CHECK_ALIAS; - } - - if (not $opts{exact_channel}) { - foreach my $factoid ($self->{storage}->get_all("index2 = $keyword", 'index1', 'action')) { - $channel = $factoid->{index1}; - $trigger = $keyword; - - if ($opts{find_alias} && $factoid->{action} =~ m{^/call\s+(.*)$}ms) { - goto CHECK_ALIAS; - } - - push @results, [$channel, $trigger]; - } - - goto CHECK_REGEX; - } - - CHECK_ALIAS: - if ($opts{find_alias}) { - $action = $self->{storage}->get_data($channel, $trigger, 'action') if not defined $action; - if ($action =~ m{^/call\s+(.*)$}ms) { - my $command; - if (length $arguments) { - $command = "$1 $arguments"; - } else { - $command = $1; - } - my $arglist = $self->{pbot}->{interpreter}->make_args($command); - ($keyword, $arguments) = $self->{pbot}->{interpreter}->split_args($arglist, 2, 0, 1); - goto NEXT_DEPTH; - } - } - - if ($opts{exact_channel} == 1) { - return ($channel, $trigger); - } else { - push @results, [$channel, $trigger]; - } - - CHECK_REGEX: - if (not $opts{exact_trigger}) { - my @factoids; - - if ($opts{exact_channel}) { - if ($channel ne '.*') { - @factoids = $self->{storage}->get_all('type = regex', "index1 = $channel", 'OR index1 = .*', 'index2', 'action'); - } else { - @factoids = $self->{storage}->get_all('type = regex', "index1 = $channel", 'index2', 'action'); - } - } else { - @factoids = $self->{storage}->get_all('type = regex', 'index1', 'index2', 'action'); - } - - foreach my $factoid (@factoids) { - $channel = $factoid->{index1}; - $trigger = $factoid->{index2}; - $action = $factoid->{action}; - - if ($string =~ /$trigger/) { - if ($opts{find_alias}) { - my $command = $action; - my $arglist = $self->{pbot}->{interpreter}->make_args($command); - ($keyword, $arguments) = $self->{pbot}->{interpreter}->split_args($arglist, 2, 0, 1); - goto NEXT_DEPTH; - } - - if ($opts{exact_channel} == 1) { return ($channel, $trigger); } - else { push @results, [$channel, $trigger]; } - } - } - } - - # match not found - last; - - NEXT_DEPTH: - last if not $opts{find_alias}; - } - - if ($debug) { - if (not @results) { $self->{pbot}->{logger}->log("find_factoid: no match\n"); } - else { - $self->{pbot}->{logger}->log("find_factoid: got results: " . (join ', ', map { "$_->[0] -> $_->[1]" } @results) . "\n"); - } - } - return @results; - }; - - if ($@) { - $self->{pbot}->{logger}->log("Error in find_factoid: $@\n"); - return undef; - } - - return @result; -} - -sub parse_expansion_modifiers { - my ($self, $modifier) = @_; - - my %settings; - - while ($$modifier =~ s/^:(?=\w)//) { - if ($$modifier =~ s/^join\s*(?=\(.*?(?=\)))//) { - my ($params, $rest) = $self->{pbot}->{interpreter}->extract_bracketed($$modifier, '(', ')', '', 1); - $$modifier = $rest; - my @args = $self->{pbot}->{interpreter}->split_line($params, strip_quotes => 1, strip_commas => 1); - $settings{'join'} = $args[0]; - next; - } - - if ($$modifier=~ s/^\+?sort//) { - $settings{'sort+'} = 1; - next; - } - - if ($$modifier=~ s/^\-sort//) { - $settings{'sort-'} = 1; - next; - } - - if ($$modifier=~ s/^pick_unique\s*(?=\(.*?(?=\)))//) { - my ($params, $rest) = $self->{pbot}->{interpreter}->extract_bracketed($$modifier, '(', ')', '', 1); - $$modifier = $rest; - my @args = $self->{pbot}->{interpreter}->split_line($params, strip_quotes => 1, strip_commas => 1); - - $settings{'pick'} = 1; - $settings{'unique'} = 1; - - if (@args == 2) { - $settings{'random'} = 1; - $settings{'pick_min'} = $args[0]; - $settings{'pick_max'} = $args[1]; - } elsif (@args == 1) { - $settings{'pick_min'} = 1; - $settings{'pick_max'} = $args[0]; - } else { - push @{$settings{errors}}, "pick_unique(): missing argument(s)"; - } - - next; - } - - if ($$modifier=~ s/^pick\s*(?=\(.*?(?=\)))//) { - my ($params, $rest) = $self->{pbot}->{interpreter}->extract_bracketed($$modifier, '(', ')', '', 1); - $$modifier = $rest; - my @args = $self->{pbot}->{interpreter}->split_line($params, strip_quotes => 1, strip_commas => 1); - - $settings{'pick'} = 1; - - if (@args == 2) { - $settings{'random'} = 1; - $settings{'pick_min'} = $args[0]; - $settings{'pick_max'} = $args[1]; - } elsif (@args == 1) { - $settings{'pick_min'} = 1; - $settings{'pick_max'} = $args[0]; - } else { - push @{$settings{errors}}, "pick(): missing argument(s)"; - } - - next; - } - - if ($$modifier=~ s/^index\s*(?=\(.*?(?=\)))//) { - my ($params, $rest) = $self->{pbot}->{interpreter}->extract_bracketed($$modifier, '(', ')', '', 1); - $$modifier = $rest; - my @args = $self->{pbot}->{interpreter}->split_line($params, strip_quotes => 1, strip_commas => 1); - if (@args == 1) { - $settings{'index'} = $args[0]; - } else { - push @{$settings{errors}}, "index(): missing argument"; - } - next; - } - - if ($$modifier =~ s/^(enumerate|comma|ucfirst|lcfirst|title|uc|lc)//) { - $settings{$1} = 1; - next; - } - - if ($$modifier =~ s/^(\w+)//) { - push @{$settings{errors}}, "Unknown modifier `$1`"; - } - } - - return %settings; -} - -sub make_list { - my ($self, $context, $extracted, $settings, %opts) = @_; - - if ($extracted =~ /(.*?)(?expand_factoid_selectors($context, $extracted, %opts); - $opts{nested}--; - } - - my @list; - foreach my $item (split /\s*(?{'uc'}) { $item = uc $item; } - - if ($settings->{'lc'}) { $item = lc $item; } - - if ($settings->{'ucfirst'}) { $item = ucfirst $item; } - - if ($settings->{'title'}) { - $item = ucfirst lc $item; - $item =~ s/ (\w)/' ' . uc $1/ge; - } - - if ($settings->{'json'}) { $item = $self->escape_json($item); } - - push @list, $item; - } - - if ($settings->{'unique'}) { - foreach my $choice (@{$settings->{'choices'}}) { - @list = grep { $_ ne $choice } @list; - } - } - - if ($settings->{'sort+'}) { - @list = sort { $a cmp $b } @list; - } - - if ($settings->{'sort-'}) { - @list = sort { $b cmp $a } @list; - } - - return \@list; -} - -sub select_weighted_item_from_list { - my ($self, $list, $index) = @_; - - my @weights; - my $weight_sum = 0; - - for (my $i = 0; $i <= $#$list; $i++) { - my $weight = 1; - if ($list->[$i] =~ s/:weight\(([0-9.-]+)\)//) { - $weight = $1; - } - $weights[$i] = [ $weight, $i ]; - $weight_sum += $weight; - } - - if (defined $index) { - return $list->[$index]; - } - - my $n = rand $weight_sum; - - for my $weight (@weights) { - if ($n < $weight->[0]) { - return $list->[$weight->[1]]; - } - $n -= $weight->[0]; - } -} - -sub select_item { - my ($self, $context, $extracted, $modifier, %opts) = @_; - - my %settings = $self->parse_expansion_modifiers($modifier); - - if (exists $settings{errors}) { - return "[Error: " . join ('; ', @{$settings{errors}}) . ']'; - } - - my $item; - - if (exists $settings{'index'}) { - my $list = $self->make_list($context, $extracted, \%settings, %opts); - my $index = $settings{'index'}; - - $index = $#$list - -$index if $index < 0; - $index = 0 if $index < 0; - $index = $#$list if $index > $#$list; - - $item = $self->select_weighted_item_from_list($list, $index); - - # strip outer quotes - if (not $item =~ s/^"(.*)"$/$1/) { $item =~ s/^'(.*)'$/$1/; } - } elsif ($settings{'pick'}) { - my $min = $settings{'pick_min'}; - my $max = $settings{'pick_max'}; - - $max = 100 if $max > 100; - - my $count = $max; - if ($settings{'random'}) { - $count = int rand ($max + 1 - $min) + $min; - } - - my @choices; - $settings{'choices'} = \@choices; - - while ($count-- > 0) { - my $list = $self->make_list($context, $extracted, \%settings, %opts); - - last if not @$list; - - $max = @$list if $settings{'unique'} and $max > @$list; - $min = $max if $min > $max; - - my $choice = $self->select_weighted_item_from_list($list); - - push @choices, $choice; - } - - # strip outer quotes - foreach my $choice (@choices) { - if (not $choice =~ s/^"(.*)"$/$1/) { $choice =~ s/^'(.*)'$/$1/; } - } - - if ($settings{'sort+'}) { - @choices = sort { $a cmp $b } @choices; - } - - if ($settings{'sort-'}) { - @choices = sort { $b cmp $a } @choices; - } - - return @choices if wantarray; - - if (exists $settings{'join'}) { - my $sep = $settings{'join'}; - $sep = '' if not defined $sep; - $item = join $sep, @choices; - } elsif ($settings{'enumerate'} or $settings{'comma'}) { - $item = join ', ', @choices; - $item =~ s/(.*), /$1 and / if $settings{'enumerate'}; - } else { - $item = $opts{nested} ? join('|', @choices) : "@choices"; - } - } else { - my $list = $self->make_list($context, $extracted, \%settings, %opts); - - $item = $self->select_weighted_item_from_list($list); - - # strip outer quotes - if (not $item =~ s/^"(.*)"$/$1/) { $item =~ s/^'(.*)'$/$1/; } - } - - return $item; -} - -sub expand_factoid_selectors { - my ($self, $context, $action, %opts) = @_; - - my %default_opts = ( - nested => 0, - recursions => 0, - ); - - %opts = (%default_opts, %opts); - - return '!recursion limit!' if ++$opts{recursions} > 100; - - my $result = ''; - - while (1) { - if ($action =~ /(.*?)(?{pbot}->{interpreter}->extract_bracketed($action, '(', ')', '%', 1); - - last if not length $extracted; - - my $item = $self->select_item($context, $extracted, \$rest, %opts); - - if ($result =~ s/\b(a|an)(\s+)$//i) { - my ($article, $trailing) = ($1, $2); - my $fixed_article = select_indefinite_article $item; - - if ($article eq 'AN') { - $fixed_article = uc $fixed_article; - } elsif ($article eq 'An' or $article eq 'A') { - $fixed_article = ucfirst $fixed_article; - } - - $item = $fixed_article . $trailing . $item; - } - - $result .= $item; - $action = $rest; - } - - $result .= $action; - return $result; -} - -sub expand_factoid_vars { - my ($self, $context, $action, %opts) = @_; - - my %default_opts = ( - nested => 0, - recursions => 0, - ); - - %opts = (%default_opts, %opts); - - return '!recursion limit reached!' if ++$opts{recursions} > 100; - - my $from = length $context->{ref_from} ? $context->{ref_from} : $context->{from}; - my $nick = $context->{nick}; - my $root_keyword = $context->{keyword_override} ? $context->{keyword_override} : $context->{root_keyword}; - - $action = defined $action ? $action : $context->{action}; - - my $interpolate = $self->{storage}->get_data($context->{channel}, $context->{keyword}, 'interpolate'); - return $action if defined $interpolate and $interpolate == 0; - - $interpolate = $self->{pbot}->{registry}->get_value($context->{channel}, 'interpolate_factoids'); - return $action if defined $interpolate and $interpolate == 0; - - $action = $self->expand_factoid_selectors($context, $action, %opts); - - my $depth = 0; - - if ($action =~ m/^\/call --keyword-override=([^ ]+)/i) { $root_keyword = $1; } - - my $result = ''; - my $rest = $action; - - while (1) { - last if ++$depth >= 100; - - $rest =~ s/(?{pbot}->{interpreter}->extract_bracketed($rest, '{', '}'); - - if ($var =~ /:/) { - my @stuff = split /:/, $var, 2; - $var = $stuff[0]; - $rest = ':' . $stuff[1] . $rest; - } - - $extract_method = 'bracket'; - } else { - $rest =~ s/^(\w+)//; - $var = $1; - $extract_method = 'regex'; - } - - if ($var =~ /^(?:_.*|[[:punct:]0-9]+|a|b|nick|channel|randomnick|arglen|args|arg\[.+\])$/i) { - # skip identifiers with leading underscores, etc - $result .= $extract_method eq 'bracket' ? '${' . $var . '}' : '$' . $var; - next; - } - - $matches++; - - # extract channel expansion modifier - if ($rest =~ s/^:(#[^:]+|global)//i) { - $from = $1; - $from = '.*' if lc $from eq 'global'; - } - - my $recurse = 0; - ALIAS: - my @factoids = $self->find_factoid($from, $var, exact_channel => 2, exact_trigger => 2); - - if (not @factoids or not $factoids[0]) { - $result .= $extract_method eq 'bracket' ? '${' . $var . '}' : '$' . $var; - next; - } - - my $var_chan; - ($var_chan, $var) = ($factoids[0]->[0], $factoids[0]->[1]); - - if ($self->{storage}->get_data($var_chan, $var, 'action') =~ m{^/call (.*)}ms) { - $var = $1; - - if (++$recurse > 100) { - $self->{pbot}->{logger}->log("Factoids: variable expansion recursion limit reached\n"); - $result .= $extract_method eq 'bracket' ? '${' . $var . '}' : '$' . $var; - next; - } - - goto ALIAS; - } - - my $copy = $rest; - my %settings = $self->parse_expansion_modifiers(\$copy); - - if ($self->{storage}->get_data($var_chan, $var, 'type') eq 'text') { - my $change = $self->{storage}->get_data($var_chan, $var, 'action'); - my @list = $self->{pbot}->{interpreter}->split_line($change); - - my @replacements; - - if (wantarray) { - @replacements = $self->select_item($context, join ('|', @list), \$rest, %opts); - return @replacements; - } else { - push @replacements, scalar $self->select_item($context, join ('|', @list), \$rest, %opts); - } - - my $replacement = $opts{nested} ? join('|', @replacements) : "@replacements"; - - if (not length $replacement) { - $result =~ s/\s+$//; - } else { - $replacement = $self->expand_factoid_vars($context, $replacement, %opts); - } - - if ($settings{'uc'}) { $replacement = uc $replacement; } - - if ($settings{'lc'}) { $replacement = lc $replacement; } - - if ($settings{'ucfirst'}) { $replacement = ucfirst $replacement; } - - if ($settings{'title'}) { - $replacement = ucfirst lc $replacement; - $replacement =~ s/ (\w)/' ' . uc $1/ge; - } - - if ($settings{'json'}) { $replacement = $self->escape_json($replacement); } - - if ($result =~ s/\b(a|an)(\s+)$//i) { - my ($article, $trailing) = ($1, $2); - my $fixed_article = select_indefinite_article $replacement; - - if ($article eq 'AN') { - $fixed_article = uc $fixed_article; - } elsif ($article eq 'An' or $article eq 'A') { - $fixed_article = ucfirst $fixed_article; - } - - $replacement = $fixed_article . $trailing . $replacement; - } - - $result .= $replacement; - - $expansions++; - } else { - $result .= $extract_method eq 'bracket' ? '${' . $var . '}' : '$' . $var; - } - } - - last if $matches == 0 or $expansions == 0; - - if (not length $rest) { - $rest = $result; - $result = ''; - } - } - - $result .= $rest; - - $result = $self->expand_special_vars($from, $nick, $root_keyword, $result); - - # unescape certain symbols - $result =~ s/(?{pbot}->{registry}->get_value('factoids', 'max_content_length')); -} - -sub expand_action_arguments { - my ($self, $action, $input, $nick) = @_; - - $action = validate_string($action, $self->{pbot}->{registry}->get_value('factoids', 'max_content_length')); - $input = validate_string($input, $self->{pbot}->{registry}->get_value('factoids', 'max_content_length')); - - my %h; - if (not defined $input or $input eq '') { %h = (args => $nick); } - else { %h = (args => $input); } - - my $jsonargs = to_json \%h; - $jsonargs =~ s/^{".*":"//; - $jsonargs =~ s/"}$//; - - if (not defined $input or $input eq '') { - $input = ""; - $action =~ s/\$args:json|\$\{args:json\}/$jsonargs/ge; - $action =~ s/\$args(?![[\w])|\$\{args(?![[\w])\}/$nick/g; - } else { - $action =~ s/\$args:json|\$\{args:json\}/$jsonargs/g; - $action =~ s/\$args(?![[\w])|\$\{args(?![[\w])\}/$input/g; - } - - my @args = $self->{pbot}->{interpreter}->split_line($input); - $action =~ s/\$arglen\b|\$\{arglen\}/scalar @args/eg; - - my $depth = 0; - my $const_action = $action; - while ($const_action =~ m/\$arg\[([^]]+)]|\$\{arg\[([^]]+)]\}/g) { - my $arg = defined $2 ? $2 : $1; - - last if ++$depth >= 100; - - if ($arg eq '*') { - if (not defined $input or $input eq '') { $action =~ s/\$arg\[\*\]|\$\{arg\[\*\]\}/$nick/; } - else { $action =~ s/\$arg\[\*\]|\$\{arg\[\*\]\}/$input/; } - next; - } - - if ($arg =~ m/([^:]*):(.*)/) { - my $arg1 = $1; - my $arg2 = $2; - - my $arg1i = $arg1; - my $arg2i = $arg2; - - $arg1i = 0 if $arg1i eq ''; - $arg2i = $#args if $arg2i eq ''; - $arg2i = $#args if $arg2i > $#args; - - my @values = eval { - local $SIG{__WARN__} = sub { }; - return @args[$arg1i .. $arg2i]; - }; - - if ($@) { next; } - else { - my $string = join(' ', @values); - - if ($string eq '') { $action =~ s/\s*\$\{arg\[$arg1:$arg2\]\}// || $action =~ s/\s*\$arg\[$arg1:$arg2\]//; } - else { $action =~ s/\$\{arg\[$arg1:$arg2\]\}/$string/ || $action =~ s/\$arg\[$arg1:$arg2\]/$string/; } - } - - next; - } - - my $value = eval { - local $SIG{__WARN__} = sub { }; - return $args[$arg]; - }; - - if ($@) { next; } - else { - if (not defined $value) { - if ($arg == 0) { $action =~ s/\$\{arg\[$arg\]\}/$nick/ || $action =~ s/\$arg\[$arg\]/$nick/; } - else { $action =~ s/\s*\$\{arg\[$arg\]\}// || $action =~ s/\s*\$arg\[$arg\]//; } - } else { - $action =~ s/\$arg\{\[$arg\]\}/$value/ || $action =~ s/\$arg\[$arg\]/$value/; - } - } - } - - return $action; -} - -sub escape_json { - my ($self, $text) = @_; - my $thing = {thing => $text}; - my $json = to_json $thing; - $json =~ s/^{".*":"//; - $json =~ s/"}$//; - return $json; -} - -sub expand_special_vars { - my ($self, $from, $nick, $root_keyword, $action) = @_; - - $action =~ s/(?escape_json($nick)/ge; - $action =~ s/(?escape_json($from)/ge; - $action =~ - s/(?{pbot}->{nicklist}->random_nick($from); $random ? $self->escape_json($random) : $self->escape_json($nick)/ge; - $action =~ s/(?escape_json($root_keyword)/ge; - - $action =~ s/(?{pbot}->{nicklist}->random_nick($from); $random ? $random : $nick/ge; - $action =~ s/(?{pbot}->{registry}->get_value('factoids', 'max_content_length')); -} - -sub execute_code_factoid_using_vm { - my ($self, $context) = @_; - - unless ($self->{storage}->exists($context->{channel}, $context->{keyword}, 'interpolate') - and $self->{storage}->get_data($context->{channel}, $context->{keyword}, 'interpolate') eq '0') - { - if ($context->{code} =~ m/(?:\$\{?nick\b|\$\{?args\b|\$\{?arg\[)/ and length $context->{arguments}) { - $context->{nickprefix_disabled} = 1; - } else { - $context->{nickprefix_disabled} = 0; - } - - $context->{code} = $self->expand_factoid_vars($context, $context->{code}); - - if ($self->{storage}->get_data($context->{channel}, $context->{keyword}, 'allow_empty_args')) { - $context->{code} = $self->expand_action_arguments($context->{code}, $context->{arguments}, ''); - } else { - $context->{code} = $self->expand_action_arguments($context->{code}, $context->{arguments}, $context->{nick}); - } - } else { - # otherwise allow nick overriding - $context->{nickprefix_disabled} = 0; - } - - # set up `compiler` module arguments - my %args = ( - nick => $context->{nick}, - channel => $context->{from}, - lang => $context->{lang}, - code => $context->{code}, - arguments => $context->{arguments}, - factoid => "$context->{channel}:$context->{keyword}", - ); - - # the vm can persist filesystem data to external storage identified by a key. - # if the `persist-key` factoid metadata is set, then use this key. - my $persist_key = $self->{storage}->get_data($context->{channel}, $context->{keyword}, 'persist-key'); - - if (defined $persist_key) { - $args{'persist-key'} = $persist_key; - } - - # encode args to utf8 json string - my $json = encode_json \%args; - - # update context details - $context->{special} = 'code-factoid'; # ensure handle_result(), etc, process this as a code-factoid - $context->{root_channel} = $context->{channel}; # override root channel to current channel - $context->{keyword} = 'compiler'; # code-factoid uses `compiler` command to invoke vm - $context->{arguments} = $json; # set arguments to json string as `compiler` wants - $context->{args_utf8} = 1; # arguments are utf8 encoded by encode_json - - # launch the `compiler` module - $self->{pbot}->{modules}->execute_module($context); - - # return empty string since the module process reader will - # pass the output along to the result handler - return ''; -} - -sub execute_code_factoid { - my ($self, @args) = @_; - # this sub used to contain an if-clause that selected - # an alternative method of executing code factoids. - # now it only uses the vm. maybe one day... - return $self->execute_code_factoid_using_vm(@args); -} - -# main entry point for PBot::Core::Interpreter to interpret a factoid command -sub interpreter { - my ($self, $context) = @_; - my $pbot = $self->{pbot}; - - if ($self->{pbot}->{registry}->get_value('general', 'debugcontext')) { - use Data::Dumper; - $Data::Dumper::Sortkeys = 1; - $self->{pbot}->{logger}->log("Factoids::interpreter\n"); - $self->{pbot}->{logger}->log(Dumper $context); - } - - return undef if not length $context->{keyword} or $context->{interpret_depth} > $self->{pbot}->{registry}->get_value('interpreter', 'max_recursion'); - - $context->{from} = lc $context->{from}; - - my $strictnamespace = $self->{pbot}->{registry}->get_value($context->{from}, 'strictnamespace'); - - if (not defined $strictnamespace) { $strictnamespace = $self->{pbot}->{registry}->get_value('general', 'strictnamespace'); } - - # search for factoid against global channel and current channel (from unless ref_from is defined) - my $original_keyword = $context->{keyword}; - my ($channel, $keyword) = - $self->find_factoid($context->{ref_from} ? $context->{ref_from} : $context->{from}, $context->{keyword}, arguments => $context->{arguments}, exact_channel => 1); - - if (not $context->{ref_from} or $context->{ref_from} eq '.*' or $context->{ref_from} eq $context->{from}) { $context->{ref_from} = ""; } - - if (defined $channel and not $channel eq '.*' and not $channel eq lc $context->{from}) { $context->{ref_from} = $channel; } - - $context->{arguments} = "" if not defined $context->{arguments}; - - # factoid > nick redirection - my $nick_regex = $self->{pbot}->{registry}->get_value('regex', 'nickname'); - - if ($context->{arguments} =~ s/> ($nick_regex)$//) { - my $rcpt = $1; - if ($self->{pbot}->{nicklist}->is_present($context->{from}, $rcpt)) { - $context->{nickprefix} = $rcpt; - $context->{nickprefix_forced} = 1; - } else { - $context->{arguments} .= "> $rcpt"; - } - } - - # if no match found, attempt to call factoid from another channel if it exists there - if (not defined $keyword) { - my $string = "$original_keyword $context->{arguments}"; - - my @chanlist = (); - my ($fwd_chan, $fwd_trig); - - unless ($strictnamespace) { - # build list of which channels contain the keyword, keeping track of the last one and count - foreach my $factoid ($self->{storage}->get_all("index2 = $original_keyword", 'index1', 'type')) { - next if $factoid->{type} ne 'text' and $factoid->{type} ne 'module'; - push @chanlist, $self->{storage}->get_data($factoid->{index1}, '_name'); - $fwd_chan = $factoid->{index1}; - $fwd_trig = $original_keyword; - } - } - - my $ref_from = $context->{ref_from} ? "[$context->{ref_from}] " : ""; - - # if multiple channels have this keyword, then ask user to disambiguate - if (@chanlist> 1) { - return undef if $context->{embedded}; - return $ref_from . "Factoid `$original_keyword` exists in " . join(', ', @chanlist) . "; use `fact $original_keyword` to choose one."; - } - - # if there's just one other channel that has this keyword, trigger that instance - elsif (@chanlist == 1) { - $pbot->{logger}->log("Found '$original_keyword' as '$fwd_trig' in [$fwd_chan]\n"); - $context->{keyword} = $fwd_trig; - $context->{interpret_depth}++; - $context->{ref_from} = $fwd_chan; - return $self->interpreter($context); - } - - # otherwise keyword hasn't been found, display similiar matches for all channels - else { - my $namespace = $strictnamespace ? $context->{from} : '.*'; - $namespace = '.*' if $namespace !~ /^#/; - - my $namespace_regex = $namespace; - if ($strictnamespace) { $namespace_regex = "(?:" . (quotemeta $namespace) . '|\\.\\*)'; } - - $context->{arguments} = quotemeta($original_keyword) . " -channel $namespace_regex"; - my $matches = $self->{pbot}->{commands}->{modules}->{Factoids}->cmd_factfind($context); - - # found factfind matches - if ($matches !~ m/^No factoids/) { - return undef if $context->{embedded}; - return "No such factoid '$original_keyword'; $matches"; - } - - # otherwise find levenshtein closest matches - $matches = $self->{storage}->levenshtein_matches($namespace, lc $original_keyword, 0.50, $strictnamespace); - - # if a non-nick argument was supplied, e.g., a sentence using the bot's nick, /msg the error to the caller - if (length $context->{arguments} and not $self->{pbot}->{nicklist}->is_present($context->{from}, $context->{arguments})) { - $context->{send_msg_to_caller} = 1; - } - - # /msg the caller if nothing similiar was found - $context->{send_msg_to_caller} = 1 if $matches eq 'none'; - $context->{send_msg_to_caller} = 1 if $context->{embedded}; - - my $msg_caller = ''; - $msg_caller = "/msg $context->{nick} " if $context->{send_msg_to_caller}; - - my $ref_from = $context->{ref_from} ? "[$context->{ref_from}] " : ""; - if ($matches eq 'none') { - return $msg_caller . $ref_from . "No such factoid '$original_keyword'; no similar matches."; - } else { - return $msg_caller . $ref_from . "No such factoid '$original_keyword'; did you mean $matches?"; - } - } - } - - my $channel_name = $self->{storage}->get_data($channel, '_name'); - my $trigger_name = $self->{storage}->get_data($channel, $keyword, '_name'); - $channel_name = 'global' if $channel_name eq '.*'; - $trigger_name = "\"$trigger_name\"" if $trigger_name =~ / /; - - $context->{keyword} = $keyword; - $context->{trigger} = $keyword; - $context->{channel} = $channel; - $context->{original_keyword} = $original_keyword; - $context->{channel_name} = $channel_name; - $context->{trigger_name} = $trigger_name; - - return undef if $context->{embedded} and $self->{storage}->get_data($channel, $keyword, 'noembed'); - - if ($self->{storage}->get_data($channel, $keyword, 'locked_to_channel')) { - if ($context->{ref_from} ne "") { # called from another channel - return "$trigger_name may be invoked only in $context->{ref_from}."; - } - } - - if ($context->{interpret_depth} <= 1 and $self->{storage}->get_data($channel, $keyword, 'last_referenced_in') eq $context->{from}) { - my $ratelimit = $self->{pbot}->{registry}->get_value($context->{from}, 'ratelimit_override'); - $ratelimit = $self->{storage}->get_data($channel, $keyword, 'rate_limit') if not defined $ratelimit; - if (gettimeofday - $self->{storage}->get_data($channel, $keyword, 'last_referenced_on') < $ratelimit) { - my $ref_from = $context->{ref_from} ? "[$context->{ref_from}] " : ""; - return - "/msg $context->{nick} $ref_from'$trigger_name' is rate-limited; try again in " - . duration($ratelimit - int(gettimeofday - $self->{storage}->get_data($channel, $keyword, 'last_referenced_on'))) . "." - unless $self->{pbot}->{users}->loggedin_admin($channel, "$context->{nick}!$context->{user}\@$context->{host}"); - } - } - - my $ref_count = $self->{storage}->get_data($channel, $keyword, 'ref_count'); - my $update_data = { - ref_count => ++$ref_count, - ref_user => "$context->{nick}!$context->{user}\@$context->{host}", - last_referenced_on => scalar gettimeofday, - last_referenced_in => $context->{from} || 'stdin', - }; - $self->{storage}->add($channel, $keyword, $update_data, 1); - - my $action; - - if ($self->{storage}->exists($channel, $keyword, 'usage') and not length $context->{arguments} and $self->{storage}->get_data($channel, $keyword, 'requires_arguments')) { - $context->{alldone} = 1; - my $usage = $self->{storage}->get_data($channel, $keyword, 'usage'); - $usage =~ s/(?{arguments} and $self->{storage}->exists($channel, $keyword, 'action_with_args')) { - $action = $self->{storage}->get_data($channel, $keyword, 'action_with_args'); - } else { - $action = $self->{storage}->get_data($channel, $keyword, 'action'); - } - - if ($action =~ m{^/code\s+([^\s]+)\s+(.+)$}msi) { - my ($lang, $code) = ($1, $2); - - if ($self->{storage}->exists($channel, $keyword, 'usage') and not length $context->{arguments}) { - $context->{alldone} = 1; - my $usage = $self->{storage}->get_data($channel, $keyword, 'usage'); - $usage =~ s/(?{lang} = $lang; - $context->{code} = $code; - $self->execute_code_factoid($context); - return ""; - } - - if ($self->{storage}->get_data($channel, $keyword, 'background-process')) { - my $timeout = $self->{storage}->get_data($channel, $keyword, 'process-timeout') // $self->{pbot}->{registry}->get_value('processmanager', 'default_timeout'); - $self->{pbot}->{process_manager}->execute_process( - $context, - sub { $context->{result} = $self->handle_action($context, $action); }, - $timeout - ); - return ""; - } else { - return $self->handle_action($context, $action); - } -} - -sub handle_action { - my ($self, $context, $action) = @_; - - if ($self->{pbot}->{registry}->get_value('general', 'debugcontext')) { - use Data::Dumper; - $Data::Dumper::Sortkeys = 1; - $self->{pbot}->{logger}->log("Factoids::handle_action [$action]\n"); - $self->{pbot}->{logger}->log(Dumper $context); - } - - return "" if not length $action; - - my ($channel, $keyword) = ($context->{channel}, $context->{trigger}); - my ($channel_name, $trigger_name) = ($context->{channel_name}, $context->{trigger_name}); - - my $ref_from = ''; - - unless ($context->{pipe} or $context->{subcmd}) { - $ref_from = $context->{ref_from} ? "[$context->{ref_from}] " : ''; - } - - unless ($self->{storage}->exists($channel, $keyword, 'interpolate') and $self->{storage}->get_data($channel, $keyword, 'interpolate') eq '0') { - my ($root_channel, $root_keyword) = - $self->find_factoid($context->{ref_from} ? $context->{ref_from} : $context->{from}, $context->{root_keyword}, arguments => $context->{arguments}, exact_channel => 1); - if (not defined $root_channel or not defined $root_keyword) { - $root_channel = $channel; - $root_keyword = $keyword; - } - if (not length $context->{keyword_override} and length $self->{storage}->get_data($root_channel, $root_keyword, 'keyword_override')) { - $context->{keyword_override} = $self->{storage}->get_data($root_channel, $root_keyword, 'keyword_override'); - } - - $action = $self->expand_factoid_vars($context, $action); - } - - if (length $context->{arguments}) { - if ($action =~ m/\$\{?args/ or $action =~ m/\$\{?arg\[/) { - unless (defined $self->{storage}->get_data($channel, $keyword, 'interpolate') and $self->{storage}->get_data($channel, $keyword, 'interpolate') eq '0') { - $action = $self->expand_action_arguments($action, $context->{arguments}, $context->{nick}); - } - - $context->{arguments} = ""; - $context->{original_arguments} = ""; - } else { - if ($self->{storage}->get_data($channel, $keyword, 'type') eq 'text') { - my $target = $self->{pbot}->{nicklist}->is_present_similar($context->{from}, $context->{arguments}); - - if ($target and $action !~ /\$\{?(?:nick|args)\b/) { - $context->{nickprefix} = $target unless $context->{nickprefix_forced}; - $context->{nickprefix_disabled} = 0; - } - } - } - } else { - # no arguments supplied, replace $args with $nick/$tonick, etc - if ($self->{storage}->exists($channel, $keyword, 'usage')) { - $action = "/say " . $self->{storage}->get_data($channel, $keyword, 'usage'); - $action =~ s/(?{alldone} = 1; - } else { - if ($self->{storage}->get_data($channel, $keyword, 'allow_empty_args')) { - $action = $self->expand_action_arguments($action, undef, ''); - } else { - $action = $self->expand_action_arguments($action, undef, $context->{nick}); - } - } - $context->{nickprefix_disabled} = 0; - } - - # Check if it's an alias - if ($action =~ /^\/call\s+(.*)$/msi) { - my $command = $1; - $command =~ s/\n$//; - unless ($self->{storage}->get_data($channel, $keyword, 'require_explicit_args')) { - my $args = $context->{arguments}; - $command .= " $args" if length $args and not $context->{special} eq 'code-factoid'; - $context->{arguments} = ''; - } - - unless ($self->{storage}->get_data($channel, $keyword, 'no_keyword_override')) { - if ($command =~ s/\s*--keyword-override=([^ ]+)\s*//) { $context->{keyword_override} = $1; } - } - - $context->{command} = $command; - $context->{aliased} = 1; - - $self->{pbot}->{logger} - ->log("[" . (defined $context->{from} ? $context->{from} : "stdin") . "] ($context->{nick}!$context->{user}\@$context->{host}) $trigger_name aliased to: $command\n"); - - if (defined $self->{storage}->get_data($channel, $keyword, 'cap-override')) { - if ($self->{storage}->get_data($channel, $keyword, 'locked')) { - $self->{pbot}->{logger}->log("Capability override set to " . $self->{storage}->get_data($channel, $keyword, 'cap-override') . "\n"); - $context->{'cap-override'} = $self->{storage}->get_data($channel, $keyword, 'cap-override'); - } else { - $self->{pbot}->{logger}->log("Ignoring cap-override of " . $self->{storage}->get_data($channel, $keyword, 'cap-override') . " on unlocked factoid\n"); - } - } - - return $self->{pbot}->{interpreter}->interpret($context); - } - - $self->{pbot}->{logger} - ->log("(" . (defined $context->{from} ? $context->{from} : "(undef)") . "): $context->{nick}!$context->{user}\@$context->{host}: $trigger_name: action: \"$action\"\n"); - - my $enabled = $self->{storage}->get_data($channel, $keyword, 'enabled'); - - if (defined $enabled and $enabled == 0) { - $self->{pbot}->{logger}->log("$trigger_name disabled.\n"); - return "/msg $context->{nick} ${ref_from}$trigger_name is currently disabled."; - } - - unless ($self->{storage}->exists($channel, $keyword, 'interpolate') and $self->{storage}->get_data($channel, $keyword, 'interpolate') eq '0') { - my ($root_channel, $root_keyword) = - $self->find_factoid($context->{ref_from} ? $context->{ref_from} : $context->{from}, $context->{root_keyword}, arguments => $context->{arguments}, exact_channel => 1); - - if (not defined $root_channel or not defined $root_keyword) { - $root_channel = $channel; - $root_keyword = $keyword; - } - - if (not length $context->{keyword_override} and length $self->{storage}->get_data($root_channel, $root_keyword, 'keyword_override')) { - $context->{keyword_override} = $self->{storage}->get_data($root_channel, $root_keyword, 'keyword_override'); - } - - $action = $self->expand_factoid_vars($context, $action); - - if ($self->{storage}->get_data($channel, $keyword, 'allow_empty_args')) { - $action = $self->expand_action_arguments($action, $context->{arguments}, ''); - } else { - $action = $self->expand_action_arguments($action, $context->{arguments}, $context->{nick}); - } - } - - return $action if $context->{special} eq 'code-factoid'; - - if ($self->{storage}->get_data($channel, $keyword, 'type') eq 'module') { - my $preserve_whitespace = $self->{storage}->get_data($channel, $keyword, 'preserve_whitespace'); - $preserve_whitespace = 0 if not defined $preserve_whitespace; - - $context->{preserve_whitespace} = $preserve_whitespace; - $context->{root_keyword} = $keyword unless defined $context->{root_keyword}; - $context->{root_channel} = $channel; - - my $result = $self->{pbot}->{modules}->execute_module($context); - - if (length $result) { - return $ref_from . $result; - } else { - return ""; - } - } elsif ($self->{storage}->get_data($channel, $keyword, 'type') eq 'text') { - # Don't allow user-custom /msg factoids, unless factoid triggered by admin - if ($action =~ m/^\/msg/i) { - if (not $self->{pbot}->{users}->loggedin_admin($context->{from}, $context->{hostmask})) { - $self->{pbot}->{logger}->log("[ABUSE] Bad factoid (starts with /msg): $action\n"); - return "You must be an admin to use /msg."; - } - } - - if ($ref_from) { - if ( $action =~ s/^\/say\s+/$ref_from/i - || $action =~ s/^\/me\s+(.*)/\/me $1 $ref_from/i - || $action =~ s/^\/msg\s+([^ ]+)/\/msg $1 $ref_from/i - ) { - return $action; - } else { - return $ref_from . "$trigger_name is $action"; - } - } else { - if ($action =~ m/^\/(?:say|me|msg)/i) { return $action; } - else { return "/say $trigger_name is $action"; } - } - } elsif ($self->{storage}->get_data($channel, $keyword, 'type') eq 'regex') { - my $result = eval { - my $string = "$context->{original_keyword}" . (defined $context->{arguments} ? " $context->{arguments}" : ""); - my $cmd; - if ($string =~ m/$keyword/i) { - $self->{pbot}->{logger}->log("[$string] matches [$keyword] - calling [" . $action . "$']\n"); - $cmd = $action . $'; - my ($a, $b, $c, $d, $e, $f, $g, $h, $i, $before, $after) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $`, $'); - $cmd =~ s/\$1/$a/g; - $cmd =~ s/\$2/$b/g; - $cmd =~ s/\$3/$c/g; - $cmd =~ s/\$4/$d/g; - $cmd =~ s/\$5/$e/g; - $cmd =~ s/\$6/$f/g; - $cmd =~ s/\$7/$g/g; - $cmd =~ s/\$8/$h/g; - $cmd =~ s/\$9/$i/g; - $cmd =~ s/\$`/$before/g; - $cmd =~ s/\$'/$after/g; - $cmd =~ s/^\s+//; - $cmd =~ s/\s+$//; - } else { - $cmd = $action; - } - - $context->{command} = $cmd; - return $self->{pbot}->{interpreter}->interpret($context); - }; - - if ($@) { - $self->{pbot}->{logger}->log("Regex fail: $@\n"); - return ""; - } - - if (length $result) { return $ref_from . $result; } - else { return ""; } - } else { - $self->{pbot}->{logger}->log("($context->{from}): $context->{nick}!$context->{user}\@$context->{host}): Unknown command type for '$trigger_name'\n"); - return "/me blinks." . " $ref_from"; - } } 1; diff --git a/lib/PBot/Core/Interpreter.pm b/lib/PBot/Core/Interpreter.pm index c6a4ebd6..41af94fe 100644 --- a/lib/PBot/Core/Interpreter.pm +++ b/lib/PBot/Core/Interpreter.pm @@ -418,7 +418,7 @@ sub interpret { # replace pronouns like "i", "my", etc, with "nick", "nick's", etc if (not $self->{pbot}->{commands}->get_meta($keyword, 'dont-replace-pronouns') - and not $self->{pbot}->{factoids}->get_meta($context->{from}, $keyword, 'dont-replace-pronouns')) + and not $self->{pbot}->{factoids}->{data}->get_meta($context->{from}, $keyword, 'dont-replace-pronouns')) { # if command recipient is "me" then replace it with invoker's nick @@ -449,7 +449,7 @@ sub interpret { # the bot doesn't like performing bot commands on itself # unless dont-protect-self is true if (not $self->{pbot}->{commands}->get_meta($keyword, 'dont-protect-self') - and not $self->{pbot}->{factoids}->get_meta($context->{from}, $keyword, 'dont-protect-self')) + and not $self->{pbot}->{factoids}->{data}->get_meta($context->{from}, $keyword, 'dont-protect-self')) { my $botnick = $self->{pbot}->{registry}->get_value('irc', 'botnick'); @@ -636,7 +636,7 @@ sub handle_result { my $use_output_queue = 0; if (not $self->{pbot}->{commands}->exists($context->{keyword})) { - my @factoids = $self->{pbot}->{factoids}->find_factoid($context->{from}, $context->{keyword}, + my @factoids = $self->{pbot}->{factoids}->{data}->find($context->{from}, $context->{keyword}, arguments => $context->{arguments}, exact_channel => 0, exact_trigger => 0, @@ -647,10 +647,10 @@ sub handle_result { my ($chan, $trigger) = ($factoids[0]->[0], $factoids[0]->[1]); if ($context->{preserve_whitespace} == 0) { - $context->{preserve_whitespace} = $self->{pbot}->{factoids}->{storage}->get_data($chan, $trigger, 'preserve_whitespace') // 0; + $context->{preserve_whitespace} = $self->{pbot}->{factoids}->{data}->{storage}->get_data($chan, $trigger, 'preserve_whitespace') // 0; } - $use_output_queue = $self->{pbot}->{factoids}->{storage}->get_data($chan, $trigger, 'use_output_queue') // 0; + $use_output_queue = $self->{pbot}->{factoids}->{data}->{storage}->get_data($chan, $trigger, 'use_output_queue') // 0; } } diff --git a/lib/PBot/Core/Modules.pm b/lib/PBot/Core/Modules.pm index 827d5a55..03e3668b 100644 --- a/lib/PBot/Core/Modules.pm +++ b/lib/PBot/Core/Modules.pm @@ -40,7 +40,7 @@ sub launch_module { $context->{arguments} //= ''; - my @factoids = $self->{pbot}->{factoids}->find_factoid($context->{from}, $context->{keyword}, exact_channel => 2, exact_trigger => 2); + my @factoids = $self->{pbot}->{factoids}->{data}->find($context->{from}, $context->{keyword}, exact_channel => 2, exact_trigger => 2); if (not @factoids or not $factoids[0]) { $context->{checkflood} = 1; @@ -54,14 +54,14 @@ sub launch_module { $context->{keyword} = $trigger; $context->{trigger} = $trigger; - my $module = $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, 'action'); + my $module = $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger, 'action'); $self->{pbot}->{logger}->log( '(' . (defined $context->{from} ? $context->{from} : "(undef)") . '): ' . "$context->{hostmask}: Executing module [$context->{command}] $module $context->{arguments}\n" ); - $context->{arguments} = $self->{pbot}->{factoids}->expand_factoid_vars($context, $context->{arguments}); + $context->{arguments} = $self->{pbot}->{factoids}->{variables}->expand_factoid_vars($context, $context->{arguments}); my $module_dir = $self->{pbot}->{registry}->get_value('general', 'module_dir'); @@ -70,8 +70,8 @@ sub launch_module { Carp::croak("Could not chdir to '$module_dir': $!"); } - if ($self->{pbot}->{factoids}->{storage}->exists($channel, $trigger, 'workdir')) { - chdir $self->{pbot}->{factoids}->{storage}->get_data($channel, $trigger, 'workdir'); + if ($self->{pbot}->{factoids}->{data}->{storage}->exists($channel, $trigger, 'workdir')) { + chdir $self->{pbot}->{factoids}->{data}->{storage}->get_data($channel, $trigger, 'workdir'); } # FIXME -- add check to ensure $module exists diff --git a/lib/PBot/Core/ProcessManager.pm b/lib/PBot/Core/ProcessManager.pm index 22d975d1..28fb4476 100644 --- a/lib/PBot/Core/ProcessManager.pm +++ b/lib/PBot/Core/ProcessManager.pm @@ -186,7 +186,7 @@ sub process_pipe_reader { } $context->{original_keyword} = $context->{root_keyword}; - $context->{result} = $self->{pbot}->{factoids}->handle_action($context, $context->{result}); + $context->{result} = $self->{pbot}->{factoids}->{interpreter}->handle_action($context, $context->{result}); } # if nick isn't overridden yet, check for a potential nick prefix @@ -196,8 +196,8 @@ sub process_pipe_reader { # if add_nick is set on the factoid, set the nick override to the caller's nick if (exists $context->{special} and $context->{special} ne 'code-factoid' - and $self->{pbot}->{factoids}->{storage}->exists($context->{channel}, $context->{trigger}, 'add_nick') - and $self->{pbot}->{factoids}->{storage}->get_data($context->{channel}, $context->{trigger}, 'add_nick') != 0) + and $self->{pbot}->{factoids}->{data}->{storage}->exists($context->{channel}, $context->{trigger}, 'add_nick') + and $self->{pbot}->{factoids}->{data}->{storage}->get_data($context->{channel}, $context->{trigger}, 'add_nick') != 0) { $context->{nickprefix} = $context->{nick}; $context->{nickprefix_disabled} = 0; diff --git a/lib/PBot/Plugin/Plang.pm b/lib/PBot/Plugin/Plang.pm index 0fabd0ac..6514e550 100644 --- a/lib/PBot/Plugin/Plang.pm +++ b/lib/PBot/Plugin/Plang.pm @@ -168,13 +168,13 @@ sub plang_validate_builtin_print { sub is_locked { my ($self, $channel, $keyword) = @_; - return $self->{pbot}->{factoids}->get_meta($channel, $keyword, 'locked'); + return $self->{pbot}->{factoids}->{data}->get_meta($channel, $keyword, 'locked'); } sub plang_builtin_factget { my ($self, $plang, $context, $name, $arguments) = @_; my ($channel, $keyword, $meta) = ($arguments->[0]->[1], $arguments->[1]->[1], $arguments->[2]->[1]); - my $result = $self->{pbot}->{factoids}->get_meta($channel, $keyword, $meta); + my $result = $self->{pbot}->{factoids}->{data}->get_meta($channel, $keyword, $meta); if (defined $result) { return [['TYPE', 'String'], $result]; } else { @@ -190,7 +190,7 @@ sub plang_builtin_factset { my ($self, $plang, $context, $name, $arguments) = @_; my ($channel, $keyword, $text) = ($arguments->[0]->[1], $arguments->[1]->[1], $arguments->[2]->[1]); die "Factoid $channel.$keyword is locked. Cannot set.\n" if $self->is_locked($channel, $keyword); - $self->{pbot}->{factoids}->add_factoid('text', $channel, 'Plang', $keyword, $text); + $self->{pbot}->{factoids}->{data}->add_factoid('text', $channel, 'Plang', $keyword, $text); return [['TYPE', 'String'], $text]; } @@ -202,10 +202,10 @@ sub plang_builtin_factappend { my ($self, $plang, $context, $name, $arguments) = @_; my ($channel, $keyword, $text) = ($arguments->[0]->[1], $arguments->[1]->[1], $arguments->[2]->[1]); die "Factoid $channel.$keyword is locked. Cannot append.\n" if $self->is_locked($channel, $keyword); - my $action = $self->{pbot}->{factoids}->get_meta($channel, $keyword, 'action'); + my $action = $self->{pbot}->{factoids}->{data}->get_meta($channel, $keyword, 'action'); $action = "" if not defined $action; $action .= $text; - $self->{pbot}->{factoids}->add_factoid('text', $channel, 'Plang', $keyword, $action); + $self->{pbot}->{factoids}->{data}->add_factoid('text', $channel, 'Plang', $keyword, $action); return [['TYPE', 'String'], $action]; }