diff --git a/PBot/Plugins/Counter.pm b/PBot/Plugins/Counter.pm index cf24c359..09e877b4 100644 --- a/PBot/Plugins/Counter.pm +++ b/PBot/Plugins/Counter.pm @@ -4,6 +4,9 @@ package PBot::Plugins::Counter; use warnings; use strict; +use feature 'switch'; +no if $] >= 5.018, warnings => "experimental::smartmatch"; + use Carp (); use DBI; use Time::Duration qw/duration/; @@ -22,11 +25,14 @@ sub initialize { $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->{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->{pbot}->{commands}->register(sub { $self->countertrigger(@_) }, 'countertrigger', 0); + + $self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) }); $self->{filename} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/counters.sqlite3'; @@ -41,6 +47,7 @@ sub unload { $self->{pbot}->{commands}->unregister('counterreset'); $self->{pbot}->{commands}->unregister('countershow'); $self->{pbot}->{commands}->unregister('counterlist'); + $self->{pbot}->{commands}->unregister('countertrigger'); } sub create_database { @@ -56,6 +63,14 @@ CREATE TABLE IF NOT EXISTS Counters ( description TEXT, timestamp NUMERIC ) +SQL + + $self->{dbh}->do(<{dbh}->disconnect; @@ -91,12 +106,19 @@ sub add_counter { 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(); + eval { + 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(); + }; + + if ($@) { + $self->{pbot}->{logger}->log("Add counter failed: $@"); + return 0; + } return 1; } @@ -109,11 +131,18 @@ sub reset_counter { 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(); + eval { + 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(); + }; + + if ($@) { + $self->{pbot}->{logger}->log("Reset counter failed: $@"); + return (undef, undef); + } return ($description, $timestamp); } @@ -126,10 +155,18 @@ sub delete_counter { 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(); + eval { + 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(); + }; + + + if ($@) { + $self->{pbot}->{logger}->log("Delete counter failed: $@"); + return 0; + } return 1; } @@ -145,7 +182,7 @@ sub list_counters { }; if ($@) { - $self->{pbot}->{logger}->log("Get counter failed: $@"); + $self->{pbot}->{logger}->log("List counters failed: $@"); } return map { $_->[0] } @$counters; @@ -171,6 +208,83 @@ sub get_counter { return ($description, $time); } +sub add_trigger { + my ($self, $channel, $trigger, $target) = @_; + + my $exists = $self->get_trigger($channel, $trigger); + if (defined $exists) { + return 0; + } + + eval { + my $sth = $self->{dbh}->prepare('INSERT INTO Triggers (channel, trigger, target) VALUES (?, ?, ?)'); + $sth->bind_param(1, lc $channel); + $sth->bind_param(2, lc $trigger); + $sth->bind_param(3, lc $target); + $sth->execute(); + }; + + if ($@) { + $self->{pbot}->{logger}->log("Add trigger failed: $@"); + return 0; + } + + return 1; +} + +sub delete_trigger { + my ($self, $channel, $trigger) = @_; + + my $target = $self->get_trigger($channel, $trigger); + if (not defined $target) { + return 0; + } + + my $sth = $self->{dbh}->prepare('DELETE FROM Triggers WHERE channel = ? AND trigger = ?'); + $sth->bind_param(1, lc $channel); + $sth->bind_param(2, lc $trigger); + $sth->execute(); + + return 1; +} + +sub list_triggers { + my ($self, $channel) = @_; + + my $triggers = eval { + my $sth = $self->{dbh}->prepare('SELECT trigger, target FROM Triggers WHERE channel = ?'); + $sth->bind_param(1, lc $channel); + $sth->execute(); + return $sth->fetchall_arrayref({}); + }; + + if ($@) { + $self->{pbot}->{logger}->log("List triggers failed: $@"); + } + + return @$triggers; +} + +sub get_trigger { + my ($self, $channel, $trigger) = @_; + + my $target = eval { + my $sth = $self->{dbh}->prepare('SELECT target FROM Triggers WHERE channel = ? AND trigger = ?'); + $sth->bind_param(1, lc $channel); + $sth->bind_param(2, lc $trigger); + $sth->execute(); + my $row = $sth->fetchrow_hashref(); + return $row->{target}; + }; + + if ($@) { + $self->{pbot}->{logger}->log("Get trigger failed: $@"); + return undef; + } + + return $target; +} + sub counteradd { my ($self, $from, $nick, $user, $host, $arguments) = @_; @@ -263,7 +377,7 @@ sub counterreset { my ($description, $timestamp) = $self->reset_counter($channel, $name); if (defined $description) { my $ago = duration gettimeofday - $timestamp; - $result = "It had been $ago since $description."; + $result = "It had been $ago since $description. Not any more."; } else { $result = "No such counter."; } @@ -343,4 +457,159 @@ sub counterlist { return $result; } +sub countertrigger { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + + if (not $self->dbi_begin) { + return "Internal error."; + } + + my $command; + ($command, $arguments) = split / /, $arguments, 2; + + my ($channel, $result); + + given ($command) { + when ('list') { + if ($from =~ m/^#/) { + $channel = $from; + } else { + ($channel) = split / /, $arguments, 1; + if ($channel !~ m/^#/) { + $self->dbi_end; + return "Usage from private message: countertrigger list "; + } + } + + my @triggers = $self->list_triggers($channel); + + if (not @triggers) { + $result = "No counter triggers set for $channel."; + } else { + $result = "Triggers for $channel: "; + my $comma = ''; + foreach my $trigger (@triggers) { + $result .= "$comma$trigger->{trigger} -> $trigger->{target}"; + $comma = ', '; + } + } + } + + when ('add') { + if ($from =~ m/^#/) { + $channel = $from; + } else { + ($channel, $arguments) = split / /, $arguments, 2; + if ($channel !~ m/^#/) { + $self->dbi_end; + return "Usage from private message: countertrigger add "; + } + } + + + my ($trigger, $target) = split / /, $arguments, 2; + + if (not defined $trigger or not defined $target) { + if ($from !~ m/^#/) { + $result = "Usage from private message: countertrigger add "; + } else { + $result = "Usage: countertrigger add "; + } + $self->dbi_end; + return $result; + } + + my $exists = $self->get_trigger($channel, $trigger); + + if (defined $exists) { + $self->dbi_end; + return "Trigger already exists."; + } + + if ($self->add_trigger($channel, $trigger, $target)) { + $result = "Trigger added."; + } else { + $result = "Failed to add trigger."; + } + } + + when ('delete') { + if ($from =~ m/^#/) { + $channel = $from; + } else { + ($channel, $arguments) = split / /, $arguments, 2; + if ($channel !~ m/^#/) { + $self->dbi_end; + return "Usage from private message: countertrigger delete "; + } + } + + my ($trigger) = split / /, $arguments, 1; + + if (not defined $trigger) { + if ($from !~ m/^#/) { + $result = "Usage from private message: countertrigger delete "; + } else { + $result = "Usage: countertrigger delete "; + } + $self->dbi_end; + return $result; + } + + my $target = $self->get_trigger($channel, $trigger); + + if (not defined $target) { + $result = "No such trigger."; + } else { + $self->delete_trigger($channel, $trigger); + $result = "Trigger deleted."; + } + } + + default { + $result = "Usage: countertrigger [arguments]"; + } + } + + $self->dbi_end; + return $result; +} + +sub on_public { + my ($self, $event_type, $event) = @_; + my ($nick, $user, $host, $msg) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args); + my $channel = $event->{event}->{to}[0]; + + if (not $self->dbi_begin) { + return 0; + } + + my @triggers = $self->list_triggers($channel); + + my $message = "$nick!$user\@$host $msg"; + + eval { + foreach my $trigger (@triggers) { + if ($message =~ m/$trigger->{trigger}/i) { + my ($desc, $timestamp) = $self->reset_counter($channel, $trigger->{target}); + + if (defined $desc) { + if (gettimeofday - $timestamp >= 60 * 60) { + my $ago = duration gettimeofday - $timestamp; + $event->{conn}->privmsg($channel, "It had been $ago since $desc. Not any more."); + } + } + } + } + }; + + if ($@) { + $self->{pbot}->{logger}->log("Error in counter trigger: $@"); + } + + $self->dbi_end; + + return 0; +} + 1;