mirror of
https://github.com/pragma-/pbot.git
synced 2026-03-30 20:18:05 +02:00
Add improved random-quote command with local database
This commit is contained in:
parent
0462e4153d
commit
3c8d98b80b
245
applets/QuoteDB.pm
vendored
Normal file
245
applets/QuoteDB.pm
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
package QuoteDB;
|
||||
|
||||
use v5.26;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
no warnings 'experimental::signatures';
|
||||
|
||||
use DBI;
|
||||
|
||||
my $debug = 10;
|
||||
|
||||
sub new($class, %conf) {
|
||||
my $self = bless {}, $class;
|
||||
$self->initialize(%conf);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub initialize($self, %conf) {
|
||||
$self->{filename} = $conf{filename} // 'quotes.sqlite';
|
||||
}
|
||||
|
||||
sub begin($self) {
|
||||
print STDERR "Opening quotes SQLite database: $self->{filename}\n" if $debug;
|
||||
|
||||
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", undef, undef,
|
||||
{ AutoCommit => 0, AutoInactiveDestroy => 1, RaiseError => 1, PrintError => 0, sqlite_unicode => 1 }) or die $DBI::errstr;
|
||||
|
||||
eval {
|
||||
$self->{dbh}->do(<< 'SQL');
|
||||
CREATE TABLE IF NOT EXISTS Quotes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
text TEXT NOT NULL COLLATE NOCASE,
|
||||
author TEXT NOT NULL COLLATE NOCASE
|
||||
)
|
||||
SQL
|
||||
|
||||
$self->{dbh}->do(<< 'SQL');
|
||||
CREATE TABLE IF NOT EXISTS Seen (
|
||||
channel TEXT NOT NULL COLLATE NOCASE,
|
||||
id INTEGER
|
||||
)
|
||||
SQL
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
}
|
||||
|
||||
sub end($self) {
|
||||
print STDERR "Closing quotes SQLite database\n" if $debug;
|
||||
|
||||
if(exists $self->{dbh} and defined $self->{dbh}) {
|
||||
$self->{dbh}->commit();
|
||||
$self->{dbh}->disconnect();
|
||||
delete $self->{dbh};
|
||||
}
|
||||
}
|
||||
|
||||
sub add_quote($self, $text, $author) {
|
||||
my $id = eval {
|
||||
my $sth = $self->{dbh}->prepare('INSERT INTO Quotes (text, author) VALUES (?, ?)');
|
||||
$sth->bind_param(1, $text) ;
|
||||
$sth->bind_param(2, $author) ;
|
||||
$sth->execute();
|
||||
return $self->{dbh}->sqlite_last_insert_rowid();
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
return $id;
|
||||
}
|
||||
|
||||
sub get_quote($self, $id) {
|
||||
my $quote = eval {
|
||||
my $sth = $self->{dbh}->prepare('SELECT * FROM Quotes WHERE id == ?');
|
||||
$sth->bind_param(1, $id);
|
||||
$sth->execute();
|
||||
return $sth->fetchrow_hashref();
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
return $quote;
|
||||
}
|
||||
|
||||
sub count_random_quote($self, $channel, $text, $author) {
|
||||
# convert from regex metachars to SQL LIKE metachars
|
||||
if (defined $text) {
|
||||
$text =~ s/\.?\*\??/%/g;
|
||||
$text =~ s/\./_/g;
|
||||
}
|
||||
|
||||
my $total = eval {
|
||||
my $sql = 'SELECT COUNT(*) FROM Quotes';
|
||||
my @params;
|
||||
my $joiner = ' WHERE';
|
||||
|
||||
if (defined $text) {
|
||||
$sql .= "$joiner text LIKE ?";
|
||||
push @params, '%' . $text . '%';
|
||||
$joiner = ' AND';
|
||||
}
|
||||
|
||||
if (defined $author) {
|
||||
$sql .= "$joiner author LIKE ?";
|
||||
push @params, '%' . $author. '%';
|
||||
$joiner = ' AND';
|
||||
}
|
||||
|
||||
my $sth = $self->{dbh}->prepare("$sql");
|
||||
$sth->execute(@params);
|
||||
return $sth->fetchrow_hashref();
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
|
||||
my $remaining = eval {
|
||||
my $sql = 'SELECT COUNT(*) FROM Quotes';
|
||||
my @params;
|
||||
my $joiner = ' WHERE';
|
||||
|
||||
if (defined $text) {
|
||||
$sql .= "$joiner text LIKE ?";
|
||||
push @params, '%' . $text . '%';
|
||||
$joiner = ' AND';
|
||||
}
|
||||
|
||||
if (defined $author) {
|
||||
$sql .= "$joiner author LIKE ?";
|
||||
push @params, '%' . $author. '%';
|
||||
$joiner = ' AND';
|
||||
}
|
||||
|
||||
my $sth = $self->{dbh}->prepare("$sql $joiner id NOT IN (SELECT id FROM Seen WHERE channel = ?)");
|
||||
$sth->execute(@params, $channel);
|
||||
return $sth->fetchrow_hashref();
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
return ($total, $remaining);
|
||||
}
|
||||
|
||||
sub get_random_quote($self, $channel, $text, $author) {
|
||||
# convert from regex metachars to SQL LIKE metachars
|
||||
if (defined $text) {
|
||||
$text =~ s/\.?\*\??/%/g;
|
||||
$text =~ s/\./_/g;
|
||||
}
|
||||
|
||||
my $quote = eval {
|
||||
my $sql = 'SELECT * FROM Quotes';
|
||||
my @params;
|
||||
my $joiner = ' WHERE';
|
||||
|
||||
if (defined $text) {
|
||||
$sql .= "$joiner text LIKE ?";
|
||||
push @params, '%' . $text . '%';
|
||||
$joiner = ' AND';
|
||||
}
|
||||
|
||||
if (defined $author) {
|
||||
$sql .= "$joiner author LIKE ?";
|
||||
push @params, '%' . $author. '%';
|
||||
$joiner = ' AND';
|
||||
}
|
||||
|
||||
# search for a random unseen quote
|
||||
my $sth = $self->{dbh}->prepare("$sql $joiner id NOT IN (SELECT id FROM Seen WHERE channel = ?) ORDER BY RANDOM() LIMIT 1");
|
||||
$sth->execute(@params, $channel);
|
||||
my $quote = $sth->fetchrow_hashref();
|
||||
|
||||
# no unseen quote found
|
||||
if (not defined $quote) {
|
||||
# remove queried quotes from Seen table
|
||||
if (not $self->remove_seen($channel, $sql, \@params)) {
|
||||
# no matching quotes in Seen table ergo no quote found
|
||||
return undef;
|
||||
}
|
||||
|
||||
# try again to search for random unseen quote
|
||||
$sth->execute(@params, $channel);
|
||||
$quote = $sth->fetchrow_hashref();
|
||||
}
|
||||
|
||||
# mark quote as seen if found
|
||||
if (defined $quote) {
|
||||
$self->add_seen($channel, $quote->{id});
|
||||
}
|
||||
|
||||
return $quote;
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
return $quote;
|
||||
}
|
||||
|
||||
sub remove_seen($self, $channel, $sql, $params) {
|
||||
$sql =~ s/^SELECT \*/SELECT id/;
|
||||
|
||||
my $count = eval {
|
||||
my $sth = $self->{dbh}->prepare("DELETE FROM Seen WHERE channel = ? AND id IN ($sql)");
|
||||
$sth->execute($channel, @$params);
|
||||
return $sth->rows;
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
return $count;
|
||||
}
|
||||
|
||||
sub add_seen($self, $channel, $id) {
|
||||
eval {
|
||||
my $sth = $self->{dbh}->prepare('INSERT INTO Seen VALUES (?, ?)');
|
||||
$sth->execute($channel, $id);
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
}
|
||||
|
||||
sub get_all_quotes($self) {
|
||||
my $quotes = eval {
|
||||
my $sth = $self->{dbh}->prepare('SELECT * from Quotes');
|
||||
$sth->execute();
|
||||
return $sth->fetchall_arrayref({});
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
return $quotes;
|
||||
}
|
||||
|
||||
sub delete_quote($self, $id) {
|
||||
eval {
|
||||
my $sth = $self->{dbh}->prepare('DELETE FROM Quotes WHERE id == ?');
|
||||
$sth->execute($id);
|
||||
|
||||
$sth = $self->{dbh}->prepare('DELETE FROM Seen WHERE id == ?');
|
||||
$sth->execute($id);
|
||||
};
|
||||
|
||||
die $@ if $@;
|
||||
}
|
||||
|
||||
1;
|
||||
BIN
applets/quotes.sqlite
vendored
Normal file
BIN
applets/quotes.sqlite
vendored
Normal file
Binary file not shown.
87
applets/random-quote.pl
vendored
Executable file
87
applets/random-quote.pl
vendored
Executable file
@ -0,0 +1,87 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
# SPDX-FileCopyrightText: 2009-2023 Pragmatic Software <pragma78@gmail.com>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
use v5.26;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
no warnings 'experimental::signatures';
|
||||
|
||||
use lib '.';
|
||||
use QuoteDB;
|
||||
use Getopt::Long 'GetOptionsFromArray';
|
||||
|
||||
my $usage = 'Usage: quote [text] [-a <author>] [-c] [-h] [-i] -- Use -c to show remaining/total count; -h to hide author; -i to show id';
|
||||
|
||||
my ($text, $author, $channel, $show_count, $hide_author, $show_id);
|
||||
|
||||
$channel = shift @ARGV;
|
||||
|
||||
if (not defined $channel) {
|
||||
print "Must have channel as first argument.\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
{
|
||||
my $opt_error;
|
||||
local $SIG{__WARN__} = sub {
|
||||
$opt_error = shift;
|
||||
chomp $opt_error;
|
||||
};
|
||||
|
||||
Getopt::Long::Configure('bundling_override');
|
||||
|
||||
GetOptionsFromArray(
|
||||
\@ARGV,
|
||||
'author|a=s' => \$author,
|
||||
'count|c' => \$show_count,
|
||||
'hide|h' => \$hide_author,
|
||||
'id|i' => \$show_id,
|
||||
);
|
||||
|
||||
$text = "@ARGV";
|
||||
|
||||
if ($opt_error) {
|
||||
print "$opt_error: $usage\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
if (!length $text && !length $author) {
|
||||
print "$usage\n";
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
|
||||
my $db = QuoteDB->new();
|
||||
|
||||
$db->begin();
|
||||
|
||||
my $quote = $db->get_random_quote($channel, $text, $author);
|
||||
|
||||
if ($show_count) {
|
||||
my ($total, $remaining) = $db->count_random_quote($channel, $text, $author);
|
||||
|
||||
if (defined $total && $total > 0) {
|
||||
$total = $total->{'COUNT(*)'};
|
||||
$remaining = $total - $remaining->{'COUNT(*)'};
|
||||
print "$remaining/$total ";
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined $quote) {
|
||||
print "No quote found.\n";
|
||||
} else {
|
||||
if ($show_id) {
|
||||
print "$quote->{id}: ";
|
||||
}
|
||||
|
||||
if ($hide_author) {
|
||||
print "$quote->{text}\n";
|
||||
} else {
|
||||
print "$quote->{text} -- $quote->{author}\n";
|
||||
}
|
||||
}
|
||||
|
||||
$db->end();
|
||||
@ -25,8 +25,8 @@ use PBot::Imports;
|
||||
# These are set by the /misc/update_version script
|
||||
use constant {
|
||||
BUILD_NAME => "PBot",
|
||||
BUILD_REVISION => 4942,
|
||||
BUILD_DATE => "2026-03-16",
|
||||
BUILD_REVISION => 4943,
|
||||
BUILD_DATE => "2026-03-27",
|
||||
};
|
||||
|
||||
sub initialize {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user