mirror of
https://github.com/pragma-/pbot.git
synced 2024-12-23 11:12:42 +01:00
9ebc77f4da
PBot now preserves whitespace by default. The `preserve_whitespace` metadata field is now redundant. It has now been replaced with `condense-whitespace`, which when set to a true value will collapse adjacent whitespace to a single space.
268 lines
8.7 KiB
Perl
268 lines
8.7 KiB
Perl
# File: Data.pm
|
|
#
|
|
# Purpose: Implements factoid data-related functions.
|
|
|
|
# SPDX-FileCopyrightText: 2005-2023 Pragmatic Software <pragma78@gmail.com>
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
package PBot::Core::Factoids::Data;
|
|
use parent 'PBot::Core::Class';
|
|
|
|
use PBot::Imports;
|
|
|
|
use Time::HiRes qw(gettimeofday);
|
|
|
|
our %factoid_metadata = (
|
|
'action' => 'TEXT',
|
|
'action_with_args' => 'TEXT',
|
|
'add_nick' => 'INTEGER',
|
|
'allow_empty_args' => 'INTEGER',
|
|
'background-process' => 'INTEGER',
|
|
'cap-override' => 'TEXT',
|
|
'condense-whitespace' => 'INTEGER',
|
|
'created_on' => 'NUMERIC',
|
|
'dont-protect-self' => 'INTEGER',
|
|
'dont-replace-pronouns' => 'INTEGER',
|
|
'edited_by' => 'TEXT',
|
|
'edited_on' => 'NUMERIC',
|
|
'enabled' => 'INTEGER',
|
|
'help' => 'TEXT',
|
|
'interpolate' => 'INTEGER',
|
|
'keep-quotes' => 'INTEGER',
|
|
'keep-escapes' => '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',
|
|
'process-timeout' => 'INTEGER',
|
|
'rate_limit' => 'INTEGER',
|
|
'ref_count' => 'INTEGER',
|
|
'ref_user' => 'TEXT',
|
|
'require_explicit_args' => 'INTEGER',
|
|
'requires_arguments' => 'INTEGER',
|
|
'suppress-no-output' => 'INTEGER',
|
|
'type' => 'TEXT',
|
|
'unquote_spaces' => 'INTEGER',
|
|
'usage' => 'TEXT',
|
|
'use_output_queue' => 'INTEGER',
|
|
'workdir' => 'TEXT',
|
|
);
|
|
|
|
sub initialize($self, %conf) {
|
|
$self->{storage} = PBot::Core::Storage::DualIndexSQLiteObject->new(
|
|
pbot => $self->{pbot},
|
|
name => 'Factoids',
|
|
filename => $conf{filename},
|
|
);
|
|
}
|
|
|
|
sub load($self) {
|
|
$self->{storage}->load;
|
|
$self->{storage}->create_metadata(\%factoid_metadata);
|
|
}
|
|
|
|
sub save($self, $export = 0) {
|
|
$self->{storage}->save;
|
|
$self->{pbot}->{factoids}->{exporter}->export if $export;
|
|
}
|
|
|
|
sub add($self, $type, $channel, $owner, $trigger, $action, $dont_save = 0) {
|
|
$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'),
|
|
last_referenced_in => '',
|
|
};
|
|
}
|
|
|
|
$self->{storage}->add($channel, $trigger, $data, $dont_save);
|
|
}
|
|
|
|
sub remove($self, $channel, $trigger) {
|
|
$channel = '.*' if $channel !~ /^#/;
|
|
return $self->{storage}->remove($channel, $trigger);
|
|
}
|
|
|
|
sub get_meta($self, $channel, $trigger = undef, $key = undef) {
|
|
return $self->{storage}->get_data($channel, $trigger, $key);
|
|
}
|
|
|
|
sub find($self, $from, $keyword, %opts) {
|
|
my %default_opts = (
|
|
arguments => '',
|
|
exact_channel => 0,
|
|
exact_trigger => 0,
|
|
find_alias => 0
|
|
);
|
|
|
|
%opts = (%default_opts, %opts);
|
|
|
|
my $debug = 0;
|
|
|
|
$from = '.*' if $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("Factoids: find: no match\n");
|
|
} else {
|
|
$self->{pbot}->{logger}->log("Factoids: find: got results: " . (join ', ', map { "$_->[0] -> $_->[1]" } @results) . "\n");
|
|
}
|
|
}
|
|
|
|
return @results;
|
|
};
|
|
|
|
if ($@) {
|
|
$self->{pbot}->{logger}->log("Factoids: error in find: $@\n");
|
|
return undef;
|
|
}
|
|
|
|
return @result;
|
|
}
|
|
|
|
1;
|