diff --git a/PBot/Plugins/Counter.pm b/PBot/Plugins/Counter.pm new file mode 100644 index 00000000..cf24c359 --- /dev/null +++ b/PBot/Plugins/Counter.pm @@ -0,0 +1,346 @@ + +package PBot::Plugins::Counter; + +use warnings; +use strict; + +use Carp (); +use DBI; +use Time::Duration qw/duration/; +use Time::HiRes qw/gettimeofday/; + +sub new { + Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH'; + my ($class, %conf) = @_; + my $self = bless {}, $class; + $self->initialize(%conf); + return $self; +} + +sub initialize { + my ($self, %conf) = @_; + + $self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__); + + $self->{pbot}->{commands}->register(sub { $self->counteradd(@_) }, 'counteradd', 0); + $self->{pbot}->{commands}->register(sub { $self->counterdel(@_) }, 'counterdel', 0); + $self->{pbot}->{commands}->register(sub { $self->counterreset(@_) }, 'counterreset', 0); + $self->{pbot}->{commands}->register(sub { $self->countershow(@_) }, 'countershow', 0); + $self->{pbot}->{commands}->register(sub { $self->counterlist(@_) }, 'counterlist', 0); + + $self->{filename} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/counters.sqlite3'; + + $self->create_database; +} + +sub unload { + my $self = shift; + + $self->{pbot}->{commands}->unregister('counteradd'); + $self->{pbot}->{commands}->unregister('counterdel'); + $self->{pbot}->{commands}->unregister('counterreset'); + $self->{pbot}->{commands}->unregister('countershow'); + $self->{pbot}->{commands}->unregister('counterlist'); +} + +sub create_database { + my $self = shift; + + eval { + $self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", { RaiseError => 1, PrintError => 0, AutoInactiveDestroy => 1 }) or die $DBI::errstr; + + $self->{dbh}->do(<{dbh}->disconnect; + }; + + $self->{pbot}->{logger}->log("Counter create database failed: $@") if $@; +} + +sub dbi_begin { + my ($self) = @_; + eval { + $self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", { RaiseError => 1, PrintError => 0, AutoInactiveDestroy => 1 }) or die $DBI::errstr; + }; + + if ($@) { + $self->{pbot}->{logger}->log("Error opening Counters database: $@"); + return 0; + } else { + return 1; + } +} + +sub dbi_end { + my ($self) = @_; + $self->{dbh}->disconnect; +} + +sub add_counter { + my ($self, $channel, $name, $description) = @_; + + my ($desc, $timestamp) = $self->get_counter($channel, $name); + if (defined $desc) { + return 0; + } + + my $sth = $self->{dbh}->prepare('INSERT INTO Counters (channel, name, description, timestamp) VALUES (?, ?, ?, ?)'); + $sth->bind_param(1, lc $channel); + $sth->bind_param(2, lc $name); + $sth->bind_param(3, $description); + $sth->bind_param(4, scalar gettimeofday); + $sth->execute(); + + return 1; +} + +sub reset_counter { + my ($self, $channel, $name) = @_; + + my ($description, $timestamp) = $self->get_counter($channel, $name); + if (not defined $description) { + return (undef, undef); + } + + my $sth = $self->{dbh}->prepare('UPDATE Counters SET timestamp = ? WHERE channel = ? AND name = ?'); + $sth->bind_param(1, scalar gettimeofday); + $sth->bind_param(2, lc $channel); + $sth->bind_param(3, lc $name); + $sth->execute(); + + return ($description, $timestamp); +} + +sub delete_counter { + my ($self, $channel, $name) = @_; + + my ($description, $timestamp) = $self->get_counter($channel, $name); + if (not defined $description) { + return 0; + } + + my $sth = $self->{dbh}->prepare('DELETE FROM Counters WHERE channel = ? AND name = ?'); + $sth->bind_param(1, lc $channel); + $sth->bind_param(2, lc $name); + $sth->execute(); + + return 1; +} + +sub list_counters { + my ($self, $channel) = @_; + + my $counters = eval { + my $sth = $self->{dbh}->prepare('SELECT name FROM Counters WHERE channel = ?'); + $sth->bind_param(1, lc $channel); + $sth->execute(); + return $sth->fetchall_arrayref(); + }; + + if ($@) { + $self->{pbot}->{logger}->log("Get counter failed: $@"); + } + + return map { $_->[0] } @$counters; +} + +sub get_counter { + my ($self, $channel, $name) = @_; + + my ($description, $time) = eval { + my $sth = $self->{dbh}->prepare('SELECT description, timestamp FROM Counters WHERE channel = ? AND name = ?'); + $sth->bind_param(1, lc $channel); + $sth->bind_param(2, lc $name); + $sth->execute(); + my $row = $sth->fetchrow_hashref(); + return ($row->{description}, $row->{timestamp}); + }; + + if ($@) { + $self->{pbot}->{logger}->log("Get counter failed: $@"); + return undef; + } + + return ($description, $time); +} + +sub counteradd { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + + if (not $self->dbi_begin) { + return "Internal error."; + } + + my ($channel, $name, $description); + + if ($from !~ m/^#/) { + ($channel, $name, $description) = split /\s+/, $arguments, 3; + if (not defined $channel or not defined $name or not defined $description or $channel !~ m/^#/) { + return "Usage from private message: counteradd "; + } + } else { + $channel = $from; + ($name, $description) = split /\s+/, $arguments, 2; + if (not defined $name or not defined $description) { + return "Usage: counteradd "; + } + } + + my $result; + if ($self->add_counter($channel, $name, $description)) { + $result = "Counter added."; + } else { + $result = "Counter '$name' already exists."; + } + + $self->dbi_end; + return $result; +} + +sub counterdel { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + + if (not $self->dbi_begin) { + return "Internal error."; + } + + my ($channel, $name); + + if ($from !~ m/^#/) { + ($channel, $name) = split /\s+/, $arguments, 2; + if (not defined $channel or not defined $name or $channel !~ m/^#/) { + return "Usage from private message: counterdel "; + } + } else { + $channel = $from; + ($name) = split /\s+/, $arguments, 1; + if (not defined $name) { + return "Usage: counterdel "; + } + } + + my $result; + if ($self->delete_counter($channel, $name)) { + $result = "Counter removed."; + } else { + $result = "No such counter."; + } + + $self->dbi_end; + return $result; +} + +sub counterreset { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + + if (not $self->dbi_begin) { + return "Internal error."; + } + + my ($channel, $name); + + if ($from !~ m/^#/) { + ($channel, $name) = split /\s+/, $arguments, 2; + if (not defined $channel or not defined $name or $channel !~ m/^#/) { + return "Usage from private message: counterreset "; + } + } else { + $channel = $from; + ($name) = split /\s+/, $arguments, 1; + if (not defined $name) { + return "Usage: counterreset "; + } + } + + my $result; + my ($description, $timestamp) = $self->reset_counter($channel, $name); + if (defined $description) { + my $ago = duration gettimeofday - $timestamp; + $result = "It had been $ago since $description."; + } else { + $result = "No such counter."; + } + + $self->dbi_end; + return $result; +} + +sub countershow { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + + if (not $self->dbi_begin) { + return "Internal error."; + } + + my ($channel, $name); + + if ($from !~ m/^#/) { + ($channel, $name) = split /\s+/, $arguments, 2; + if (not defined $channel or not defined $name or $channel !~ m/^#/) { + return "Usage from private message: countershow "; + } + } else { + $channel = $from; + ($name) = split /\s+/, $arguments, 1; + if (not defined $name) { + return "Usage: countershow "; + } + } + + my $result; + my ($description, $timestamp) = $self->get_counter($channel, $name); + if (defined $description) { + my $ago = duration gettimeofday - $timestamp; + $result = "It has been $ago since $description."; + } else { + $result = "No such counter."; + } + + $self->dbi_end; + return $result; +} + +sub counterlist { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + + if (not $self->dbi_begin) { + return "Internal error."; + } + + my $channel; + + if ($from !~ m/^#/) { + if (not length $arguments or $arguments !~ m/^#/) { + return "Usage from private message: counterlist "; + } + $channel = $arguments; + } else { + $channel = $from; + } + + my @counters = $self->list_counters($channel); + + my $result; + if (not @counters) { + $result = "No counters available for $channel."; + } else { + my $comma = ''; + $result = "Counters for $channel: "; + foreach my $counter (sort @counters) { + $result .= "$comma$counter"; + $comma = ', '; + } + } + + $self->dbi_end; + return $result; +} + +1;