mirror of
https://github.com/pragma-/pbot.git
synced 2025-02-18 14:30:40 +01:00
Convert message history to use SQLite database instead of Perl hashtable
Added MessageHistory.pm and MessageHistory_SQLite.pm. May eventually port and add MessageHistory_Hashtable.pm as was done with Quotegrabs, but this is not particularly high on the todo list. Antiflood.pm has been updated to use the new MessageHistory API. The `recall` command has been moved from Quotegrabs into MessageHistory. It also now has the ability to ignore messages containing the recall command itself, for improved usability. Likewise, the `grab` command will now ignore previous `grab` commands when grabbing by regex in order to prevent accidentally grabbing previous grab attempts. The `join` and `part` commands have been improved to accept multiple channels, and `part` will use the current channel if none is provided.
This commit is contained in:
parent
ae1842e3db
commit
54ac8ec0ef
@ -1,12 +1,11 @@
|
|||||||
# File: AntiFlood.pm
|
# File: AntiFlood.pm
|
||||||
# Author: pragma_
|
# Author: pragma_
|
||||||
#
|
#
|
||||||
# Purpose: Keeps track of who has said what and when. Used in
|
# Purpose: Tracks message and nickserv statistics to enforce anti-flooding and
|
||||||
# conjunction with ChanOps and Quotegrabs for kick/ban on flood
|
# ban-evasion detection.
|
||||||
# and grabbing quotes, respectively.
|
|
||||||
#
|
#
|
||||||
# We should take out the message-tracking stuff and put it in its own
|
# The nickserv/ban-evasion stuff probably ought to be in BanTracker or some
|
||||||
# MessageTracker class.
|
# such suitable class.
|
||||||
|
|
||||||
package PBot::AntiFlood;
|
package PBot::AntiFlood;
|
||||||
|
|
||||||
@ -15,8 +14,6 @@ use strict;
|
|||||||
|
|
||||||
use feature 'switch';
|
use feature 'switch';
|
||||||
|
|
||||||
use Storable;
|
|
||||||
|
|
||||||
use vars qw($VERSION);
|
use vars qw($VERSION);
|
||||||
$VERSION = $PBot::PBot::VERSION;
|
$VERSION = $PBot::PBot::VERSION;
|
||||||
|
|
||||||
@ -29,7 +26,7 @@ use Carp ();
|
|||||||
|
|
||||||
sub new {
|
sub new {
|
||||||
if(ref($_[1]) eq 'HASH') {
|
if(ref($_[1]) eq 'HASH') {
|
||||||
Carp::croak("Options to AntiFlood should be key/value pairs, not hash reference");
|
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference");
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($class, %conf) = @_;
|
my ($class, %conf) = @_;
|
||||||
@ -42,32 +39,26 @@ sub new {
|
|||||||
sub initialize {
|
sub initialize {
|
||||||
my ($self, %conf) = @_;
|
my ($self, %conf) = @_;
|
||||||
|
|
||||||
my $pbot = delete $conf{pbot};
|
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
|
||||||
if(not defined $pbot) {
|
|
||||||
Carp::croak("Missing pbot reference to AntiFlood");
|
|
||||||
}
|
|
||||||
|
|
||||||
$self->{pbot} = $pbot;
|
# flags for 'validated' field
|
||||||
$self->{FLOOD_IGNORE} = -1;
|
$self->{NICKSERV_VALIDATED} = (1<<0);
|
||||||
$self->{FLOOD_CHAT} = 0;
|
$self->{NEEDS_CHECKBAN} = (1<<1);
|
||||||
$self->{FLOOD_JOIN} = 1;
|
|
||||||
|
|
||||||
$self->{ENTER_ABUSE_MAX_LINES} = 4;
|
$self->{ENTER_ABUSE_MAX_LINES} = 4;
|
||||||
$self->{ENTER_ABUSE_MAX_OFFENSES} = 3;
|
$self->{ENTER_ABUSE_MAX_OFFENSES} = 3;
|
||||||
$self->{ENTER_ABUSE_MAX_SECONDS} = 20;
|
$self->{ENTER_ABUSE_MAX_SECONDS} = 20;
|
||||||
|
|
||||||
$self->load_message_history;
|
$self->{channels} = {}; # per-channel statistics, e.g. for optimized tracking of last spoken nick for enter-abuse detection, etc
|
||||||
$self->{channels} = {}; # per-channel statistics, e.g. for optimized tracking of last spoken nick, etc
|
|
||||||
|
|
||||||
my $filename = delete $conf{filename} // $self->{pbot}->{data_dir} . '/ban_whitelist';
|
my $filename = delete $conf{banwhitelist_file} // $self->{pbot}->{data_dir} . '/ban_whitelist';
|
||||||
$self->{ban_whitelist} = PBot::DualIndexHashObject->new(name => 'BanWhitelist', filename => $filename);
|
$self->{ban_whitelist} = PBot::DualIndexHashObject->new(name => 'BanWhitelist', filename => $filename);
|
||||||
$self->{ban_whitelist}->load;
|
$self->{ban_whitelist}->load;
|
||||||
|
|
||||||
$pbot->timer->register(sub { $self->prune_message_history }, 60 * 60 * 1);
|
$self->{pbot}->timer->register(sub { $self->adjust_offenses }, 60 * 60 * 1);
|
||||||
|
|
||||||
$pbot->commands->register(sub { return $self->unbanme(@_) }, "unbanme", 0);
|
$self->{pbot}->commands->register(sub { return $self->unbanme(@_) }, "unbanme", 0);
|
||||||
$pbot->commands->register(sub { return $self->whitelist(@_) }, "whitelist", 10);
|
$self->{pbot}->commands->register(sub { return $self->whitelist(@_) }, "whitelist", 10);
|
||||||
$pbot->commands->register(sub { return $self->save_message_history_cmd(@_) }, "save_message_history", 60);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub ban_whitelisted {
|
sub ban_whitelisted {
|
||||||
@ -135,136 +126,68 @@ sub whitelist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_flood_account {
|
sub check_join_watch {
|
||||||
my ($self, $nick, $user, $host) = @_;
|
|
||||||
|
|
||||||
return "$nick!$user\@$host" if exists $self->message_history->{"$nick!$user\@$host"};
|
|
||||||
|
|
||||||
my $found_link;
|
|
||||||
foreach my $mask (keys %{ $self->message_history }) {
|
|
||||||
# check if foo!bar@baz matches foo!*@*; e.g., same nick, but possibly different user@host
|
|
||||||
# (usually logging into nickserv or a dynamic ip address, but could possibly be attempted nick hijacking)
|
|
||||||
if($mask =~ m/^\Q$nick\E!.*/i) {
|
|
||||||
#$self->{pbot}->logger->log("anti-flood: [get-account] $nick!$user\@$host seen previously as $mask\n");
|
|
||||||
$found_link = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# check if foo!bar@baz matches *!bar@baz; e.g., same user@host, but different nick
|
|
||||||
# (usually alternate-nicks due to rejoining)
|
|
||||||
if($mask =~ m/!\Q$user\E@\Q$host\E$/i) {
|
|
||||||
$found_link = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($found_link) {
|
|
||||||
$self->{pbot}->logger->log("anti-flood: [get-account] $nick!$user\@$host linked to $mask\n");
|
|
||||||
$self->{message_history}->{"$nick!$user\@$host"} = $self->{message_history}->{$mask};
|
|
||||||
|
|
||||||
foreach my $channel (keys %{ $self->{message_history}->{$mask}->{channels} }) {
|
|
||||||
$self->{message_history}->{$mask}->{channels}->{$channel}{validated} = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(exists $self->{message_history}->{$mask}->{nickserv_accounts}) {
|
|
||||||
foreach my $nickserv_account (keys $self->{message_history}->{$mask}->{nickserv_accounts}) {
|
|
||||||
$self->check_nickserv_accounts($nick, $nickserv_account, "$nick!$user\@$host");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "$nick!$user\@$host";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub add_message {
|
|
||||||
my ($self, $account, $channel, $text, $mode) = @_;
|
my ($self, $account, $channel, $text, $mode) = @_;
|
||||||
my $now = gettimeofday;
|
|
||||||
|
|
||||||
return undef if $channel =~ /[@!]/; # ignore QUIT messages from nick!user@host channels
|
return if $channel =~ /[@!]/; # ignore QUIT messages from nick!user@host channels
|
||||||
|
|
||||||
$text =~ s/^$self->{pbot}->{trigger}login\s+\S+/$self->{pbot}->{trigger}login <redacted>/; # redact login passwords (e.g., from `recall` command, etc)
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'join_watch');
|
||||||
|
|
||||||
if(not exists $self->message_history->{$account}->{channels}->{$channel}) {
|
if($mode == $self->{pbot}->{messagehistory}->{MSG_JOIN}) {
|
||||||
#$self->{pbot}->logger->log("adding new channel for existing nick\n");
|
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{offenses} = 0;
|
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{last_offense_timestamp} = 0;
|
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{join_watch} = 0;
|
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{messages} = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
#$self->{pbot}->logger->log("appending new message\n");
|
|
||||||
push(@{ $self->message_history->{$account}->{channels}->{$channel}{messages} }, { timestamp => $now, msg => $text, mode => $mode });
|
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{last_spoken} = $now;
|
|
||||||
|
|
||||||
my $length = $#{ $self->message_history->{$account}->{channels}->{$channel}{messages} } + 1;
|
|
||||||
|
|
||||||
if($mode == $self->{FLOOD_JOIN}) {
|
|
||||||
if($text =~ /^JOIN/) {
|
if($text =~ /^JOIN/) {
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{join_watch}++;
|
$channel_data->{join_watch}++;
|
||||||
|
$self->{pbot}->logger->log("Join watch incremented to $channel_data->{join_watch} for $account\n");
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
} else {
|
} else {
|
||||||
# PART or QUIT
|
# PART or QUIT
|
||||||
# check QUIT message for netsplits, and decrement joinwatch if found
|
# check QUIT message for netsplits, and decrement joinwatch to allow a free rejoin
|
||||||
if($text =~ /^QUIT .*\.net .*\.split/) {
|
if($text =~ /^QUIT .*\.net .*\.split/) {
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{join_watch}--;
|
if($channel_data->{join_watch} > 0) {
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{join_watch} = 0 if $self->message_history->{$account}->{channels}->{$channel}{join_watch} < 0;
|
$channel_data->{join_watch}--;
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{messages}->[$length - 1]{mode} = $self->{FLOOD_IGNORE};
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
# check QUIT message for Ping timeout or Excess Flood
|
# check QUIT message for Ping timeout or Excess Flood
|
||||||
elsif($text =~ /^QUIT Ping timeout/ or $text =~ /^QUIT Excess Flood/) {
|
elsif($text =~ /^QUIT Ping timeout/ or $text =~ /^QUIT Excess Flood/) {
|
||||||
# ignore these (used to treat aggressively)
|
# ignore these (used to treat aggressively)
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{messages}->[$length - 1]{mode} = $self->{FLOOD_IGNORE};
|
|
||||||
} else {
|
} else {
|
||||||
# some other type of QUIT or PART
|
# some other type of QUIT or PART
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{messages}->[$length - 1]{mode} = $self->{FLOOD_IGNORE};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elsif($mode == $self->{FLOOD_CHAT}) {
|
} elsif($mode == $self->{pbot}->{messagehistory}->{MSG_CHAT}) {
|
||||||
# reset joinwatch if they send a message
|
# reset joinwatch if they send a message
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{join_watch} = 0;
|
if($channel_data->{join_watch} > 0) {
|
||||||
|
$channel_data->{join_watch} = 0;
|
||||||
|
$self->{pbot}->logger->log("Join watch reset for $account\n");
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
# keep only MAX_NICK_MESSAGES message history per channel
|
|
||||||
while($length >= $self->{pbot}->{MAX_NICK_MESSAGES}) {
|
|
||||||
my %msg = %{ shift(@{ $self->message_history->{$account}->{channels}->{$channel}{messages} }) };
|
|
||||||
#$self->{pbot}->logger->log("shifting message off top: $msg{msg}, $msg{timestamp}\n");
|
|
||||||
$length--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub check_flood {
|
sub check_flood {
|
||||||
my ($self, $channel, $nick, $user, $host, $text, $max_messages, $max_time, $mode) = @_;
|
my ($self, $channel, $nick, $user, $host, $text, $max_messages, $max_time, $mode) = @_;
|
||||||
|
|
||||||
$channel = lc $channel;
|
$channel = lc $channel;
|
||||||
my $mask = "$nick!$user\@$host";
|
|
||||||
|
|
||||||
|
my $mask = "$nick!$user\@$host";
|
||||||
$self->{pbot}->logger->log(sprintf("%-14s | %-65s | %s\n", $channel eq $mask ? "QUIT" : $channel, $mask, $text));
|
$self->{pbot}->logger->log(sprintf("%-14s | %-65s | %s\n", $channel eq $mask ? "QUIT" : $channel, $mask, $text));
|
||||||
|
|
||||||
my $account = $self->get_flood_account($nick, $user, $host);
|
my $account = $self->{pbot}->{messagehistory}->get_message_account($nick, $user, $host);
|
||||||
|
|
||||||
if(not defined $account) {
|
|
||||||
$account = $mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
# handle QUIT events
|
# handle QUIT events
|
||||||
# (these events come from $channel nick!user@host, not a specific channel or nick,
|
# (these events come from $channel nick!user@host, not a specific channel or nick,
|
||||||
# so they need to be dispatched to all channels the nick has been seen on)
|
# so they need to be dispatched to all channels the nick has been seen on)
|
||||||
if($mode == $self->{FLOOD_JOIN} and $text =~ /^QUIT/) {
|
if($mode == $self->{pbot}->{messagehistory}->{MSG_JOIN} and $text =~ /^QUIT/) {
|
||||||
return if not exists $self->message_history->{$account}; # don't create empty account
|
my @channels = $self->{pbot}->{messagehistory}->{database}->get_channels($account);
|
||||||
foreach my $chan (keys %{ $self->message_history->{$account}->{channels} }) {
|
foreach my $chan (@channels) {
|
||||||
next if $chan !~ m/^#/; # skip non-channels (private messages, etc)
|
$self->check_join_watch($account, $chan, $text, $mode);
|
||||||
$self->add_message($account, $chan, $text, $mode);
|
|
||||||
# remove validation on QUITs so we check for ban-evasion when user returns at a later time
|
|
||||||
$self->message_history->{$account}->{channels}->{$chan}{validated} = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->devalidate_all_channels($account);
|
||||||
# don't do flood processing for QUIT events
|
# don't do flood processing for QUIT events
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $length = $self->add_message($account, $channel, $text, $mode);
|
$self->check_join_watch($account, $channel, $text, $mode);
|
||||||
return if not defined $length;
|
|
||||||
|
|
||||||
# do not do flood processing for bot messages
|
# do not do flood processing for bot messages
|
||||||
if($nick eq $self->{pbot}->botnick) {
|
if($nick eq $self->{pbot}->botnick) {
|
||||||
@ -275,15 +198,13 @@ sub check_flood {
|
|||||||
# do not do flood processing if channel is not in bot's channel list or bot is not set as chanop for the channel
|
# do not do flood processing if channel is not in bot's channel list or bot is not set as chanop for the channel
|
||||||
return if ($channel =~ /^#/) and (not exists $self->{pbot}->channels->channels->hash->{$channel} or $self->{pbot}->channels->channels->hash->{$channel}{chanop} == 0);
|
return if ($channel =~ /^#/) and (not exists $self->{pbot}->channels->channels->hash->{$channel} or $self->{pbot}->channels->channels->hash->{$channel}{chanop} == 0);
|
||||||
|
|
||||||
if($channel =~ /^#/ and $mode == $self->{FLOOD_JOIN} and $text =~ /^PART/) {
|
if($channel =~ /^#/ and $mode == $self->{pbot}->{messagehistory}->{MSG_JOIN} and $text =~ /^PART/) {
|
||||||
# remove validation on PART so we check for ban-evasion when user returns at a later time
|
# remove validation on PART so we check for ban-evasion when user returns at a later time
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{validated} = 0;
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'validated');
|
||||||
|
if($channel_data->{validated} & $self->{NICKSERV_VALIDATED}) {
|
||||||
|
$channel_data->{validated} &= ~$self->{NICKSERV_VALIDATED};
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
# do not do flood enforcement for this event if bot is lagging
|
|
||||||
if($self->{pbot}->lagchecker->lagging) {
|
|
||||||
$self->{pbot}->logger->log("Disregarding enforcement of anti-flood due to lag: " . $self->{pbot}->lagchecker->lagstring . "\n");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($max_messages > $self->{pbot}->{MAX_NICK_MESSAGES}) {
|
if($max_messages > $self->{pbot}->{MAX_NICK_MESSAGES}) {
|
||||||
@ -292,107 +213,129 @@ sub check_flood {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# check for ban evasion if channel begins with # (not private message) and hasn't yet been validated against ban evasion
|
# check for ban evasion if channel begins with # (not private message) and hasn't yet been validated against ban evasion
|
||||||
if($channel =~ m/^#/ and not $self->message_history->{$account}->{channels}->{$channel}{validated}) {
|
if($channel =~ m/^#/ and not $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'validated')->{'validated'} & $self->{NICKSERV_VALIDATED}) {
|
||||||
if($mode == $self->{FLOOD_JOIN} and $text =~ /^PART/) {
|
if($mode == $self->{pbot}->{messagehistory}->{MSG_JOIN} and $text =~ /^PART/) {
|
||||||
# don't check for evasion on PARTs
|
# don't check for evasion on PARTs
|
||||||
} else {
|
} else {
|
||||||
$self->{pbot}->conn->whois($nick);
|
$self->{pbot}->conn->whois($nick);
|
||||||
$self->check_bans($account, $channel);
|
$self->check_bans($account, $mask, $channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($mode == $self->{FLOOD_CHAT} and $channel =~ m/^#/) {
|
# do not do flood enforcement for this event if bot is lagging
|
||||||
|
if($self->{pbot}->lagchecker->lagging) {
|
||||||
|
$self->{pbot}->logger->log("Disregarding enforcement of anti-flood due to lag: " . $self->{pbot}->lagchecker->lagstring . "\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($mode == $self->{pbot}->{messagehistory}->{MSG_CHAT} and $channel =~ m/^#/) {
|
||||||
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'enter_abuse', 'enter_abuses');
|
||||||
|
|
||||||
if(defined $self->{channels}->{$channel}->{last_spoken_nick} and $nick eq $self->{channels}->{$channel}->{last_spoken_nick}) {
|
if(defined $self->{channels}->{$channel}->{last_spoken_nick} and $nick eq $self->{channels}->{$channel}->{last_spoken_nick}) {
|
||||||
my %msg = %{ @{ $self->message_history->{$account}->{channels}->{$channel}{messages} }[$length - 2] };
|
my $messages = $self->{pbot}->{messagehistory}->{database}->get_recent_messages($account, $channel, 2, $self->{pbot}->{messagehistory}->{MSG_CHAT});
|
||||||
my %last = %{ @{ $self->message_history->{$account}->{channels}->{$channel}{messages} }[$length - 1] };
|
|
||||||
|
|
||||||
if($last{timestamp} - $msg{timestamp} <= $self->{ENTER_ABUSE_MAX_SECONDS}) {
|
if($messages->[1]->{timestamp} - $messages->[0]->{timestamp} <= $self->{ENTER_ABUSE_MAX_SECONDS}) {
|
||||||
if(++$self->message_history->{$account}->{channels}->{$channel}{enter_abuse} >= $self->{ENTER_ABUSE_MAX_LINES} - 1) {
|
if(++$channel_data->{enter_abuse} >= $self->{ENTER_ABUSE_MAX_LINES} - 1) {
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{enter_abuse} = $self->{ENTER_ABUSE_MAX_LINES} / 2 - 1;
|
$channel_data->{enter_abuse} = $self->{ENTER_ABUSE_MAX_LINES} / 2 - 1;
|
||||||
if(++$self->message_history->{$account}->{channels}->{$channel}{enter_abuses} >= $self->{ENTER_ABUSE_MAX_OFFENSES}) {
|
if(++$channel_data->{enter_abuses} >= $self->{ENTER_ABUSE_MAX_OFFENSES}) {
|
||||||
my $offenses = $self->message_history->{$account}->{channels}->{$channel}{enter_abuses} - $self->{ENTER_ABUSE_MAX_OFFENSES} + 1;
|
my $offenses = $channel_data->{enter_abuses} - $self->{ENTER_ABUSE_MAX_OFFENSES} + 1;
|
||||||
my $ban_length = $offenses ** $offenses * $offenses * 30;
|
my $ban_length = $offenses ** $offenses * $offenses * 30;
|
||||||
$self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $ban_length);
|
$self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $ban_length);
|
||||||
$ban_length = duration($ban_length);
|
$ban_length = duration($ban_length);
|
||||||
$self->{pbot}->logger->log("$nick $channel enter abuse offense " . $self->message_history->{$account}->{channels}->{$channel}{enter_abuses} . " earned $ban_length ban\n");
|
$self->{pbot}->logger->log("$nick $channel enter abuse offense " . $channel_data->{enter_abuses} . " earned $ban_length ban\n");
|
||||||
$self->{pbot}->conn->privmsg($nick, "You have been muted due to abusing the enter key. Please do not split your sentences over multiple messages. You will be allowed to speak again in $ban_length.");
|
$self->{pbot}->conn->privmsg($nick, "You have been muted due to abusing the enter key. Please do not split your sentences over multiple messages. You will be allowed to speak again in $ban_length.");
|
||||||
} else {
|
} else {
|
||||||
$self->{pbot}->logger->log("$nick $channel enter abuses counter incremented to " . $self->message_history->{$account}->{channels}->{$channel}{enter_abuses} . "\n");
|
$self->{pbot}->logger->log("$nick $channel enter abuses counter incremented to " . $channel_data->{enter_abuses} . "\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$self->{pbot}->logger->log("$nick $channel enter abuse counter incremented to " . $self->message_history->{$account}->{channels}->{$channel}{enter_abuse} . "\n");
|
$self->{pbot}->logger->log("$nick $channel enter abuse counter incremented to " . $channel_data->{enter_abuse} . "\n");
|
||||||
}
|
}
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
} else {
|
} else {
|
||||||
|
if($channel_data->{enter_abuse} > 0) {
|
||||||
$self->{pbot}->logger->log("$nick $channel more than $self->{ENTER_ABUSE_MAX_SECONDS} seconds since last message, enter abuse counter reset\n");
|
$self->{pbot}->logger->log("$nick $channel more than $self->{ENTER_ABUSE_MAX_SECONDS} seconds since last message, enter abuse counter reset\n");
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{enter_abuse} = 0;
|
$channel_data->{enter_abuse} = 0;
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$self->{channels}->{$channel}->{last_spoken_nick} = $nick;
|
$self->{channels}->{$channel}->{last_spoken_nick} = $nick;
|
||||||
$self->{pbot}->logger->log("$nick $channel enter abuse counter reset\n") if defined $self->message_history->{$account}->{channels}->{$channel}{enter_abuse} and $self->message_history->{$account}->{channels}->{$channel}{enter_abuse} > 0;
|
if($channel_data->{enter_abuse} > 0) {
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{enter_abuse} = 0;
|
$self->{pbot}->logger->log("$nick $channel enter abuse counter reset\n");
|
||||||
|
$channel_data->{enter_abuse} = 0;
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($max_messages > 0 and $length >= $max_messages) {
|
if($max_messages > 0 and $self->{pbot}->{messagehistory}->{database}->get_max_messages($account, $channel) >= $max_messages) {
|
||||||
# $self->{pbot}->logger->log("More than $max_messages messages, comparing time differences ($max_time)\n") if $mode == $self->{FLOOD_JOIN};
|
my $msg;
|
||||||
|
if($mode == $self->{pbot}->{messagehistory}->{MSG_CHAT}) {
|
||||||
my %msg;
|
$msg = $self->{pbot}->{messagehistory}->{database}->recall_message_by_count($account, $channel, $max_messages - 1)
|
||||||
if($mode == $self->{FLOOD_CHAT}) {
|
|
||||||
%msg = %{ @{ $self->message_history->{$account}->{channels}->{$channel}{messages} }[$length - $max_messages] };
|
|
||||||
}
|
}
|
||||||
elsif($mode == $self->{FLOOD_JOIN}) {
|
elsif($mode == $self->{pbot}->{messagehistory}->{MSG_JOIN}) {
|
||||||
my $count = 0;
|
my $joins = $self->{pbot}->{messagehistory}->{database}->get_recent_messages($account, $channel, $max_messages, $self->{pbot}->{messagehistory}->{MSG_JOIN});
|
||||||
my $i = $length - 1;
|
$msg = $joins->[0];
|
||||||
# $self->{pbot}->logger->log("Checking flood history, i = $i\n") if $self->message_history->{$account}->{channels}->{$channel}{join_watch} >= $max_messages;
|
|
||||||
for(; $i >= 0; $i--) {
|
|
||||||
# $self->{pbot}->logger->log($i . " " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{mode} ." " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{msg} . " " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{timestamp} . " [" . ago_exact(time - $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{timestamp}) . "]\n") if $self->message_history->{$account}->{channels}->{$channel}{join_watch} >= $max_messages;
|
|
||||||
next if $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{mode} != $self->{FLOOD_JOIN};
|
|
||||||
last if ++$count >= 4;
|
|
||||||
}
|
|
||||||
$i = 0 if $i < 0;
|
|
||||||
%msg = %{ @{ $self->message_history->{$account}->{channels}->{$channel}{messages} }[$i] };
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$self->{pbot}->logger->log("Unknown flood mode [$mode] ... aborting flood enforcement.\n");
|
$self->{pbot}->logger->log("Unknown flood mode [$mode] ... aborting flood enforcement.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my %last = %{ @{ $self->message_history->{$account}->{channels}->{$channel}{messages} }[$length - 1] };
|
my $last = $self->{pbot}->{messagehistory}->{database}->recall_message_by_count($account, $channel, 0);
|
||||||
|
|
||||||
if($last{timestamp} - $msg{timestamp} <= $max_time && not $self->{pbot}->admins->loggedin($channel, "$nick!$user\@$host")) {
|
$self->{pbot}->logger->log(" msg: [$msg->{timestamp}] $msg->{msg}\n");
|
||||||
if($mode == $self->{FLOOD_JOIN}) {
|
$self->{pbot}->logger->log("last: [$last->{timestamp}] $last->{msg}\n");
|
||||||
if($self->message_history->{$account}->{channels}->{$channel}{join_watch} >= $max_messages) {
|
$self->{pbot}->logger->log("Comparing message timestamps $last->{timestamp} - $msg->{timestamp} = " . ($last->{timestamp} - $msg->{timestamp}) . " against max_time $max_time\n");
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{offenses}++;
|
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{last_offense_timestamp} = gettimeofday;
|
|
||||||
|
|
||||||
my $timeout = (2 ** (($self->message_history->{$account}->{channels}->{$channel}{offenses} + 2) < 10 ? $self->message_history->{$account}->{channels}->{$channel}{offenses} + 2 : 10));
|
if($last->{timestamp} - $msg->{timestamp} <= $max_time && not $self->{pbot}->admins->loggedin($channel, "$nick!$user\@$host")) {
|
||||||
|
if($mode == $self->{pbot}->{messagehistory}->{MSG_JOIN}) {
|
||||||
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'offenses', 'last_offense', 'join_watch');
|
||||||
|
$self->{pbot}->{logger}->log("$account offenses $channel_data->{offenses}, join watch $channel_data->{join_watch}, max messages $max_messages\n");
|
||||||
|
if($channel_data->{join_watch} >= $max_messages) {
|
||||||
|
$channel_data->{offenses}++;
|
||||||
|
$channel_data->{last_offense} = gettimeofday;
|
||||||
|
|
||||||
|
my $timeout = (2 ** (($channel_data->{offenses} + 2) < 10 ? $channel_data->{offenses} + 2 : 10));
|
||||||
my $banmask = address_to_mask($host);
|
my $banmask = address_to_mask($host);
|
||||||
|
|
||||||
$self->{pbot}->chanops->ban_user_timed("*!$user\@$banmask\$##stop_join_flood", $channel, $timeout * 60 * 60);
|
$self->{pbot}->chanops->ban_user_timed("*!$user\@$banmask\$##stop_join_flood", $channel, $timeout * 60 * 60);
|
||||||
$self->{pbot}->logger->log("$nick!$user\@$banmask banned for $timeout hours due to join flooding (offense #" . $self->message_history->{$account}->{channels}->{$channel}{offenses} . ").\n");
|
$self->{pbot}->logger->log("$nick!$user\@$banmask banned for $timeout hours due to join flooding (offense #" . $channel_data->{offenses} . ").\n");
|
||||||
$self->{pbot}->conn->privmsg($nick, "You have been banned from $channel due to join flooding. If your connection issues have been fixed, or this was an accident, you may request an unban at any time by responding to this message with: unbanme $channel, otherwise you will be automatically unbanned in $timeout hours.");
|
$self->{pbot}->conn->privmsg($nick, "You have been banned from $channel due to join flooding. If your connection issues have been fixed, or this was an accident, you may request an unban at any time by responding to this message with: unbanme $channel, otherwise you will be automatically unbanned in $timeout hours.");
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{join_watch} = $max_messages - 2; # give them a chance to rejoin
|
$channel_data->{join_watch} = $max_messages - 2; # give them a chance to rejoin
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
}
|
}
|
||||||
} elsif($mode == $self->{FLOOD_CHAT}) {
|
} elsif($mode == $self->{pbot}->{messagehistory}->{MSG_CHAT}) {
|
||||||
|
if($channel =~ /^#/) { #channel flood (opposed to private message or otherwise)
|
||||||
# don't increment offenses again if already banned
|
# don't increment offenses again if already banned
|
||||||
return if $self->{pbot}->chanops->{unban_timeout}->find_index($channel, "*!$user\@$host");
|
return if $self->{pbot}->chanops->{unban_timeout}->find_index($channel, "*!$user\@$host");
|
||||||
|
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{offenses}++;
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'offenses', 'last_offense');
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{last_offense_timestamp} = gettimeofday;
|
$channel_data->{offenses}++;
|
||||||
|
$channel_data->{last_offense} = gettimeofday;
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
|
|
||||||
my $length = $self->message_history->{$account}->{channels}->{$channel}{offenses} ** $self->message_history->{$account}->{channels}->{$channel}{offenses} * $self->message_history->{$account}->{channels}->{$channel}{offenses} * 30;
|
my $length = $channel_data->{offenses} ** $channel_data->{offenses} * $channel_data->{offenses} * 30;
|
||||||
|
|
||||||
if($channel =~ /^#/) { #channel flood (opposed to private message or otherwise)
|
|
||||||
$self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $length);
|
$self->{pbot}->chanops->ban_user_timed("*!$user\@$host", $channel, $length);
|
||||||
$length = duration($length);
|
$length = duration($length);
|
||||||
$self->{pbot}->logger->log("$nick $channel flood offense " . $self->message_history->{$account}->{channels}->{$channel}{offenses} . " earned $length ban\n");
|
$self->{pbot}->logger->log("$nick $channel flood offense " . $channel_data->{offenses} . " earned $length ban\n");
|
||||||
$self->{pbot}->conn->privmsg($nick, "You have been muted due to flooding. Please use a web paste service such as http://codepad.org for lengthy pastes. You will be allowed to speak again in $length.");
|
$self->{pbot}->conn->privmsg($nick, "You have been muted due to flooding. Please use a web paste service such as http://codepad.org for lengthy pastes. You will be allowed to speak again in $length.");
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
}
|
}
|
||||||
else { # private message flood
|
else { # private message flood
|
||||||
return if exists ${ $self->{pbot}->ignorelist->{ignore_list} }{"$nick!$user\@$host"}{$channel};
|
return if exists ${ $self->{pbot}->ignorelist->{ignore_list} }{"$nick!$user\@$host"}{$channel};
|
||||||
|
|
||||||
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'offenses', 'last_offense');
|
||||||
|
$channel_data->{offenses}++;
|
||||||
|
$channel_data->{last_offense} = gettimeofday;
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
|
|
||||||
|
my $length = $channel_data->{offenses} ** $channel_data->{offenses} * $channel_data->{offenses} * 30;
|
||||||
|
|
||||||
$self->{pbot}->{ignorelistcmds}->ignore_user("", "floodcontrol", "", "", "$nick!$user\@$host $channel $length");
|
$self->{pbot}->{ignorelistcmds}->ignore_user("", "floodcontrol", "", "", "$nick!$user\@$host $channel $length");
|
||||||
$length = duration($length);
|
$length = duration($length);
|
||||||
$self->{pbot}->logger->log("$nick msg flood offense " . $self->message_history->{$account}->{channels}->{$channel}{offenses} . " earned $length ignore\n");
|
$self->{pbot}->logger->log("$nick msg flood offense " . $channel_data->{offenses} . " earned $length ignore\n");
|
||||||
$self->{pbot}->conn->privmsg($nick, "You have used too many commands in too short a time period, you have been ignored for $length.");
|
$self->{pbot}->conn->privmsg($nick, "You have used too many commands in too short a time period, you have been ignored for $length.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -400,60 +343,6 @@ sub check_flood {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub message_history {
|
|
||||||
my $self = shift;
|
|
||||||
return $self->{message_history};
|
|
||||||
}
|
|
||||||
|
|
||||||
sub prune_message_history {
|
|
||||||
my $self = shift;
|
|
||||||
|
|
||||||
$self->{pbot}->logger->log("Pruning message history . . .\n");
|
|
||||||
|
|
||||||
foreach my $mask (keys %{ $self->{message_history} }) {
|
|
||||||
foreach my $channel (keys %{ $self->{message_history}->{$mask}->{channels} }) {
|
|
||||||
my $length = $#{ $self->{message_history}->{$mask}->{channels}->{$channel}{messages} } + 1;
|
|
||||||
|
|
||||||
if($length <= 0) {
|
|
||||||
$self->{pbot}->logger->log("[prune-message-history] $mask in $channel has no messages, removing channel entry\n");
|
|
||||||
delete $self->{message_history}->{$mask}->{channels}->{$channel};
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
my %last = %{ @{ $self->{message_history}->{$mask}->{channels}->{$channel}{messages} }[$length - 1] };
|
|
||||||
|
|
||||||
# delete channel key if no activity for a while
|
|
||||||
if(gettimeofday - $last{timestamp} >= 60 * 60 * 24 * 90) {
|
|
||||||
$self->{pbot}->logger->log("[prune-message-history] $mask in $channel hasn't spoken in ninety days; removing channel history.\n");
|
|
||||||
delete $self->{message_history}->{$mask}->{channels}->{$channel};
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
# decrease offenses counter if 24 hours of elapsed without any new offense
|
|
||||||
if ($self->{message_history}->{$mask}->{channels}->{$channel}{offenses} > 0 and
|
|
||||||
$self->{message_history}->{$mask}->{channels}->{$channel}{last_offense_timestamp} > 0 and
|
|
||||||
(gettimeofday - $self->{message_history}->{$mask}->{channels}->{$channel}{last_offense_timestamp} >= 60 * 60 * 24)) {
|
|
||||||
$self->{message_history}->{$mask}->{channels}->{$channel}{offenses}--;
|
|
||||||
$self->{message_history}->{$mask}->{channels}->{$channel}{last_offense_timestamp} = gettimeofday;
|
|
||||||
$self->{pbot}->logger->log("[prune-message-history] [$channel][$mask] 24 hours since last offense/decrease -- decreasing offenses to $self->{message_history}->{$mask}->{channels}->{$channel}{offenses}\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
# decrease enter abuses counter once an hour
|
|
||||||
if(defined $self->message_history->{$mask}->{channels}->{$channel}{enter_abuses} and $self->message_history->{$mask}->{channels}->{$channel}{enter_abuses} > 0) {
|
|
||||||
$self->message_history->{$mask}->{channels}->{$channel}{enter_abuses}--;
|
|
||||||
$self->{pbot}->logger->log("[prune-message-history] [$channel][$mask] decreasing enter abuse offenses to $self->{message_history}->{$mask}->{channels}->{$channel}{enter_abuses}\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# delete account for this $mask if all its channels have been deleted
|
|
||||||
if(scalar keys %{ $self->{message_history}->{$mask} } == 0) {
|
|
||||||
$self->{pbot}->logger->log("[prune-message-history] $mask has no more channels remaining; deleting history account.\n");
|
|
||||||
delete $self->{message_history}->{$mask};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$self->save_message_history;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub unbanme {
|
sub unbanme {
|
||||||
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
||||||
my $channel = lc $arguments;
|
my $channel = lc $arguments;
|
||||||
@ -470,14 +359,10 @@ sub unbanme {
|
|||||||
return "/msg $nick There is no temporary ban set for $mask in channel $channel.";
|
return "/msg $nick There is no temporary ban set for $mask in channel $channel.";
|
||||||
}
|
}
|
||||||
|
|
||||||
my $nickserv_accounts;
|
my $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
|
||||||
if(exists $self->{message_history}->{"$nick!$user\@$host"}->{nickserv_accounts}) {
|
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
|
||||||
$nickserv_accounts = $self->{message_history}->{"$nick!$user\@$host"}->{nickserv_accounts};
|
|
||||||
} else {
|
|
||||||
$nickserv_accounts->{-1} = undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach my $nickserv_account (keys $nickserv_accounts) {
|
foreach my $nickserv_account (@nickserv_accounts) {
|
||||||
my $baninfos = $self->{pbot}->bantracker->get_baninfo("$nick!$user\@$host", $channel, $nickserv_account);
|
my $baninfos = $self->{pbot}->bantracker->get_baninfo("$nick!$user\@$host", $channel, $nickserv_account);
|
||||||
|
|
||||||
if(defined $baninfos) {
|
if(defined $baninfos) {
|
||||||
@ -495,8 +380,8 @@ sub unbanme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my $account = $self->get_flood_account($nick, $user, $host);
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'offenses');
|
||||||
if(defined $account and $self->message_history->{$account}->{channels}->{$channel}{offenses} > 2) {
|
if($channel_data->{offenses} > 2) {
|
||||||
return "/msg $nick You may only use unbanme for the first two offenses. You will be automatically unbanned in a few hours, and your offense counter will decrement once every 24 hours.";
|
return "/msg $nick You may only use unbanme for the first two offenses. You will be automatically unbanned in a few hours, and your offense counter will decrement once every 24 hours.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,123 +413,107 @@ sub address_to_mask {
|
|||||||
sub devalidate_accounts {
|
sub devalidate_accounts {
|
||||||
# remove validation on accounts in $channel that match a ban/quiet $mask
|
# remove validation on accounts in $channel that match a ban/quiet $mask
|
||||||
my ($self, $mask, $channel) = @_;
|
my ($self, $mask, $channel) = @_;
|
||||||
my ($nickserv_accounts, $ban_account);
|
my @message_accounts;
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log("Devalidating accounts for $mask in $channel\n");
|
||||||
|
|
||||||
if($mask =~ m/^\$a:(.*)/) {
|
if($mask =~ m/^\$a:(.*)/) {
|
||||||
$ban_account = lc $1;
|
my $ban_account = lc $1;
|
||||||
|
@message_accounts = $self->{pbot}->{messagehistory}->{database}->find_message_accounts_by_nickserv($ban_account);
|
||||||
} else {
|
} else {
|
||||||
$ban_account = undef;
|
@message_accounts = $self->{pbot}->{messagehistory}->{database}->find_message_accounts_by_mask($mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
my $mask_original = $mask;
|
foreach my $account (@message_accounts) {
|
||||||
$mask = quotemeta $mask;
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'validated');
|
||||||
$mask =~ s/\\\*/.*?/g;
|
if(defined $channel_data and $channel_data->{validated} & $self->{NICKSERV_VALIDATED}) {
|
||||||
$mask =~ s/\\\?/./g;
|
$channel_data->{validated} &= ~$self->{NICKSERV_VALIDATED};
|
||||||
|
$self->{pbot}->logger->log("Devalidating account $account\n");
|
||||||
foreach my $account (keys %{ $self->{message_history} }) {
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
|
||||||
if(exists $self->{message_history}->{$account}->{channels}->{$channel}) {
|
|
||||||
if(defined $ban_account and exists $self->{message_history}->{$account}->{nickserv_accounts}) {
|
|
||||||
$nickserv_accounts = $self->{message_history}->{$account}->{nickserv_accounts};
|
|
||||||
} else {
|
|
||||||
$nickserv_accounts = undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $devalidate = 0;
|
|
||||||
if(defined $ban_account and defined $nickserv_accounts) {
|
|
||||||
foreach my $nickserv_account (keys $nickserv_accounts) {
|
|
||||||
if($nickserv_account eq $ban_account) {
|
|
||||||
$devalidate = 1;
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($devalidate or $account =~ m/^$mask$/i) {
|
|
||||||
$self->{pbot}->logger->log("anti-flood: [devalidate-accounts] $account matches $mask_original in $channel, devalidating\n");
|
|
||||||
$self->message_history->{$account}->{channels}->{$channel}{validated} = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub check_bans {
|
sub check_bans {
|
||||||
my ($self, $mask, $channel) = @_;
|
my ($self, $message_account, $mask, $channel) = @_;
|
||||||
my ($bans, @nickserv_accounts, $nick, $host, $do_not_validate);
|
|
||||||
|
|
||||||
# $self->{pbot}->logger->log("anti-flood: [check-bans] checking for bans on $mask in $channel\n");
|
$self->{pbot}->logger->log("anti-flood: [check-bans] checking for bans on $mask in $channel\n");
|
||||||
|
|
||||||
if(exists $self->{message_history}->{$mask}->{nickserv_accounts}) {
|
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
|
||||||
foreach my $account (keys $self->{message_history}->{$mask}->{nickserv_accounts}) {
|
my $current_nickserv_account = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account);
|
||||||
# $self->{pbot}->logger->log("anti-flood: [check-bans] $mask is using account $account\n");
|
|
||||||
push @nickserv_accounts, $account;
|
if($current_nickserv_account) {
|
||||||
|
$self->{pbot}->logger->log("anti-flood: [check-bans] current nickserv [$current_nickserv_account] found for $mask\n");
|
||||||
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
|
||||||
|
if($channel_data->{validated} & $self->{NEEDS_CHECKBAN}) {
|
||||||
|
$channel_data->{validated} &= ~$self->{NEEDS_CHECKBAN};
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
|
||||||
}
|
}
|
||||||
delete $self->{message_history}->{$mask}->{channels}->{$channel}{needs_validation};
|
|
||||||
} else {
|
} else {
|
||||||
# mark this account as needing check-bans when nickserv account is identified
|
# mark this account as needing check-bans when nickserv account is identified
|
||||||
$self->{message_history}->{$mask}->{channels}->{$channel}{needs_validation} = 1;
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
|
||||||
# $self->{pbot}->logger->log("anti-flood: [check-bans] no account for $mask; marking for later validation\n");
|
if(not $channel_data->{validated} & $self->{NEEDS_CHECKBAN}) {
|
||||||
|
$channel_data->{validated} |= $self->{NEEDS_CHECKBAN};
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
|
||||||
|
}
|
||||||
|
$self->{pbot}->logger->log("anti-flood: [check-bans] no account for $mask; marking for later validation\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
($nick, $host) = $mask =~ m/^([^!]+)![^@]+\@(.*)$/;
|
my ($nick, $host) = $mask =~ m/^([^!]+)![^@]+\@(.*)$/;
|
||||||
|
|
||||||
foreach my $account (keys %{ $self->{message_history} }) {
|
my $hostmasks = $self->{pbot}->{messagehistory}->{database}->get_hostmasks_for_channel($channel);
|
||||||
if(exists $self->{message_history}->{$account}->{channels}->{$channel}) {
|
|
||||||
|
my ($do_not_validate, $bans);
|
||||||
|
foreach my $hostmask (@$hostmasks) {
|
||||||
|
my @hostmask_nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($hostmask->{id});
|
||||||
my $check_ban = 0;
|
my $check_ban = 0;
|
||||||
my $target_nickserv_accounts;
|
|
||||||
|
|
||||||
# check if nickserv accounts match
|
# check if nickserv accounts match
|
||||||
foreach my $nickserv_account (@nickserv_accounts) {
|
foreach my $nickserv_account (@nickserv_accounts) {
|
||||||
if(exists $self->{message_history}->{$account}->{nickserv_accounts}) {
|
foreach my $key (@hostmask_nickserv_accounts) {
|
||||||
foreach my $key ($self->{message_history}->{$account}->{nickserv_accounts}) {
|
|
||||||
if($key eq $nickserv_account) {
|
if($key eq $nickserv_account) {
|
||||||
#$self->{pbot}->logger->log("anti-flood: [check-bans] nickserv account for $account matches $nickserv_account\n");
|
$self->{pbot}->logger->log("anti-flood: [check-bans] nickserv account for $hostmask->{hostmask} matches $nickserv_account\n");
|
||||||
$target_nickserv_accounts = $self->{message_history}->{$account}->{nickserv_accounts};
|
|
||||||
$check_ban = 1;
|
$check_ban = 1;
|
||||||
goto CHECKBAN;
|
goto CHECKBAN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
# check if hosts match
|
# check if hosts match
|
||||||
my ($account_host) = $account =~ m/\@(.*)$/;
|
my ($account_host) = $hostmask->{hostmask} =~ m/\@(.*)$/;
|
||||||
|
|
||||||
if($host eq $account_host) {
|
if($host eq $account_host) {
|
||||||
#$self->{pbot}->logger->log("anti-flood: [check-bans] host for $account matches $mask\n");
|
$self->{pbot}->logger->log("anti-flood: [check-bans] host for $hostmask->{hostmask} matches $mask\n");
|
||||||
if(exists $self->{message_history}->{$account}->{nickserv_accounts}) {
|
|
||||||
$target_nickserv_accounts = $self->{message_history}->{$account}->{nickserv_accounts};
|
|
||||||
}
|
|
||||||
$check_ban = 1;
|
$check_ban = 1;
|
||||||
goto CHECKBAN;
|
goto CHECKBAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
# check if nicks match
|
# check if nicks match
|
||||||
my ($account_nick) = $account =~ m/^([^!]+)/;
|
my ($account_nick) = $hostmask->{hostmask} =~ m/^([^!]+)/;
|
||||||
|
|
||||||
if($nick eq $account_nick) {
|
if($nick eq $account_nick) {
|
||||||
#$self->{pbot}->logger->log("anti-flood: [check-bans] nick for $account matches $mask\n");
|
$self->{pbot}->logger->log("anti-flood: [check-bans] nick for $hostmask->{hostmask} matches $mask\n");
|
||||||
if(exists $self->{message_history}->{$account}->{nickserv_accounts}) {
|
|
||||||
$target_nickserv_accounts = $self->{message_history}->{$account}->{nickserv_accounts};
|
|
||||||
}
|
|
||||||
$check_ban = 1;
|
$check_ban = 1;
|
||||||
goto CHECKBAN;
|
goto CHECKBAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECKBAN:
|
CHECKBAN:
|
||||||
if($check_ban) {
|
if($check_ban) {
|
||||||
if(not defined $target_nickserv_accounts) {
|
if(not @hostmask_nickserv_accounts) {
|
||||||
$target_nickserv_accounts->{-1} = undef;
|
push @hostmask_nickserv_accounts, -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach my $target_nickserv_account (keys $target_nickserv_accounts) {
|
foreach my $target_nickserv_account (@hostmask_nickserv_accounts) {
|
||||||
# $self->{pbot}->logger->log("anti-flood: [check-bans] checking for bans in $channel on $account using $target_nickserv_account\n");
|
$self->{pbot}->logger->log("anti-flood: [check-bans] checking for bans in $channel on $hostmask->{hostmask} using $target_nickserv_account\n");
|
||||||
my $baninfos = $self->{pbot}->bantracker->get_baninfo($account, $channel, $target_nickserv_account);
|
my $baninfos = $self->{pbot}->bantracker->get_baninfo($hostmask->{hostmask}, $channel, $target_nickserv_account);
|
||||||
|
|
||||||
if(defined $baninfos) {
|
if(defined $baninfos) {
|
||||||
foreach my $baninfo (@$baninfos) {
|
foreach my $baninfo (@$baninfos) {
|
||||||
if(time - $baninfo->{when} < 5) {
|
if(time - $baninfo->{when} < 5) {
|
||||||
$self->{pbot}->logger->log("anti-flood: [check-bans] $mask evaded $baninfo->{banmask} in $baninfo->{channel}, but within 5 seconds of establishing ban; giving another chance\n");
|
$self->{pbot}->logger->log("anti-flood: [check-bans] $mask evaded $baninfo->{banmask} in $baninfo->{channel}, but within 5 seconds of establishing ban; giving another chance\n");
|
||||||
$self->message_history->{$mask}->{channels}->{$channel}{validated} = 0;
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
|
||||||
|
if($channel_data->{validated} & $self->{NICKSERV_VALIDATED}) {
|
||||||
|
$channel_data->{validated} &= ~$self->{NICKSERV_VALIDATED};
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
|
||||||
|
}
|
||||||
$do_not_validate = 1;
|
$do_not_validate = 1;
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
@ -670,8 +539,8 @@ sub check_bans {
|
|||||||
|
|
||||||
my $skip_quiet_nickserv_mask = 0;
|
my $skip_quiet_nickserv_mask = 0;
|
||||||
foreach my $nickserv_account (@nickserv_accounts) {
|
foreach my $nickserv_account (@nickserv_accounts) {
|
||||||
if($baninfo->{type} eq '+q' and $baninfo->{banmask} =~ /^\$a:(.*)/ and lc $1 eq $nickserv_account and $nickserv_account eq $self->{message_history}->{$mask}->{nickserv_account}) {
|
if($baninfo->{type} eq '+q' and $baninfo->{banmask} =~ /^\$a:(.*)/ and lc $1 eq $nickserv_account and $nickserv_account eq $current_nickserv_account) {
|
||||||
$self->{pbot}->logger->log("anti-flood: [check-bans] Hostmask ($mask) matches account ($nickserv_account), disregarding\n");
|
$self->{pbot}->logger->log("anti-flood: [check-bans] Hostmask ($mask) matches quiet on account ($nickserv_account), disregarding\n");
|
||||||
$skip_quiet_nickserv_mask = 1;
|
$skip_quiet_nickserv_mask = 1;
|
||||||
} elsif($baninfo->{type} eq '+b' and $baninfo->{banmask} =~ /^\$a:(.*)/ and lc $1 eq $nickserv_account) {
|
} elsif($baninfo->{type} eq '+b' and $baninfo->{banmask} =~ /^\$a:(.*)/ and lc $1 eq $nickserv_account) {
|
||||||
$skip_quiet_nickserv_mask = 0;
|
$skip_quiet_nickserv_mask = 0;
|
||||||
@ -692,7 +561,6 @@ sub check_bans {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(defined $bans) {
|
if(defined $bans) {
|
||||||
$mask =~ m/[^!]+!([^@]+)@(.*)/;
|
$mask =~ m/[^!]+!([^@]+)@(.*)/;
|
||||||
@ -703,55 +571,66 @@ sub check_bans {
|
|||||||
my ($bannick) = $mask =~ m/^([^!]+)/;
|
my ($bannick) = $mask =~ m/^([^!]+)/;
|
||||||
$self->{pbot}->chanops->add_op_command($baninfo->{channel}, "kick $baninfo->{channel} $bannick Ban evasion");
|
$self->{pbot}->chanops->add_op_command($baninfo->{channel}, "kick $baninfo->{channel} $bannick Ban evasion");
|
||||||
$self->{pbot}->chanops->ban_user_timed($banmask, $baninfo->{channel}, 60 * 60 * 12);
|
$self->{pbot}->chanops->ban_user_timed($banmask, $baninfo->{channel}, 60 * 60 * 12);
|
||||||
$self->message_history->{$mask}->{channels}->{$channel}{validated} = 0;
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
|
||||||
|
if($channel_data->{validated} & $self->{NICKSERV_VALIDATED}) {
|
||||||
|
$channel_data->{validated} &= ~$self->{NICKSERV_VALIDATED};
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->message_history->{$mask}->{channels}->{$channel}{validated} = 1 unless $do_not_validate;
|
unless($do_not_validate) {
|
||||||
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
|
||||||
|
if(not $channel_data->{validated} & $self->{NICKSERV_VALIDATED}) {
|
||||||
|
$channel_data->{validated} |= $self->{NICKSERV_VALIDATED};
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub check_nickserv_accounts {
|
sub check_nickserv_accounts {
|
||||||
my ($self, $nick, $account, $hostmask) = @_;
|
my ($self, $nick, $account, $hostmask) = @_;
|
||||||
my $force_validation = 0;
|
my $force_validation = 0;
|
||||||
|
my $message_account;
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log("Checking nickserv accounts for nick $nick with account $account and hostmask " . (defined $hostmask ? $hostmask : 'undef') . "\n");
|
||||||
|
|
||||||
$account = lc $account;
|
$account = lc $account;
|
||||||
|
|
||||||
if(not defined $hostmask) {
|
if(not defined $hostmask) {
|
||||||
foreach my $mask (keys %{ $self->{message_history} }) {
|
($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($nick);
|
||||||
if(exists $self->{message_history}->{$mask}->{nickserv_accounts} and exists $self->{message_history}->{$mask}->{nickserv_accounts}->{$account}) {
|
|
||||||
# pre-existing mask found using this account previously
|
if(not defined $message_account) {
|
||||||
#$self->{pbot}->logger->log("anti-flood: [check-account] $nick [nickserv: $account] seen previously as $mask.\n");
|
$self->{pbot}->logger->log("No message account found for nick $nick.\n");
|
||||||
$hostmask = $mask;
|
($message_account) = $self->{pbot}->{messagehistory}->{database}->find_message_accounts_by_nickserv($account);
|
||||||
}
|
|
||||||
else {
|
if(not $message_account) {
|
||||||
# no nickserv account set yet
|
$self->{pbot}->logger->log("No message account found for nickserv $account.\n");
|
||||||
if($mask =~ m/^\Q$nick\E!/i) {
|
return;
|
||||||
# nick matches, must belong to account
|
|
||||||
$hostmask = $mask;
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
($message_account) = $self->{pbot}->{messagehistory}->{database}->find_message_accounts_by_mask($hostmask);
|
||||||
|
if(not $message_account) {
|
||||||
|
$self->{pbot}->logger->log("No message account found for hostmask $hostmask.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
$force_validation = 1;
|
$force_validation = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(not defined $hostmask) {
|
$self->{pbot}->logger->log("anti-flood: $message_account: setting nickserv account to [$account]\n");
|
||||||
# could not find mask for nick/account
|
$self->{pbot}->{messagehistory}->{database}->update_nickserv_account($message_account, $account, scalar gettimeofday);
|
||||||
$self->{pbot}->logger->log("anti-flood: [check-account] could not find mask for $nick [nickserv: $account]\n");
|
$self->{pbot}->{messagehistory}->{database}->set_current_nickserv_account($message_account, $account);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$self->{pbot}->logger->log("anti-flood: $hostmask: setting nickserv account to [$account]\n");
|
|
||||||
$self->{message_history}->{$hostmask}->{nickserv_accounts}->{$account} = gettimeofday;
|
|
||||||
$self->{message_history}->{$hostmask}->{nickserv_account} = $account;
|
|
||||||
|
|
||||||
# check to see if any channels need check-ban validation
|
# check to see if any channels need check-ban validation
|
||||||
foreach my $channel (keys %{ $self->message_history->{$hostmask}->{channels} }) {
|
$hostmask = $self->{pbot}->{messagehistory}->{database}->find_most_recent_hostmask($message_account);
|
||||||
if($force_validation or exists $self->message_history->{$hostmask}->{channels}{$channel}->{needs_validation}) {
|
my @channels = $self->{pbot}->{messagehistory}->{database}->get_channels($message_account);
|
||||||
# $self->{pbot}->logger->log("anti-flood: [check-account] $nick [nickserv: $account] needs check-ban validation for $hostmask in $channel.\n");
|
foreach my $channel (@channels) {
|
||||||
$self->check_bans($hostmask, $channel);
|
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
|
||||||
|
if($force_validation or $channel_data->{validated} & $self->{NEEDS_CHECKBAN}) {
|
||||||
|
$self->{pbot}->logger->log("anti-flood: [check-account] $nick [nickserv: $account] needs check-ban validation for $hostmask in $channel.\n");
|
||||||
|
$self->check_bans($message_account, $hostmask, $channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -765,27 +644,32 @@ sub on_whoisaccount {
|
|||||||
$self->check_nickserv_accounts($nick, $account);
|
$self->check_nickserv_accounts($nick, $account);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub save_message_history {
|
sub adjust_offenses {
|
||||||
my $self = shift;
|
|
||||||
$self->{pbot}->logger->log("Saving message history\n");
|
|
||||||
store($self->{message_history}, $self->{pbot}->{message_history_file});
|
|
||||||
$self->{pbot}->logger->log("Message history saved\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
sub load_message_history {
|
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
if(-e $self->{pbot}->{message_history_file}) {
|
$self->{pbot}->logger->log("Adjusting offenses . . .\n");
|
||||||
$self->{message_history} = retrieve($self->{pbot}->{message_history_file});
|
|
||||||
} else {
|
# decrease offenses counter if 24 hours have elapsed since latest offense
|
||||||
$self->{message_history} = {};
|
my $channel_datas = $self->{pbot}->{messagehistory}->{database}->get_channel_datas_where_last_offense_older_than(gettimeofday - 60 * 60 * 24);
|
||||||
|
foreach my $channel_data (@$channel_datas) {
|
||||||
|
if($channel_data->{offenses} > 0) {
|
||||||
|
my $id = delete $channel_data->{id};
|
||||||
|
my $channel = delete $channel_data->{channel};
|
||||||
|
$channel_data->{offenses}--;
|
||||||
|
$channel_data->{last_offense} = gettimeofday;
|
||||||
|
$self->{pbot}->logger->log("[adjust-offenses] [$id][$channel] 24 hours since last offense/decrease -- decreasing offenses to $channel_data->{offenses}\n");
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($id, $channel, $channel_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub save_message_history_cmd {
|
$channel_datas = $self->{pbot}->{messagehistory}->{database}->get_channel_datas_with_enter_abuses();
|
||||||
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
foreach my $channel_data (@$channel_datas) {
|
||||||
$self->save_message_history;
|
my $id = delete $channel_data->{id};
|
||||||
return "Message history saved.";
|
my $channel = delete $channel_data->{channel};
|
||||||
|
$channel_data->{enter_abuses}--;
|
||||||
|
$self->{pbot}->logger->log("[adjust-offenses] [$id][$channel] decreasing enter abuse offenses to $channel_data->{enter_abuses}\n");
|
||||||
|
$self->{pbot}->{messagehistory}->{database}->update_channel_data($id, $channel, $channel_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -137,9 +137,11 @@ sub join_channel {
|
|||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($from, $nick, $user, $host, $arguments) = @_;
|
my ($from, $nick, $user, $host, $arguments) = @_;
|
||||||
|
|
||||||
# FIXME -- update %channels hash?
|
foreach my $channel (split /\s+/, $arguments) {
|
||||||
$self->{pbot}->logger->log("$nick!$user\@$host made me join $arguments\n");
|
$self->{pbot}->logger->log("$nick!$user\@$host made me join $channel\n");
|
||||||
$self->{pbot}->conn->join($arguments);
|
$self->{pbot}->conn->join($channel);
|
||||||
|
}
|
||||||
|
|
||||||
return "/msg $nick Joining $arguments";
|
return "/msg $nick Joining $arguments";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +149,13 @@ sub part_channel {
|
|||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($from, $nick, $user, $host, $arguments) = @_;
|
my ($from, $nick, $user, $host, $arguments) = @_;
|
||||||
|
|
||||||
# FIXME -- update %channels hash?
|
$arguments = $from if not $arguments;
|
||||||
$self->{pbot}->logger->log("$nick!$user\@$host made me part $arguments\n");
|
|
||||||
$self->{pbot}->conn->part($arguments);
|
foreach my $channel (split /\s+/, $arguments) {
|
||||||
|
$self->{pbot}->logger->log("$nick!$user\@$host made me part $channel\n");
|
||||||
|
$self->{pbot}->conn->part($channel);
|
||||||
|
}
|
||||||
|
|
||||||
return "/msg $nick Parting $arguments";
|
return "/msg $nick Parting $arguments";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,10 +163,13 @@ sub ack_die {
|
|||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($from, $nick, $user, $host, $arguments) = @_;
|
my ($from, $nick, $user, $host, $arguments) = @_;
|
||||||
$self->{pbot}->logger->log("$nick!$user\@$host made me exit.\n");
|
$self->{pbot}->logger->log("$nick!$user\@$host made me exit.\n");
|
||||||
|
|
||||||
|
# TODO: move all of those to an registerable atexit handler
|
||||||
$self->{pbot}->factoids->save_factoids;
|
$self->{pbot}->factoids->save_factoids;
|
||||||
$self->{pbot}->ignorelist->save_ignores;
|
$self->{pbot}->ignorelist->save_ignores;
|
||||||
$self->{pbot}->antiflood->save_message_history;
|
$self->{pbot}->{quotegrabs}->{database}->end();
|
||||||
$self->{pbot}->{quotegrabs}->{quotegrabs_db}->end();
|
$self->{pbot}->{messagehistory}->{database}->end();
|
||||||
|
|
||||||
$self->{pbot}->conn->privmsg($from, "Good-bye.") if defined $from;
|
$self->{pbot}->conn->privmsg($from, "Good-bye.") if defined $from;
|
||||||
$self->{pbot}->conn->quit("Departure requested.");
|
$self->{pbot}->conn->quit("Departure requested.");
|
||||||
exit 0;
|
exit 0;
|
||||||
|
@ -183,7 +183,9 @@ sub on_join {
|
|||||||
my ($self, $conn, $event) = @_;
|
my ($self, $conn, $event) = @_;
|
||||||
my ($nick, $user, $host, $channel) = ($event->nick, $event->user, $event->host, $event->to);
|
my ($nick, $user, $host, $channel) = ($event->nick, $event->user, $event->host, $event->to);
|
||||||
|
|
||||||
$self->{pbot}->antiflood->check_flood($channel, $nick, $user, $host, "JOIN", 4, 60 * 30, $self->{pbot}->antiflood->{FLOOD_JOIN});
|
my $message_account = $self->{pbot}->{messagehistory}->get_message_account($nick, $user, $host);
|
||||||
|
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, "JOIN", $self->{pbot}->{messagehistory}->{MSG_JOIN});
|
||||||
|
$self->{pbot}->antiflood->check_flood($channel, $nick, $user, $host, "JOIN", 4, 60 * 30, $self->{pbot}->{messagehistory}->{MSG_JOIN});
|
||||||
}
|
}
|
||||||
|
|
||||||
sub on_departure {
|
sub on_departure {
|
||||||
@ -193,7 +195,19 @@ sub on_departure {
|
|||||||
my $text = uc $event->type;
|
my $text = uc $event->type;
|
||||||
$text .= " $args";
|
$text .= " $args";
|
||||||
|
|
||||||
$self->{pbot}->antiflood->check_flood($channel, $nick, $user, $host, $text, 4, 60 * 30, $self->{pbot}->antiflood->{FLOOD_JOIN});
|
my $message_account = $self->{pbot}->{messagehistory}->get_message_account($nick, $user, $host);
|
||||||
|
|
||||||
|
if($text =~ m/^QUIT/) {
|
||||||
|
# QUIT messages must be dispatched to each channel the user is on
|
||||||
|
my @channels = $self->{pbot}->{messagehistory}->{database}->get_channels($message_account);
|
||||||
|
foreach my $chan (@channels) {
|
||||||
|
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $chan, $text, $self->{pbot}->{messagehistory}->{MSG_JOIN});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, $text, $self->{pbot}->{messagehistory}->{MSG_JOIN});
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{pbot}->antiflood->check_flood($channel, $nick, $user, $host, $text, 4, 60 * 30, $self->{pbot}->{messagehistory}->{MSG_JOIN});
|
||||||
|
|
||||||
my $admin = $self->{pbot}->admins->find_admin($channel, "$nick!$user\@$host");
|
my $admin = $self->{pbot}->admins->find_admin($channel, "$nick!$user\@$host");
|
||||||
if(defined $admin and $admin->{loggedin}) {
|
if(defined $admin and $admin->{loggedin}) {
|
||||||
|
@ -97,7 +97,10 @@ sub process_line {
|
|||||||
|
|
||||||
my $pbot = $self->pbot;
|
my $pbot = $self->pbot;
|
||||||
|
|
||||||
$pbot->antiflood->check_flood($from, $nick, $user, $host, $text, $pbot->{MAX_FLOOD_MESSAGES}, 10, $pbot->antiflood->{FLOOD_CHAT}) if defined $from;
|
my $message_account = $pbot->{messagehistory}->get_message_account($nick, $user, $host);
|
||||||
|
$pbot->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $from, $text, $pbot->{messagehistory}->{MSG_CHAT});
|
||||||
|
|
||||||
|
$pbot->antiflood->check_flood($from, $nick, $user, $host, $text, $pbot->{MAX_FLOOD_MESSAGES}, 10, $pbot->{messagehistory}->{MSG_CHAT}) if defined $from;
|
||||||
|
|
||||||
$text =~ s/^\s+//;
|
$text =~ s/^\s+//;
|
||||||
$text =~ s/\s+$//;
|
$text =~ s/\s+$//;
|
||||||
|
131
PBot/MessageHistory.pm
Normal file
131
PBot/MessageHistory.pm
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# File: MessageHistory.pm
|
||||||
|
# Author: pragma_
|
||||||
|
#
|
||||||
|
# Purpose: Keeps track of who has said what and when, as well as their
|
||||||
|
# nickserv accounts and alter-hostmasks.
|
||||||
|
#
|
||||||
|
# Used in conjunction with AntiFlood and Quotegrabs for kick/ban on
|
||||||
|
# flood/ban-evasion and grabbing quotes, respectively.
|
||||||
|
|
||||||
|
package PBot::MessageHistory;
|
||||||
|
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
use Time::HiRes qw(gettimeofday tv_interval);
|
||||||
|
use Time::Duration;
|
||||||
|
use Carp ();
|
||||||
|
|
||||||
|
use PBot::MessageHistory_SQLite;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
if(ref($_[1]) eq 'HASH') {
|
||||||
|
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference");
|
||||||
|
}
|
||||||
|
|
||||||
|
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->{filename} = delete $conf{filename} // $self->{pbot}->{data_dir} . '/message_history.sqlite3';
|
||||||
|
|
||||||
|
$self->{database} = PBot::MessageHistory_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
|
||||||
|
$self->{database}->begin();
|
||||||
|
$self->{database}->devalidate_all_channels();
|
||||||
|
|
||||||
|
$self->{MSG_CHAT} = 0;
|
||||||
|
$self->{MSG_JOIN} = 1;
|
||||||
|
|
||||||
|
$self->{pbot}->commands->register(sub { $self->recall_message(@_) }, "recall", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_message_account {
|
||||||
|
my ($self, $nick, $user, $host) = @_;
|
||||||
|
return $self->{database}->get_message_account($nick, $user, $host);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_message {
|
||||||
|
my ($self, $account, $mask, $channel, $text, $mode) = @_;
|
||||||
|
$self->{database}->add_message($account, $mask, $channel, { timestamp => scalar gettimeofday, msg => $text, mode => $mode });
|
||||||
|
}
|
||||||
|
|
||||||
|
sub recall_message {
|
||||||
|
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
||||||
|
|
||||||
|
if(not defined $from) {
|
||||||
|
$self->{pbot}->logger->log("Command missing ~from parameter!\n");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(not defined $arguments or not length $arguments) {
|
||||||
|
return "Usage: recall <nick> [history [channel]] -- where [history] is an optional argument that is either an integral number of recent messages or a regex (without whitespace) of the text within the message; e.g., to recall the 3rd most recent message for nick, use `recall nick 3` or to recall a message containing 'pizza', use `recall nick pizza`; and [channel] is an optional channel, so you can use it from /msg (you will need to also specify [history] in this case)";
|
||||||
|
}
|
||||||
|
|
||||||
|
$arguments = lc $arguments;
|
||||||
|
|
||||||
|
my @recalls = split /\s\+\s/, $arguments;
|
||||||
|
|
||||||
|
my ($recall_nick, $recall_history, $channel, $recall_nicks, $recall_text);
|
||||||
|
|
||||||
|
foreach my $recall (@recalls) {
|
||||||
|
($recall_nick, $recall_history, $channel) = split(/\s+/, $recall, 3);
|
||||||
|
|
||||||
|
$recall_history = $nick eq $recall_nick ? 2 : 1 if not defined $recall_history; # skip recall command if recalling self without arguments
|
||||||
|
$channel = $from if not defined $channel;
|
||||||
|
|
||||||
|
my ($account, $found_nick) = $self->{database}->find_message_account_by_nick($recall_nick);
|
||||||
|
|
||||||
|
if(not defined $account) {
|
||||||
|
return "I don't know anybody named $recall_nick.";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $message;
|
||||||
|
|
||||||
|
if($recall_history =~ /^\d+$/) {
|
||||||
|
# integral history
|
||||||
|
my $max_messages = $self->{database}->get_max_messages($account, $channel);
|
||||||
|
if($recall_history < 1 || $recall_history > $max_messages) {
|
||||||
|
return "Please choose a history between 1 and $max_messages";
|
||||||
|
}
|
||||||
|
|
||||||
|
$recall_history--;
|
||||||
|
|
||||||
|
$message = $self->{database}->recall_message_by_count($account, $channel, $recall_history, 'recall');
|
||||||
|
} else {
|
||||||
|
# regex history
|
||||||
|
$message = $self->{database}->recall_message_by_text($account, $channel, $recall_history, 'recall');
|
||||||
|
|
||||||
|
if(not defined $message) {
|
||||||
|
return "No such message for nick $found_nick in channel $channel containing text '$recall_history'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log("$nick ($from) recalled <$recall_nick/$channel> $message->{msg}\n");
|
||||||
|
|
||||||
|
my $text = $message->{msg};
|
||||||
|
my $ago = ago(gettimeofday - $message->{timestamp});
|
||||||
|
|
||||||
|
if(not defined $recall_text) {
|
||||||
|
if($text =~ s/^\/me\s+//) {
|
||||||
|
$recall_text = "[$ago] * $found_nick $text";
|
||||||
|
} else {
|
||||||
|
$recall_text = "[$ago] <$found_nick> $text";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if($text =~ s/^\/me\s+//) {
|
||||||
|
$recall_text .= " [$ago] * $found_nick $text";
|
||||||
|
} else {
|
||||||
|
$recall_text .= " [$ago] <$found_nick> $text";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $recall_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
645
PBot/MessageHistory_SQLite.pm
Normal file
645
PBot/MessageHistory_SQLite.pm
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
# File: MessageHistory_SQLite.pm
|
||||||
|
# Author: pragma_
|
||||||
|
#
|
||||||
|
# Purpose: SQLite backend for storing/retreiving a user's message history
|
||||||
|
|
||||||
|
package PBot::MessageHistory_SQLite;
|
||||||
|
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
use DBI;
|
||||||
|
use Carp qw(shortmess);
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
if(ref($_[1]) eq 'HASH') {
|
||||||
|
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 in " . __FILE__);
|
||||||
|
$self->{filename} = delete $conf{filename} // $self->{pbot}->{data_dir} . '/message_history.sqlite3';
|
||||||
|
|
||||||
|
$self->{pbot}->timer->register(sub { $self->commit_message_history }, 5);
|
||||||
|
$self->{new_entries} = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub begin {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log("Opening message history SQLite database: $self->{filename}\n");
|
||||||
|
|
||||||
|
$self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{filename}", "", "", { RaiseError => 1, PrintError => 0 }) or die $DBI::errstr;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
#$self->{dbh}->trace($self->{dbh}->parse_trace_flags('SQL|1|test'));
|
||||||
|
|
||||||
|
$self->{dbh}->do(<<SQL);
|
||||||
|
CREATE TABLE IF NOT EXISTS Hostmasks (
|
||||||
|
hostmask TEXT PRIMARY KEY UNIQUE,
|
||||||
|
id INTEGER,
|
||||||
|
last_seen NUMERIC
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
$self->{dbh}->do(<<SQL);
|
||||||
|
CREATE TABLE IF NOT EXISTS Accounts (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
hostmask TEXT UNIQUE,
|
||||||
|
nickserv TEXT
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
$self->{dbh}->do(<<SQL);
|
||||||
|
CREATE TABLE IF NOT EXISTS Nickserv (
|
||||||
|
id INTEGER,
|
||||||
|
nickserv TEXT,
|
||||||
|
timestamp NUMERIC
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
$self->{dbh}->do(<<SQL);
|
||||||
|
CREATE TABLE IF NOT EXISTS Channels (
|
||||||
|
id INTEGER,
|
||||||
|
channel TEXT,
|
||||||
|
enter_abuse INTEGER,
|
||||||
|
enter_abuses INTEGER,
|
||||||
|
offenses INTEGER,
|
||||||
|
last_offense NUMERIC,
|
||||||
|
last_seen NUMERIC,
|
||||||
|
validated INTEGER,
|
||||||
|
join_watch INTEGER
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
$self->{dbh}->do(<<SQL);
|
||||||
|
CREATE TABLE IF NOT EXISTS Messages (
|
||||||
|
id INTEGER,
|
||||||
|
channel TEXT,
|
||||||
|
msg TEXT,
|
||||||
|
timestamp NUMERIC,
|
||||||
|
mode INTEGER
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
$self->{dbh}->begin_work();
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub end {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log("Closing message history SQLite database\n");
|
||||||
|
|
||||||
|
if(exists $self->{dbh} and defined $self->{dbh}) {
|
||||||
|
$self->{dbh}->commit() if $self->{new_entries};
|
||||||
|
$self->{dbh}->disconnect();
|
||||||
|
delete $self->{dbh};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_nickserv_accounts {
|
||||||
|
my ($self, $id) = @_;
|
||||||
|
|
||||||
|
my $nickserv_accounts = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT nickserv FROM Nickserv WHERE ID = ?');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref();
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return map {$_->[0]} @$nickserv_accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_current_nickserv_account {
|
||||||
|
my ($self, $id, $nickserv) = @_;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('UPDATE Accounts SET nickserv = ? WHERE id = ?');
|
||||||
|
$sth->bind_param(1, $nickserv);
|
||||||
|
$sth->bind_param(2, $id);
|
||||||
|
$sth->execute();
|
||||||
|
$self->{new_entries}++;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_current_nickserv_account {
|
||||||
|
my ($self, $id) = @_;
|
||||||
|
|
||||||
|
my $nickserv = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT nickserv FROM Accounts WHERE id = ?');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchrow_hashref()->{'nickserv'};
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return $nickserv;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub create_nickserv {
|
||||||
|
my ($self, $id, $nickserv) = @_;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('INSERT INTO Nickserv SELECT ?, ?, 0 WHERE NOT EXISTS (SELECT 1 FROM Nickserv WHERE id = ? AND nickserv = ?)');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $nickserv);
|
||||||
|
$sth->bind_param(3, $id);
|
||||||
|
$sth->bind_param(4, $nickserv);
|
||||||
|
my $rv = $sth->execute();
|
||||||
|
$self->{new_entries}++ if $sth->rows;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub update_nickserv_account {
|
||||||
|
my ($self, $id, $nickserv, $timestamp) = @_;
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log("Updating nickserv account for id $id to $nickserv with timestamp [$timestamp]\n");
|
||||||
|
|
||||||
|
$self->create_nickserv($id, $nickserv);
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('UPDATE Nickserv SET timestamp = ? WHERE id = ? AND nickserv = ?');
|
||||||
|
$sth->bind_param(1, $timestamp);
|
||||||
|
$sth->bind_param(2, $id);
|
||||||
|
$sth->bind_param(3, $nickserv);
|
||||||
|
$sth->execute();
|
||||||
|
$self->{new_entries}++;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_message_account {
|
||||||
|
my ($self, $mask, $link_id) = @_;
|
||||||
|
my $id;
|
||||||
|
|
||||||
|
if(defined $link_id) {
|
||||||
|
$id = $link_id;
|
||||||
|
} else {
|
||||||
|
$id = $self->get_new_account_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('INSERT INTO Hostmasks VALUES (?, ?, 0)');
|
||||||
|
$sth->bind_param(1, $mask);
|
||||||
|
$sth->bind_param(2, $id);
|
||||||
|
$sth->execute();
|
||||||
|
$self->{new_entries}++;
|
||||||
|
|
||||||
|
if(not defined $link_id) {
|
||||||
|
$sth = $self->{dbh}->prepare('INSERT INTO Accounts VALUES (?, ?, ?)');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $mask);
|
||||||
|
$sth->bind_param(3, "");
|
||||||
|
$sth->execute();
|
||||||
|
$self->{new_entries}++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub find_message_account_by_nick {
|
||||||
|
my ($self, $nick) = @_;
|
||||||
|
|
||||||
|
my ($id, $hostmask) = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT id,hostmask FROM Hostmasks WHERE hostmask LIKE ? LIMIT 1');
|
||||||
|
$sth->bind_param(1, "$nick!%");
|
||||||
|
$sth->execute();
|
||||||
|
my $row = $sth->fetchrow_hashref();
|
||||||
|
return ($row->{id}, $row->{hostmask});
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
$hostmask =~ s/!.*$// if defined $hostmask;
|
||||||
|
return ($id, $hostmask);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub find_message_accounts_by_nickserv {
|
||||||
|
my ($self, $nickserv) = @_;
|
||||||
|
|
||||||
|
my $accounts = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT id FROM Nickserv WHERE nickserv = ?');
|
||||||
|
$sth->bind_param(1, $nickserv);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref();
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return map {$_->[0]} @$accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub find_message_accounts_by_mask {
|
||||||
|
my ($self, $mask) = @_;
|
||||||
|
|
||||||
|
$mask =~ s/\*/%/g;
|
||||||
|
$mask =~ s/\?/_/g;
|
||||||
|
$mask =~ s/\$.*$//;
|
||||||
|
|
||||||
|
my $accounts = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT id FROM Hostmasks WHERE hostmask LIKE ?');
|
||||||
|
$sth->bind_param(1, $mask);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref();
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return map {$_->[0]} @$accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_message_account {
|
||||||
|
my ($self, $nick, $user, $host) = @_;
|
||||||
|
|
||||||
|
my $mask = "$nick!$user\@$host";
|
||||||
|
my $id = $self->get_message_account_id($mask);
|
||||||
|
return $id if defined $id;
|
||||||
|
|
||||||
|
my $rows = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT id,hostmask FROM Hostmasks WHERE hostmask LIKE ?');
|
||||||
|
$sth->bind_param(1, "$nick!%");
|
||||||
|
$sth->execute();
|
||||||
|
my $rows = $sth->fetchall_arrayref({});
|
||||||
|
|
||||||
|
foreach my $row (@$rows) {
|
||||||
|
$self->{pbot}->logger->log("Found matching nick $row->{hostmask} with id $row->{id}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(not defined $rows->[0]) {
|
||||||
|
$sth->bind_param(1, "%!$user\@$host");
|
||||||
|
$sth->execute();
|
||||||
|
$rows = $sth->fetchall_arrayref({});
|
||||||
|
|
||||||
|
foreach my $row (@$rows) {
|
||||||
|
$self->{pbot}->logger->log("Found matching user\@host mask $row->{hostmask} with id $row->{id}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $rows;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
|
||||||
|
if(defined $rows->[0]) {
|
||||||
|
$self->{pbot}->logger->log("message-history: [get-account] $nick!$user\@$host linked to $rows->[0]->{hostmask} with id $rows->[0]->{id}\n");
|
||||||
|
$self->add_message_account("$nick!$user\@$host", $rows->[0]->{id});
|
||||||
|
$self->devalidate_all_channels($rows->[0]->{id});
|
||||||
|
my @nickserv_accounts = $self->get_nickserv_accounts($rows->[0]->{id});
|
||||||
|
foreach my $nickserv_account (@nickserv_accounts) {
|
||||||
|
$self->{pbot}->logger->log("$nick!$user\@$host [$rows->[0]->{id}] seen with nickserv account [$nickserv_account]\n");
|
||||||
|
$self->{pbot}->antiflood->check_nickserv_accounts($nick, $nickserv_account, "$nick!$user\@$host");
|
||||||
|
}
|
||||||
|
return $rows->[0]->{id};
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log("No account found for mask [$mask], adding new account\n");
|
||||||
|
return $self->add_message_account($mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub find_most_recent_hostmask {
|
||||||
|
my ($self, $id) = @_;
|
||||||
|
|
||||||
|
my $hostmask = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT hostmask FROM Hostmasks WHERE ID = ? ORDER BY last_seen DESC LIMIT 1');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchrow_hashref()->{'hostmask'};
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return $hostmask;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub update_hostmask_data {
|
||||||
|
my ($self, $mask, $data) = @_;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sql = 'UPDATE Hostmasks SET ';
|
||||||
|
|
||||||
|
my $comma = '';
|
||||||
|
foreach my $key (keys %$data) {
|
||||||
|
$sql .= "$comma$key = ?";
|
||||||
|
$comma = ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= ' WHERE hostmask LIKE ?';
|
||||||
|
|
||||||
|
my $sth = $self->{dbh}->prepare($sql);
|
||||||
|
|
||||||
|
my $param = 1;
|
||||||
|
foreach my $key (keys %$data) {
|
||||||
|
$sth->bind_param($param++, $data->{$key});
|
||||||
|
}
|
||||||
|
|
||||||
|
$sth->bind_param($param, $mask);
|
||||||
|
$sth->execute();
|
||||||
|
$self->{new_entries}++;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_hostmasks_for_channel {
|
||||||
|
my ($self, $channel) = @_;
|
||||||
|
|
||||||
|
my $hostmasks = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT hostmasks.id, hostmask FROM Hostmasks, Channels WHERE channels.id = hostmasks.id AND channel = ?');
|
||||||
|
$sth->bind_param(1, $channel);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref({});
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return $hostmasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_message {
|
||||||
|
my ($self, $id, $mask, $channel, $message) = @_;
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log("Adding message [$id][$mask][$channel][$message->{msg}][$message->{timestamp}][$message->{mode}]\n");
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('INSERT INTO Messages VALUES (?, ?, ?, ?, ?)');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $channel);
|
||||||
|
$sth->bind_param(3, $message->{msg});
|
||||||
|
$sth->bind_param(4, $message->{timestamp});
|
||||||
|
$sth->bind_param(5, $message->{mode});
|
||||||
|
$sth->execute();
|
||||||
|
$self->{new_entries}++;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
$self->update_channel_data($id, $channel, { last_seen => $message->{timestamp} });
|
||||||
|
$self->update_hostmask_data($mask, { last_seen => $message->{timestamp} });
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_recent_messages {
|
||||||
|
my ($self, $id, $channel, $limit, $mode) = @_;
|
||||||
|
$limit = 25 if not defined $limit;
|
||||||
|
|
||||||
|
my $mode_query = '';
|
||||||
|
$mode_query = "AND mode = $mode" if defined $mode;
|
||||||
|
|
||||||
|
my $messages = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare(<<SQL);
|
||||||
|
SELECT msg, mode, timestamp
|
||||||
|
FROM Messages
|
||||||
|
WHERE id = ? AND channel = ? $mode_query
|
||||||
|
ORDER BY timestamp ASC
|
||||||
|
LIMIT ? OFFSET (SELECT COUNT(*) FROM Messages WHERE id = ? AND channel = ? $mode_query) - ?
|
||||||
|
SQL
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $channel);
|
||||||
|
$sth->bind_param(3, $limit);
|
||||||
|
$sth->bind_param(4, $id);
|
||||||
|
$sth->bind_param(5, $channel);
|
||||||
|
$sth->bind_param(6, $limit);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref({});
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub recall_message_by_count {
|
||||||
|
my ($self, $id, $channel, $count, $ignore_command) = @_;
|
||||||
|
|
||||||
|
my $messages = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT msg, mode, timestamp FROM Messages WHERE id = ? AND channel = ? ORDER BY timestamp DESC LIMIT 10 OFFSET ?');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $channel);
|
||||||
|
$sth->bind_param(3, $count);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref({});
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
|
||||||
|
if(defined $ignore_command) {
|
||||||
|
foreach my $message (@$messages) {
|
||||||
|
next if $message->{msg} =~ m/^$self->{pbot}->{botnick}. $ignore_command/ or $message->{msg} =~ m/^$self->{pbot}->{trigger}$ignore_command/;
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
return $messages->[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub recall_message_by_text {
|
||||||
|
my ($self, $id, $channel, $text, $ignore_command) = @_;
|
||||||
|
|
||||||
|
my $messages = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT msg,mode,timestamp FROM Messages WHERE id = ? AND channel = ? AND msg LIKE ? ORDER BY timestamp DESC LIMIT 10');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $channel);
|
||||||
|
$sth->bind_param(3, "%$text%");
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref({});
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
|
||||||
|
if(defined $ignore_command) {
|
||||||
|
foreach my $message (@$messages) {
|
||||||
|
next if $message->{msg} =~ m/^$self->{pbot}->{botnick}. $ignore_command/ or $message->{msg} =~ m/^$self->{pbot}->{trigger}$ignore_command/;
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
return $messages->[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_max_messages {
|
||||||
|
my ($self, $id, $channel) = @_;
|
||||||
|
|
||||||
|
my $count = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT COUNT(*) FROM Messages WHERE id = ? AND channel = ?');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $channel);
|
||||||
|
$sth->execute();
|
||||||
|
my $row = $sth->fetchrow_hashref();
|
||||||
|
$sth->finish();
|
||||||
|
return $row->{'COUNT(*)'};
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
$count = 0 if not defined $count;
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub create_channel {
|
||||||
|
my ($self, $id, $channel) = @_;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('INSERT INTO Channels SELECT ?, ?, 0, 0, 0, 0, 0, 0, 0 WHERE NOT EXISTS (SELECT 1 FROM Channels WHERE id = ? AND channel = ?)');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $channel);
|
||||||
|
$sth->bind_param(3, $id);
|
||||||
|
$sth->bind_param(4, $channel);
|
||||||
|
my $rv = $sth->execute();
|
||||||
|
$self->{new_entries}++ if $sth->rows;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_channels {
|
||||||
|
my ($self, $id) = @_;
|
||||||
|
|
||||||
|
my $channels = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT channel FROM Channels WHERE id = ?');
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref();
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return map {$_->[0]} @$channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_channel_data {
|
||||||
|
my ($self, $id, $channel, @columns) = @_;
|
||||||
|
|
||||||
|
$self->create_channel($id, $channel);
|
||||||
|
|
||||||
|
my $channel_data = eval {
|
||||||
|
my $sql = 'SELECT ';
|
||||||
|
|
||||||
|
if(not @columns) {
|
||||||
|
$sql .= '*';
|
||||||
|
} else {
|
||||||
|
my $comma = '';
|
||||||
|
foreach my $column (@columns) {
|
||||||
|
$sql .= "$comma$column";
|
||||||
|
$comma = ', ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= ' FROM Channels WHERE id = ? AND channel = ?';
|
||||||
|
my $sth = $self->{dbh}->prepare($sql);
|
||||||
|
$sth->bind_param(1, $id);
|
||||||
|
$sth->bind_param(2, $channel);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchrow_hashref();
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return $channel_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub update_channel_data {
|
||||||
|
my ($self, $id, $channel, $data) = @_;
|
||||||
|
|
||||||
|
$self->create_channel($id, $channel);
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sql = 'UPDATE Channels SET ';
|
||||||
|
|
||||||
|
my $comma = '';
|
||||||
|
foreach my $key (keys %$data) {
|
||||||
|
$sql .= "$comma$key = ?";
|
||||||
|
$comma = ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= ' WHERE id = ? AND channel = ?';
|
||||||
|
|
||||||
|
my $sth = $self->{dbh}->prepare($sql);
|
||||||
|
|
||||||
|
my $param = 1;
|
||||||
|
foreach my $key (keys %$data) {
|
||||||
|
$sth->bind_param($param++, $data->{$key});
|
||||||
|
}
|
||||||
|
|
||||||
|
$sth->bind_param($param++, $id);
|
||||||
|
$sth->bind_param($param, $channel);
|
||||||
|
$sth->execute();
|
||||||
|
$self->{new_entries}++;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_channel_datas_where_last_offense_older_than {
|
||||||
|
my ($self, $timestamp) = @_;
|
||||||
|
|
||||||
|
my $channel_datas = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT id, channel, offenses, last_offense FROM Channels WHERE last_offense > 0 AND last_offense <= ?');
|
||||||
|
$sth->bind_param(1, $timestamp);
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref({});
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return $channel_datas;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_channel_datas_with_enter_abuses {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $channel_datas = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT id, channel, enter_abuses FROM Channels WHERE enter_abuses > 0');
|
||||||
|
$sth->execute();
|
||||||
|
return $sth->fetchall_arrayref({});
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return $channel_datas;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub devalidate_all_channels {
|
||||||
|
my ($self, $id) = @_;
|
||||||
|
|
||||||
|
my $where = '';
|
||||||
|
$where = 'WHERE id = ?' if defined $id;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
my $sth = $self->{dbh}->prepare("UPDATE Channels SET validated = 0 $where");
|
||||||
|
$sth->bind_param(1, $id) if defined $id;
|
||||||
|
$sth->execute();
|
||||||
|
$self->{new_entries}++;
|
||||||
|
};
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
}
|
||||||
|
|
||||||
|
# End of public API, the remaining are internal support routines for this module
|
||||||
|
|
||||||
|
sub get_new_account_id {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $id = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT id FROM Accounts ORDER BY id DESC LIMIT 1');
|
||||||
|
$sth->execute();
|
||||||
|
my $row = $sth->fetchrow_hashref();
|
||||||
|
return $row->{id};
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
return ++$id;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_message_account_id {
|
||||||
|
my ($self, $mask) = @_;
|
||||||
|
|
||||||
|
my $id = eval {
|
||||||
|
my $sth = $self->{dbh}->prepare('SELECT id FROM Hostmasks WHERE hostmask == ?');
|
||||||
|
$sth->bind_param(1, $mask);
|
||||||
|
$sth->execute();
|
||||||
|
my $row = $sth->fetchrow_hashref();
|
||||||
|
return $row->{id};
|
||||||
|
};
|
||||||
|
|
||||||
|
$self->{pbot}->logger->log($@) if $@;
|
||||||
|
$self->{pbot}->logger->log("get_message_account_id: returning id [". (defined $id ? $id: 'undef') . "] for mask [$mask]\n");
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub commit_message_history {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
if($self->{new_entries} > 0) {
|
||||||
|
$self->{pbot}->logger->log("Commiting $self->{new_entries} messages to SQLite\n");
|
||||||
|
$self->{new_entries} = 0;
|
||||||
|
$self->{dbh}->commit();
|
||||||
|
$self->{dbh}->begin_work();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
@ -31,6 +31,7 @@ use PBot::Channels;
|
|||||||
use PBot::BanTracker;
|
use PBot::BanTracker;
|
||||||
|
|
||||||
use PBot::LagChecker;
|
use PBot::LagChecker;
|
||||||
|
use PBot::MessageHistory;
|
||||||
use PBot::AntiFlood;
|
use PBot::AntiFlood;
|
||||||
|
|
||||||
use PBot::Interpreter;
|
use PBot::Interpreter;
|
||||||
@ -87,10 +88,10 @@ sub initialize {
|
|||||||
$self->{max_msg_len} = delete $conf{max_msg_len} // 425;
|
$self->{max_msg_len} = delete $conf{max_msg_len} // 425;
|
||||||
$self->{MAX_FLOOD_MESSAGES} = delete $conf{MAX_FLOOD_MESSAGES} // 4;
|
$self->{MAX_FLOOD_MESSAGES} = delete $conf{MAX_FLOOD_MESSAGES} // 4;
|
||||||
$self->{MAX_NICK_MESSAGES} = delete $conf{MAX_NICK_MESSAGES} // 32;
|
$self->{MAX_NICK_MESSAGES} = delete $conf{MAX_NICK_MESSAGES} // 32;
|
||||||
$self->{message_history_file} = delete $conf{message_history_file} // "$ENV{HOME}/pbot/data/message_history";
|
|
||||||
|
|
||||||
$self->{trigger} = delete $conf{trigger} // '!';
|
$self->{trigger} = delete $conf{trigger} // '!';
|
||||||
|
|
||||||
|
my $messagehistory_file = delete $conf{message_history_file};
|
||||||
my $channels_file = delete $conf{channels_file};
|
my $channels_file = delete $conf{channels_file};
|
||||||
my $admins_file = delete $conf{admins_file};
|
my $admins_file = delete $conf{admins_file};
|
||||||
my $ignorelist_file = delete $conf{ignorelist_file};
|
my $ignorelist_file = delete $conf{ignorelist_file};
|
||||||
@ -128,6 +129,7 @@ sub initialize {
|
|||||||
$self->{bantracker} = PBot::BanTracker->new(pbot => $self);
|
$self->{bantracker} = PBot::BanTracker->new(pbot => $self);
|
||||||
|
|
||||||
$self->{lagchecker} = PBot::LagChecker->new(pbot => $self);
|
$self->{lagchecker} = PBot::LagChecker->new(pbot => $self);
|
||||||
|
$self->{messagehistory} = PBot::MessageHistory->new(pbot => $self, filename => $messagehistory_file);
|
||||||
$self->{antiflood} = PBot::AntiFlood->new(pbot => $self);
|
$self->{antiflood} = PBot::AntiFlood->new(pbot => $self);
|
||||||
|
|
||||||
$self->{ignorelist} = PBot::IgnoreList->new(pbot => $self, filename => $ignorelist_file);
|
$self->{ignorelist} = PBot::IgnoreList->new(pbot => $self, filename => $ignorelist_file);
|
||||||
|
@ -41,9 +41,9 @@ sub initialize {
|
|||||||
$self->{export_path} = delete $conf{export_path};
|
$self->{export_path} = delete $conf{export_path};
|
||||||
$self->{export_site} = delete $conf{export_site};
|
$self->{export_site} = delete $conf{export_site};
|
||||||
|
|
||||||
$self->{quotegrabs_db} = PBot::Quotegrabs_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
|
$self->{database} = PBot::Quotegrabs_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
|
||||||
#$self->{quotegrabs_db} = PBot::Quotegrabs_Hashtable->new(pbot => $self->{pbot}, filename => $self->{filename});
|
#$self->{database} = PBot::Quotegrabs_Hashtable->new(pbot => $self->{pbot}, filename => $self->{filename});
|
||||||
$self->{quotegrabs_db}->begin();
|
$self->{database}->begin();
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------------
|
||||||
# The following could be in QuotegrabsCommands.pm, or they could be kept in here?
|
# The following could be in QuotegrabsCommands.pm, or they could be kept in here?
|
||||||
@ -52,9 +52,6 @@ sub initialize {
|
|||||||
$self->{pbot}->commands->register(sub { $self->show_quotegrab(@_) }, "getq", 0);
|
$self->{pbot}->commands->register(sub { $self->show_quotegrab(@_) }, "getq", 0);
|
||||||
$self->{pbot}->commands->register(sub { $self->delete_quotegrab(@_) }, "delq", 0);
|
$self->{pbot}->commands->register(sub { $self->delete_quotegrab(@_) }, "delq", 0);
|
||||||
$self->{pbot}->commands->register(sub { $self->show_random_quotegrab(@_) }, "rq", 0);
|
$self->{pbot}->commands->register(sub { $self->show_random_quotegrab(@_) }, "rq", 0);
|
||||||
|
|
||||||
# ought to be in MessageTracker.pm once we create that module
|
|
||||||
$self->{pbot}->commands->register(sub { $self->recall_message(@_) }, "recall", 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub uniq { my %seen; grep !$seen{$_}++, @_ }
|
sub uniq { my %seen; grep !$seen{$_}++, @_ }
|
||||||
@ -63,7 +60,7 @@ sub export_quotegrabs {
|
|||||||
my $self = shift;
|
my $self = shift;
|
||||||
return "Not enabled" if not defined $self->{export_path};
|
return "Not enabled" if not defined $self->{export_path};
|
||||||
|
|
||||||
my $quotegrabs = $self->{quotegrabs_db}->get_all_quotegrabs();
|
my $quotegrabs = $self->{database}->get_all_quotegrabs();
|
||||||
|
|
||||||
my $text;
|
my $text;
|
||||||
my $table_id = 1;
|
my $table_id = 1;
|
||||||
@ -177,80 +174,43 @@ sub grab_quotegrab {
|
|||||||
foreach my $grab (@grabs) {
|
foreach my $grab (@grabs) {
|
||||||
($grab_nick, $grab_history, $channel) = split(/\s+/, $grab, 3);
|
($grab_nick, $grab_history, $channel) = split(/\s+/, $grab, 3);
|
||||||
|
|
||||||
if(not defined $grab_history) {
|
$grab_history = $nick eq $grab_nick ? 2 : 1 if not defined $grab_history; # skip grab command if grabbing self without arguments
|
||||||
$grab_history = $nick eq $grab_nick ? 2 : 1;
|
|
||||||
}
|
|
||||||
$channel = $from if not defined $channel;
|
$channel = $from if not defined $channel;
|
||||||
|
|
||||||
if($grab_history =~ /^\d+$/ and ($grab_history < 1 || $grab_history > $self->{pbot}->{MAX_NICK_MESSAGES})) {
|
|
||||||
return "/msg $nick Please choose a history between 1 and $self->{pbot}->{MAX_NICK_MESSAGES}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(not $channel =~ m/^#/) {
|
if(not $channel =~ m/^#/) {
|
||||||
return "'$channel' is not a valid channel; usage: grab <nick> [[history] channel] (you must specify a history parameter before the channel parameter)";
|
return "'$channel' is not a valid channel; usage: grab <nick> [[history] channel] (you must specify a history parameter before the channel parameter)";
|
||||||
}
|
}
|
||||||
|
|
||||||
my $found_mask = undef;
|
my ($account, $found_nick) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($grab_nick);
|
||||||
my $last_spoken = 0;
|
|
||||||
foreach my $mask (keys %{ $self->{pbot}->antiflood->message_history }) {
|
if(not defined $account) {
|
||||||
if($mask =~ m/^\Q$grab_nick\E!/i) {
|
return "I don't know anybody named $grab_nick";
|
||||||
if(defined $self->{pbot}->antiflood->message_history->{$mask}->{channels}->{$channel}{last_spoken}
|
|
||||||
and $self->{pbot}->antiflood->message_history->{$mask}->{channels}->{$channel}{last_spoken} > $last_spoken) {
|
|
||||||
$last_spoken = $self->{pbot}->antiflood->message_history->{$mask}->{channels}->{$channel}{last_spoken};
|
|
||||||
$found_mask = $mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(not defined $found_mask) {
|
$grab_nick = $found_nick; # convert nick to proper casing
|
||||||
return "No message history for $grab_nick in channel $channel. Usage: grab <nick> [history [channel]]; to specify channel, you must also specify history";
|
|
||||||
}
|
|
||||||
|
|
||||||
($grab_nick) = $found_mask =~ m/^([^!]+)!/; # convert $grab_nick to match casing of nick
|
my $message;
|
||||||
|
|
||||||
if(not exists $self->{pbot}->antiflood->message_history->{$found_mask}->{channels}->{$channel}) {
|
|
||||||
return "No message history for $grab_nick in channel $channel. Usage: grab <nick> [history [channel]]; to specify channel, you must also specify history";
|
|
||||||
}
|
|
||||||
|
|
||||||
my @messages = @{ $self->{pbot}->antiflood->message_history->{$found_mask}->{channels}->{$channel}{messages} };
|
|
||||||
|
|
||||||
if($grab_history =~ /^\d+$/) {
|
if($grab_history =~ /^\d+$/) {
|
||||||
# integral history
|
# integral history
|
||||||
|
my $max_messages = $self->{pbot}->{messagehistory}->{database}->get_max_messages($account, $channel);
|
||||||
|
if($grab_history < 1 || $grab_history > $max_messages) {
|
||||||
|
return "Please choose a history between 1 and $max_messages";
|
||||||
|
}
|
||||||
|
|
||||||
$grab_history--;
|
$grab_history--;
|
||||||
|
|
||||||
if($grab_history > $#messages) {
|
$message = $self->{pbot}->{messagehistory}->{database}->recall_message_by_count($account, $channel, $grab_history, 'grab');
|
||||||
return "$grab_nick has only " . ($#messages + 1) . " messages in the history for channel $channel.";
|
|
||||||
}
|
|
||||||
|
|
||||||
$grab_history = $#messages - $grab_history;
|
|
||||||
} else {
|
} else {
|
||||||
# regex history
|
# regex history
|
||||||
my $ret = eval {
|
$message = $self->{pbot}->{messagehistory}->{database}->recall_message_by_text($account, $channel, $grab_history, 'grab');
|
||||||
my $i = $#messages;
|
|
||||||
$i-- if($nick =~ /^\Q$grab_nick\E$/i); # skip 'grab' command if grabbing own nick
|
|
||||||
my $found = 0;
|
|
||||||
while($i >= 0) {
|
|
||||||
if($messages[$i]->{msg} =~ m/$grab_history/i) {
|
|
||||||
$grab_history = $i;
|
|
||||||
$found = 1;
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
$i--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($found == 0) {
|
if(not defined $message) {
|
||||||
return "/msg $nick No message containing regex '$grab_history' found for $grab_nick in channel $channel.";
|
return "No such message for nick $grab_nick in channel $channel containing text '$grab_history'";
|
||||||
} else {
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return "/msg $nick Bad grab regex: $@" if $@;
|
|
||||||
if(defined $ret) {
|
|
||||||
return $ret;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->{pbot}->logger->log("$nick ($from) grabbed <$grab_nick/$channel> $messages[$grab_history]->{msg}\n");
|
$self->{pbot}->logger->log("$nick ($from) grabbed <$grab_nick/$channel> $message->{msg}\n");
|
||||||
|
|
||||||
if(not defined $grab_nicks) {
|
if(not defined $grab_nicks) {
|
||||||
$grab_nicks = $grab_nick;
|
$grab_nicks = $grab_nick;
|
||||||
@ -258,7 +218,7 @@ sub grab_quotegrab {
|
|||||||
$grab_nicks .= "+$grab_nick";
|
$grab_nicks .= "+$grab_nick";
|
||||||
}
|
}
|
||||||
|
|
||||||
my $text = $messages[$grab_history]->{msg};
|
my $text = $message->{msg};
|
||||||
|
|
||||||
if(not defined $grab_text) {
|
if(not defined $grab_text) {
|
||||||
$grab_text = $text;
|
$grab_text = $text;
|
||||||
@ -279,7 +239,7 @@ sub grab_quotegrab {
|
|||||||
$quotegrab->{text} = $grab_text;
|
$quotegrab->{text} = $grab_text;
|
||||||
$quotegrab->{id} = undef;
|
$quotegrab->{id} = undef;
|
||||||
|
|
||||||
$quotegrab->{id} = $self->{quotegrabs_db}->add_quotegrab($quotegrab);
|
$quotegrab->{id} = $self->{database}->add_quotegrab($quotegrab);
|
||||||
|
|
||||||
if(not defined $quotegrab->{id}) {
|
if(not defined $quotegrab->{id}) {
|
||||||
return "Failed to grab quote.";
|
return "Failed to grab quote.";
|
||||||
@ -300,7 +260,7 @@ sub grab_quotegrab {
|
|||||||
sub delete_quotegrab {
|
sub delete_quotegrab {
|
||||||
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
||||||
|
|
||||||
my $quotegrab = $self->{quotegrabs_db}->get_quotegrab($arguments);
|
my $quotegrab = $self->{database}->get_quotegrab($arguments);
|
||||||
|
|
||||||
if(not defined $quotegrab) {
|
if(not defined $quotegrab) {
|
||||||
return "/msg $nick No quotegrab matching id $arguments found.";
|
return "/msg $nick No quotegrab matching id $arguments found.";
|
||||||
@ -310,7 +270,7 @@ sub delete_quotegrab {
|
|||||||
return "You are not the grabber of this quote.";
|
return "You are not the grabber of this quote.";
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->{quotegrabs_db}->delete_quotegrab($arguments);
|
$self->{database}->delete_quotegrab($arguments);
|
||||||
$self->export_quotegrabs();
|
$self->export_quotegrabs();
|
||||||
|
|
||||||
my $text = $quotegrab->{text};
|
my $text = $quotegrab->{text};
|
||||||
@ -327,7 +287,7 @@ sub delete_quotegrab {
|
|||||||
sub show_quotegrab {
|
sub show_quotegrab {
|
||||||
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
||||||
|
|
||||||
my $quotegrab = $self->{quotegrabs_db}->get_quotegrab($arguments);
|
my $quotegrab = $self->{database}->get_quotegrab($arguments);
|
||||||
|
|
||||||
if(not defined $quotegrab) {
|
if(not defined $quotegrab) {
|
||||||
return "/msg $nick No quotegrab matching id $arguments found.";
|
return "/msg $nick No quotegrab matching id $arguments found.";
|
||||||
@ -387,7 +347,7 @@ sub show_random_quotegrab {
|
|||||||
|
|
||||||
$channel_search = undef if defined $channel_search and $channel_search !~ /^#/;
|
$channel_search = undef if defined $channel_search and $channel_search !~ /^#/;
|
||||||
|
|
||||||
my $quotegrab = $self->{quotegrabs_db}->get_random_quotegrab($nick_search, $channel_search, $text_search);
|
my $quotegrab = $self->{database}->get_random_quotegrab($nick_search, $channel_search, $text_search);
|
||||||
|
|
||||||
if(not defined $quotegrab) {
|
if(not defined $quotegrab) {
|
||||||
my $result = "No quotes grabbed ";
|
my $result = "No quotes grabbed ";
|
||||||
@ -417,117 +377,4 @@ sub show_random_quotegrab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# this ought to be in MessageTracker.pm once we create that module
|
|
||||||
sub recall_message {
|
|
||||||
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
|
||||||
|
|
||||||
if(not defined $from) {
|
|
||||||
$self->{pbot}->logger->log("Command missing ~from parameter!\n");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(not defined $arguments or not length $arguments) {
|
|
||||||
return "Usage: recall <nick> [history [channel]] -- where [history] is an optional argument that is either an integral number of recent messages or a regex (without whitespace) of the text within the message; e.g., to recall the 3rd most recent message for nick, use `recall nick 3` or to recall a message containing 'pizza', use `recall nick pizza`; and [channel] is an optional channel, so you can use it from /msg (you will need to also specify [history] in this case)";
|
|
||||||
}
|
|
||||||
|
|
||||||
$arguments = lc $arguments;
|
|
||||||
|
|
||||||
my @recalls = split /\s\+\s/, $arguments;
|
|
||||||
|
|
||||||
my ($recall_nick, $recall_history, $channel, $recall_nicks, $recall_text);
|
|
||||||
|
|
||||||
foreach my $recall (@recalls) {
|
|
||||||
($recall_nick, $recall_history, $channel) = split(/\s+/, $recall, 3);
|
|
||||||
|
|
||||||
if(not defined $recall_history) {
|
|
||||||
$recall_history = $nick eq $recall_nick ? 2 : 1;
|
|
||||||
}
|
|
||||||
$channel = $from if not defined $channel;
|
|
||||||
|
|
||||||
if($recall_history =~ /^\d+$/ and ($recall_history < 1 || $recall_history > $self->{pbot}->{MAX_NICK_MESSAGES})) {
|
|
||||||
return "/msg $nick Please choose a history between 1 and $self->{pbot}->{MAX_NICK_MESSAGES}";
|
|
||||||
}
|
|
||||||
|
|
||||||
my $found_mask = undef;
|
|
||||||
my $last_spoken = 0;
|
|
||||||
foreach my $mask (keys %{ $self->{pbot}->antiflood->message_history }) {
|
|
||||||
if($mask =~ m/^\Q$recall_nick\E!/i) {
|
|
||||||
if(defined $self->{pbot}->antiflood->message_history->{$mask}->{channels}->{$channel}{last_spoken}
|
|
||||||
and $self->{pbot}->antiflood->message_history->{$mask}->{channels}->{$channel}{last_spoken} > $last_spoken) {
|
|
||||||
$last_spoken = $self->{pbot}->antiflood->message_history->{$mask}->{channels}->{$channel}{last_spoken};
|
|
||||||
$found_mask = $mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(not defined $found_mask) {
|
|
||||||
return "No message history for $recall_nick in channel $channel. Usage: recall <nick> [history [channel]]; to specify channel, you must also specify history";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(not exists $self->{pbot}->antiflood->message_history->{$found_mask}->{channels}->{$channel}) {
|
|
||||||
return "No message history for $recall_nick in channel $channel. Usage: recall <nick> [history [channel]]; to specify channel, you must also specify history";
|
|
||||||
}
|
|
||||||
|
|
||||||
my @messages = @{ $self->{pbot}->antiflood->message_history->{$found_mask}->{channels}->{$channel}{messages} };
|
|
||||||
my ($found_nick) = $found_mask =~ m/^([^!]+)/;
|
|
||||||
|
|
||||||
if($recall_history =~ /^\d+$/) {
|
|
||||||
# integral history
|
|
||||||
$recall_history--;
|
|
||||||
|
|
||||||
if($recall_history > $#messages) {
|
|
||||||
return "$recall_nick has only " . ($#messages + 1) . " messages in the history for channel $channel.";
|
|
||||||
}
|
|
||||||
|
|
||||||
$recall_history = $#messages - $recall_history;
|
|
||||||
} else {
|
|
||||||
# regex history
|
|
||||||
my $ret = eval {
|
|
||||||
my $i = $#messages;
|
|
||||||
$i-- if($nick =~ /^\Q$recall_nick\E$/i); # skip 'recall' command if recallbing own nick
|
|
||||||
my $found = 0;
|
|
||||||
while($i >= 0) {
|
|
||||||
if($messages[$i]->{msg} =~ m/$recall_history/i) {
|
|
||||||
$recall_history = $i;
|
|
||||||
$found = 1;
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
$i--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($found == 0) {
|
|
||||||
return "/msg $nick No message containing regex '$recall_history' found for $recall_nick in channel $channel.";
|
|
||||||
} else {
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return "/msg $nick Bad recall regex: $@" if $@;
|
|
||||||
if(defined $ret) {
|
|
||||||
return $ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$self->{pbot}->logger->log("$nick ($from) recalled <$recall_nick/$channel> $messages[$recall_history]->{msg}\n");
|
|
||||||
|
|
||||||
my $text = $messages[$recall_history]->{msg};
|
|
||||||
my $ago = ago(gettimeofday - $messages[$recall_history]->{timestamp});
|
|
||||||
|
|
||||||
if(not defined $recall_text) {
|
|
||||||
if($text =~ s/^\/me\s+//) {
|
|
||||||
$recall_text = "[$ago] * $found_nick $text";
|
|
||||||
} else {
|
|
||||||
$recall_text = "[$ago] <$found_nick> $text";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if($text =~ s/^\/me\s+//) {
|
|
||||||
$recall_text .= " [$ago] * $found_nick $text";
|
|
||||||
} else {
|
|
||||||
$recall_text .= " [$ago] <$found_nick> $text";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $recall_text;
|
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -63,6 +63,7 @@ sub end {
|
|||||||
|
|
||||||
if(exists $self->{dbh} and defined $self->{dbh}) {
|
if(exists $self->{dbh} and defined $self->{dbh}) {
|
||||||
$self->{dbh}->disconnect();
|
$self->{dbh}->disconnect();
|
||||||
|
delete $self->{dbh};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ use warnings;
|
|||||||
# These are set automatically by the build/commit script
|
# These are set automatically by the build/commit script
|
||||||
use constant {
|
use constant {
|
||||||
BUILD_NAME => "PBot",
|
BUILD_NAME => "PBot",
|
||||||
BUILD_REVISION => 568,
|
BUILD_REVISION => 569,
|
||||||
BUILD_DATE => "2014-05-08",
|
BUILD_DATE => "2014-05-13",
|
||||||
};
|
};
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
2
pbot.pl
2
pbot.pl
@ -110,7 +110,7 @@ $config{factoids_file} = "$config{data_dir}/factoids";
|
|||||||
$config{quotegrabs_file} = "$config{data_dir}/quotegrabs.sqlite3";
|
$config{quotegrabs_file} = "$config{data_dir}/quotegrabs.sqlite3";
|
||||||
|
|
||||||
# Location of file containing message history
|
# Location of file containing message history
|
||||||
$config{message_history_file} = "$config{data_dir}/message_history";
|
$config{message_history_file} = "$config{data_dir}/message_history.sqlite3";
|
||||||
|
|
||||||
# Create and initialize bot object
|
# Create and initialize bot object
|
||||||
my $pbot = PBot::PBot->new(%config);
|
my $pbot = PBot::PBot->new(%config);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user