More robust coverage of aliases when managing message history accounts

When adding a new unknown hostmask to the message history accounts, we now
take significant advantage of the aliases table to find an existing account
id for the hostmask before assigning it a new account id.

Likewise, we now take significant advantage of the aliases table when looking
for a nick-change match.

Fix misc channel case-sensitivity issues, add missing last-seen hostmask
updates, reduce message account linking log verbosity level.
This commit is contained in:
Pragmatic Software 2016-11-29 01:50:49 -08:00
parent bbf45a3fab
commit 8ba4ffffe4
2 changed files with 229 additions and 79 deletions

View File

@ -330,8 +330,8 @@ sub check_flood {
my ($newnick) = $text =~ m/NICKCHANGE (.*)/;
$mask = "$newnick!$user\@$host";
$account = $self->{pbot}->{messagehistory}->get_message_account($newnick, $user, $host);
$self->{pbot}->{messagehistory}->{database}->update_hostmask_data($mask, { last_seen => scalar gettimeofday });
$nick = $newnick;
$self->{nickflood}->{$account}->{changes}++;
} else {
$self->{pbot}->{logger}->log(sprintf("%-18s | %-65s | %s\n", lc $channel eq lc $mask ? "QUIT" : $channel, $mask, $text));
}
@ -342,6 +342,18 @@ sub check_flood {
return;
}
$self->{pbot}->{logger}->log("Processing anti-flood account $account for mask $mask\n");
my $ancestor = $self->{pbot}->{messagehistory}->{database}->get_ancestor_id($account);
if ($ancestor != $account) {
$self->{pbot}->{logger}->log("Using ancestor id $ancestor\n");
$account = $ancestor;
}
if ($mode == $self->{pbot}->{messagehistory}->{MSG_NICKCHANGE}) {
$self->{nickflood}->{$account}->{changes}++;
$self->{pbot}->{logger}->log("account $account has $self->{nickflood}->{$account}->{changes} nickchanges\n");
}
# handle QUIT events
# (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)
@ -367,6 +379,7 @@ sub check_flood {
if($mode == $self->{pbot}->{messagehistory}->{MSG_NICKCHANGE}) {
$channels = $self->{pbot}->{nicklist}->get_channels($nick);
$self->{pbot}->{logger}->log("Nick changes for $account: $self->{nickflood}->{$account}->{changes}\n");
} else {
$self->update_join_watch($account, $channel, $text, $mode);
push @$channels, $channel;
@ -399,7 +412,7 @@ sub check_flood {
if($channel =~ m/^#/) {
my $validated = $self->{pbot}->{messagehistory}->{database}->get_channel_data($account, $channel, 'validated')->{'validated'};
if($validated & $self->{NEEDS_CHECKBAN} or not $validated & $self->{NICKSERV_VALIDATED}) {
if ($validated & $self->{NEEDS_CHECKBAN} or not $validated & $self->{NICKSERV_VALIDATED}) {
if($mode == $self->{pbot}->{messagehistory}->{MSG_DEPARTURE}) {
# don't check for evasion on PART/KICK
} elsif ($mode == $self->{pbot}->{messagehistory}->{MSG_NICKCHANGE}) {
@ -417,6 +430,9 @@ sub check_flood {
$self->{pbot}->{conn}->whois($nick);
$self->{whois_pending}->{$nick} = gettimeofday;
}
} else {
$self->{pbot}->{logger}->log("Not validated; checking bans...\n");
$self->check_bans($account, "$nick!$user\@$host", $channel);
}
}
}
@ -483,9 +499,13 @@ sub check_flood {
my $duration = duration($timeout);
my $banmask = address_to_mask($host);
$self->{pbot}->{chanops}->ban_user_timed("*!$user\@$banmask\$##stop_join_flood", $channel . '-floodbans', $timeout);
$self->{pbot}->{logger}->log("$nick!$user\@$banmask banned for $duration 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 $duration.");
if ($self->{pbot}->{channels}->is_active_op("${channel}-floodbans")) {
$self->{pbot}->{chanops}->ban_user_timed("*!$user\@$banmask\$##stop_join_flood", $channel . '-floodbans', $timeout);
$self->{pbot}->{logger}->log("$nick!$user\@$banmask banned for $duration 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 $duration.");
} else {
$self->{pbot}->{logger}->log("[anti-flood] I am not an op for ${channel}-floodbans, disregarding join-flood.\n");
}
}
$channel_data->{join_watch} = $max_messages - 2; # give them a chance to rejoin
$self->{pbot}->{messagehistory}->{database}->update_channel_data($account, $channel, $channel_data);
@ -682,8 +702,10 @@ sub unbanme {
$warning = ' You may not use `unbanme` again for several hours; ensure that your client or connection issues are resolved, otherwise leave the channel until they are or you will be temporarily banned for several hours if you join-flood.';
}
$self->{pbot}->{chanops}->unban_user($mask, $channel . '-floodbans');
$unbanned{$mask}++;
if ($self->{pbot}->{channels}->is_active_op("${channel}-floodbans")) {
$self->{pbot}->{chanops}->unban_user($mask, $channel . '-floodbans');
$unbanned{$mask}++;
}
}
if (keys %unbanned) {
@ -746,7 +768,12 @@ sub devalidate_accounts {
sub check_bans {
my ($self, $message_account, $mask, $channel, $dry_run) = @_;
return if not $self->{pbot}->{chanops}->can_gain_ops($channel);
$channel = lc $channel;
if (not $self->{pbot}->{chanops}->can_gain_ops($channel)) {
$self->{pbot}->{logger}->log("anti-flood: [check-ban] I do not have ops for $channel.\n");
return;
}
my $debug_checkban = $self->{pbot}->{registry}->get_value('antiflood', 'debug_checkban');
@ -754,6 +781,8 @@ sub check_bans {
$self->{pbot}->{logger}->log("anti-flood: [check-bans] checking for bans on $mask " . (defined $current_nickserv_account and length $current_nickserv_account ? "[$current_nickserv_account] " : "") . "in $channel\n");
my ($do_not_validate, $bans);
if (defined $current_nickserv_account and length $current_nickserv_account) {
$self->{pbot}->{logger}->log("anti-flood: [check-bans] current nickserv [$current_nickserv_account] found for $mask\n") if $debug_checkban >= 2;
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
@ -762,13 +791,17 @@ sub check_bans {
$self->{pbot}->{messagehistory}->{database}->update_channel_data($message_account, $channel, $channel_data);
}
} else {
# mark this account as needing check-bans when nickserv account is identified
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
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);
if (not exists $self->{pbot}->{capabilities}->{'account-notify'}) {
# mark this account as needing check-bans when nickserv account is identified
my $channel_data = $self->{pbot}->{messagehistory}->{database}->get_channel_data($message_account, $channel, 'validated');
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") if $debug_checkban >= 1;
} else {
$do_not_validate = 1;
}
$self->{pbot}->{logger}->log("anti-flood: [check-bans] no account for $mask; marking for later validation\n") if $debug_checkban >= 1;
}
my ($nick) = $mask =~ m/^([^!]+)/;
@ -776,7 +809,6 @@ sub check_bans {
my $csv = Text::CSV->new({binary => 1});
my ($do_not_validate, $bans);
foreach my $alias (keys %aliases) {
next if $alias =~ /^Guest\d+(?:!.*)?$/;
@ -884,7 +916,7 @@ sub check_bans {
my ($user, $host) = $mask =~ m/[^!]+!([^@]+)@(.*)/;
if ($host =~ m{^([^/]+)/.+} and $1 ne 'gateway' and $1 ne 'nat') {
$banmask = "*!*\@$host";
} elsif ($current_nickserv_account and $baninfo->{banmask} !~ m/^\$a:/i) {
} elsif ($current_nickserv_account and $baninfo->{banmask} !~ m/^\$a:/i and not exists $self->{pbot}->{bantracker}->{banlist}->{$baninfo->{channel}}->{'+b'}->{"\$a:$current_nickserv_account"}) {
$banmask = "\$a:$current_nickserv_account";
} else {
if ($host =~ m{^gateway/web/irccloud.com/}) {
@ -935,7 +967,7 @@ sub check_bans {
}
}
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};
@ -1035,6 +1067,8 @@ sub on_whoisaccount {
sub on_accountnotify {
my ($self, $event_type, $event) = @_;
$self->{pbot}->{messagehistory}->{database}->update_hostmask_data($event->{event}->{from}, { last_seen => scalar gettimeofday });
if ($event->{event}->{args}[0] eq '*') {
$self->{pbot}->{logger}->log("$event->{event}->{from} logged out of NickServ\n");
} else {

View File

@ -389,13 +389,26 @@ sub get_message_account {
my $id = $self->get_message_account_id($mask);
return $id if defined $id;
$self->{pbot}->{logger}->log("Getting new message account for $nick!$user\@$host...\n");
$self->{pbot}->{logger}->log("It's a nick-change!\n") if defined $orig_nick;
my $do_nothing = 0;
my ($rows, $link_type) = eval {
my $sth = $self->{dbh}->prepare('SELECT id, hostmask, last_seen FROM Hostmasks WHERE hostmask LIKE ? ESCAPE "\" ORDER BY last_seen DESC');
my ($account1) = $host =~ m{/([^/]+)$};
$account1 = '' if not defined $account1;
my $hostip = undef;
if ($host =~ m/(\d+[[:punct:]]\d+[[:punct:]]\d+[[:punct:]]\d+)\D/) {
$hostip = $1;
$hostip =~ s/[[:punct:]]/./g;
}
if (defined $orig_nick) {
my $orig_id = $self->get_message_account_id("$orig_nick!$user\@$host");
my @orig_nickserv_accounts = $self->get_nickserv_accounts($orig_id);
my $qnick = quotemeta $nick;
$qnick =~ s/_/\\_/g;
@ -408,13 +421,89 @@ sub get_message_account {
return ($rows, $self->{alias_type}->{STRONG});
}
my %processed_nicks;
my %processed_akas;
foreach my $row (@$rows) {
$self->{pbot}->{logger}->log("Found matching nickchange account: [$row->{id}] $row->{hostmask}\n");
my ($tnick) = $row->{hostmask} =~ m/^([^!]+)!/;
if ($row->{id} == $orig_id || $row->{hostmask} =~ m/^.*!\Q$nick\E\@\Q$host\E$/i) {
$self->{pbot}->{logger}->log("Using this match.\n");
$rows->[0] = { id => $orig_id, hostmask => $row->{hostmask} };
return ($rows, $self->{alias_type}->{STRONG});
next if exists $processed_nicks{lc $tnick};
$processed_nicks{lc $tnick} = 1;
my %akas = $self->get_also_known_as($tnick);
foreach my $aka (keys %akas) {
next if $akas{$aka}->{type} == $self->{alias_type}->{WEAK};
next if $akas{$aka}->{nickchange} == 1;
next if exists $processed_akas{$akas{$aka}->{id}};
$processed_akas{$akas{$aka}->{id}} = 1;
$self->{pbot}->{logger}->log("Testing alias [$akas{$aka}->{id}] $aka\n");
my $match = 0;
if ($akas{$aka}->{id} == $orig_id || $aka =~ m/^.*!\Q$user\E\@\Q$host\E$/i) {
$match = 1;
goto MATCH;
}
if (@orig_nickserv_accounts) {
my @nickserv_accounts = $self->get_nickserv_accounts($akas{$aka}->{id});
foreach my $ns1 (@orig_nickserv_accounts) {
foreach my $ns2 (@nickserv_accounts) {
if ($ns1 eq $ns2) {
$self->{pbot}->{logger}->log("Got matching nickserv: $ns1\n");
$match = 1;
goto MATCH;
}
}
}
}
my ($thost) = $aka =~ m/@(.*)$/;
if ($thost =~ m{(^unaffiliated|/staff/|/member/)}) {
my ($account2) = $thost =~ m{/([^/]+)$};
if ($account1 ne $account2) {
$self->{pbot}->{logger}->log("Skipping non-matching cloaked hosts: $host vs $thost\n");
next;
} else {
$self->{pbot}->{logger}->log("Cloaked hosts match: $host vs $thost\n");
$rows->[0] = { id => $self->get_ancestor_id($akas{$aka}->{id}), hostmask => $aka };
return ($rows, $self->{alias_type}->{STRONG});
}
}
my $distance = fastdistance($host, $thost);
my $length = (length($host) > length($thost)) ? length $host : length $thost;
$self->{pbot}->{logger}->log("distance: " . ($distance / $length) . " -- $host vs $thost\n") if $length != 0;
if ($length != 0 && $distance / $length < 0.50) {
$match = 1;
} else {
# handle cases like 99.57.140.149 vs 99-57-140-149.lightspeed.sntcca.sbcglobal.net
if (defined $hostip) {
if ($hostip eq $thost) {
$match = 1;
$self->{pbot}->{logger}->log("IP vs hostname match: $host vs $thost\n");
}
} elsif ($thost =~ m/(\d+[[:punct:]]\d+[[:punct:]]\d+[[:punct:]]\d+)\D/) {
my $thostip = $1;
$thostip =~ s/[[:punct:]]/./g;
if ($thostip eq $host) {
$match = 1;
$self->{pbot}->{logger}->log("IP vs hostname match: $host vs $thost\n");
}
}
}
MATCH:
if ($match) {
$self->{pbot}->{logger}->log("Using this match.\n");
$rows->[0] = { id => $self->get_ancestor_id($akas{$aka}->{id}), hostmask => $aka };
return ($rows, $self->{alias_type}->{STRONG});
}
}
}
@ -422,7 +511,7 @@ sub get_message_account {
my $new_id = $self->add_message_account($mask);
$self->link_alias($orig_id, $new_id, $self->{alias_type}->{WEAK});
$self->update_hostmask_data($mask, { nickchange => 1 });
$self->update_hostmask_data($mask, { nickchange => 1, last_seen => scalar gettimeofday });
$do_nothing = 1;
$rows->[0] = { id => $new_id };
@ -463,62 +552,72 @@ sub get_message_account {
$sth->execute();
my $rows = $sth->fetchall_arrayref({});
my ($account1) = $host =~ m{/([^/]+)$};
$account1 = '' if not defined $account1;
my $hostip = undef;
if ($host =~ m/(\d+[[:punct:]]\d+[[:punct:]]\d+[[:punct:]]\d+)\D/) {
$hostip = $1;
$hostip =~ s/[[:punct:]]/./g;
}
my %processed_nicks;
my %processed_akas;
foreach my $row (@$rows) {
$self->{pbot}->{logger}->log("Found matching nick $row->{hostmask} with id $row->{id}\n");
my ($thost) = $row->{hostmask} =~ m/@(.*)$/;
my ($tnick) = $row->{hostmask} =~ m/^([^!]+)!/;
if ($thost =~ m{(^unaffiliated|/staff/|/member/)}) {
my ($account2) = $thost =~ m{/([^/]+)$};
next if exists $processed_nicks{lc $tnick};
$processed_nicks{lc $tnick} = 1;
if ($account1 ne $account2) {
$self->{pbot}->{logger}->log("Skipping non-matching cloaked hosts: $host vs $thost\n");
next;
my %akas = $self->get_also_known_as($tnick);
foreach my $aka (keys %akas) {
next if $akas{$aka}->{type} == $self->{alias_type}->{WEAK};
next if $akas{$aka}->{nickchange} == 1;
next if exists $processed_akas{$akas{$aka}->{id}};
$processed_akas{$akas{$aka}->{id}} = 1;
$self->{pbot}->{logger}->log("Testing alias [$akas{$aka}->{id}] $aka\n");
my ($thost) = $aka =~ m/@(.*)$/;
if ($thost =~ m{(^unaffiliated|/staff/|/member/)}) {
my ($account2) = $thost =~ m{/([^/]+)$};
if ($account1 ne $account2) {
$self->{pbot}->{logger}->log("Skipping non-matching cloaked hosts: $host vs $thost\n");
next;
} else {
$self->{pbot}->{logger}->log("Cloaked hosts match: $host vs $thost\n");
$rows->[0] = { id => $self->get_ancestor_id($akas{$aka}->{id}), hostmask => $aka };
return ($rows, $self->{alias_type}->{STRONG});
}
}
my $distance = fastdistance($host, $thost);
my $length = (length($host) > length($thost)) ? length $host : length $thost;
$self->{pbot}->{logger}->log("distance: " . ($distance / $length) . " -- $host vs $thost\n") if $length != 0;
my $match = 0;
if ($length != 0 && $distance / $length < 0.50) {
$match = 1;
} else {
$self->{pbot}->{logger}->log("Cloaked hosts match: $host vs $thost\n");
$rows->[0] = $row;
# handle cases like 99.57.140.149 vs 99-57-140-149.lightspeed.sntcca.sbcglobal.net
if (defined $hostip) {
if ($hostip eq $thost) {
$match = 1;
$self->{pbot}->{logger}->log("IP vs hostname match: $host vs $thost\n");
}
} elsif ($thost =~ m/(\d+[[:punct:]]\d+[[:punct:]]\d+[[:punct:]]\d+)\D/) {
my $thostip = $1;
$thostip =~ s/[[:punct:]]/./g;
if ($thostip eq $host) {
$match = 1;
$self->{pbot}->{logger}->log("IP vs hostname match: $host vs $thost\n");
}
}
}
if ($match) {
$rows->[0] = { id => $self->get_ancestor_id($akas{$aka}->{id}), hostmask => $aka };
return ($rows, $self->{alias_type}->{STRONG});
}
}
my $distance = fastdistance($host, $thost);
my $length = (length($host) > length($thost)) ? length $host : length $thost;
$self->{pbot}->{logger}->log("distance: " . ($distance / $length) . " -- $host vs $thost\n") if $length != 0;
my $match = 0;
if ($length != 0 && $distance / $length < 0.50) {
$match = 1;
} else {
# handle cases like 99.57.140.149 vs 99-57-140-149.lightspeed.sntcca.sbcglobal.net
if (defined $hostip) {
if ($hostip eq $thost) {
$match = 1;
$self->{pbot}->{logger}->log("IP vs hostname match: $host vs $thost\n");
}
} elsif ($thost =~ m/(\d+[[:punct:]]\d+[[:punct:]]\d+[[:punct:]]\d+)\D/) {
my $thostip = $1;
$thostip =~ s/[[:punct:]]/./g;
if ($thostip eq $host) {
$match = 1;
$self->{pbot}->{logger}->log("IP vs hostname match: $host vs $thost\n");
}
}
}
if ($match) {
$rows->[0] = $row;
return ($rows, $self->{alias_type}->{STRONG});
}
}
if (not defined $rows->[0]) {
@ -1054,7 +1153,7 @@ sub link_aliases {
my $debug_link = $self->{pbot}->{registry}->get_value('messagehistory', 'debug_link');
$self->{pbot}->{logger}->log("Linking [$account][" . ($hostmask?$hostmask:'undef') . "][" . ($nickserv?$nickserv:'undef') . "]\n") if $debug_link;
$self->{pbot}->{logger}->log("Linking [$account][" . ($hostmask?$hostmask:'undef') . "][" . ($nickserv?$nickserv:'undef') . "]\n") if $debug_link >= 2;
eval {
my %ids;
@ -1071,10 +1170,10 @@ sub link_aliases {
foreach my $row (@$rows) {
if ($now - $row->{last_seen} <= 60 * 60 * 48) {
$ids{$row->{id}} = { id => $row->{id}, type => $self->{alias_type}->{STRONG}, force => 1 };
$self->{pbot}->{logger}->log("found STRONG matching id $row->{id} for host [$host]\n") if $debug_link && $row->{id} != $account;
$self->{pbot}->{logger}->log("found STRONG matching id $row->{id} for host [$host]\n") if $debug_link >= 2 && $row->{id} != $account;
} else {
$ids{$row->{id}} = { id => $row->{id}, type => $self->{alias_type}->{WEAK} };
$self->{pbot}->{logger}->log("found WEAK matching id $row->{id} for host [$host]\n") if $debug_link && $row->{id} != $account;
$self->{pbot}->{logger}->log("found WEAK matching id $row->{id} for host [$host]\n") if $debug_link >= 2 && $row->{id} != $account;
}
}
@ -1119,7 +1218,7 @@ sub link_aliases {
if ($length != 0 && $distance / $length < 0.50) {
$ids{$row->{id}} = { id => $row->{id}, type => $self->{alias_type}->{STRONG} }; # don't force linking
$self->{pbot}->{logger}->log("found STRONG matching id $row->{id} for nick [$qnick]\n") if $debug_link;
$self->{pbot}->{logger}->log("found STRONG matching id $row->{id} for nick [$qnick]\n") if $debug_link >= 2;
} else {
# handle cases like 99.57.140.149 vs 99-57-140-149.lightspeed.sntcca.sbcglobal.net
if (defined $hostip) {
@ -1148,7 +1247,7 @@ sub link_aliases {
foreach my $row (@$rows) {
$ids{$row->{id}} = { id => $row->{id}, type => $self->{alias_type}->{STRONG}, force => 1 };
$self->{pbot}->{logger}->log("found STRONG matching id $row->{id} for nickserv [$nickserv]\n") if $debug_link && $row->{id} != $account;
$self->{pbot}->{logger}->log("found STRONG matching id $row->{id} for nickserv [$nickserv]\n") if $debug_link >= 2 && $row->{id} != $account;
}
}
@ -1165,7 +1264,7 @@ sub link_alias {
my $debug_link = $self->{pbot}->{registry}->get_value('messagehistory', 'debug_link');
$self->{pbot}->{logger}->log("Attempting to " . ($force ? "forcefully " : "") . ($type == $self->{alias_type}->{STRONG} ? "strongly" : "weakly") . " link $id to $alias\n") if $debug_link;
$self->{pbot}->{logger}->log("Attempting to " . ($force ? "forcefully " : "") . ($type == $self->{alias_type}->{STRONG} ? "strongly" : "weakly") . " link $id to $alias\n") if $debug_link >= 2;
my $ret = eval {
my $sth = $self->{dbh}->prepare('SELECT type FROM Aliases WHERE id = ? AND alias = ? LIMIT 1');
@ -1178,7 +1277,7 @@ sub link_alias {
if (defined $row) {
if ($force) {
if ($row->{'type'} != $type) {
$self->{pbot}->{logger}->log("$id already " . ($row->{'type'} == $self->{alias_type}->{STRONG} ? "strongly" : "weakly") . " linked to $alias, forcing override\n") if $debug_link;
$self->{pbot}->{logger}->log("$id already " . ($row->{'type'} == $self->{alias_type}->{STRONG} ? "strongly" : "weakly") . " linked to $alias, forcing override\n") if $debug_link >= 2;
$sth = $self->{dbh}->prepare('UPDATE Aliases SET type = ? WHERE alias = ? AND id = ?');
$sth->bind_param(1, $type);
@ -1192,11 +1291,11 @@ sub link_alias {
return 1;
} else {
$self->{pbot}->{logger}->log("$id already " . ($row->{'type'} == $self->{alias_type}->{STRONG} ? "strongly" : "weakly") . " linked to $alias, ignoring\n") if $debug_link;
$self->{pbot}->{logger}->log("$id already " . ($row->{'type'} == $self->{alias_type}->{STRONG} ? "strongly" : "weakly") . " linked to $alias, ignoring\n") if $debug_link >= 2;
return 0;
}
} else {
$self->{pbot}->{logger}->log("$id already " . ($row->{'type'} == $self->{alias_type}->{STRONG} ? "strongly" : "weakly") . " linked to $alias, ignoring\n") if $debug_link;
$self->{pbot}->{logger}->log("$id already " . ($row->{'type'} == $self->{alias_type}->{STRONG} ? "strongly" : "weakly") . " linked to $alias, ignoring\n") if $debug_link >= 2;
return 0;
}
}
@ -1214,7 +1313,7 @@ sub link_alias {
return 1;
};
$self->{pbot}->{logger}->log($@) if $@;
$self->{pbot}->{logger}->log("Linked.\n") if $ret and $debug_link;
$self->{pbot}->{logger}->log(($type == $self->{alias_type}->{STRONG} ? "Strongly" : "Weakly") . " linked $id to $alias.\n") if $ret and $debug_link;
return $ret;
}
@ -1516,6 +1615,23 @@ sub get_also_known_as {
return %akas;
}
sub get_ancestor_id {
my ($self, $id) = @_;
my $ancestor = eval {
my $sth = $self->{dbh}->prepare('SELECT id FROM Aliases WHERE alias = ? ORDER BY id LIMIT 1');
$sth->bind_param(1, $id);
$sth->execute();
my $row = $sth->fetchrow_hashref();
return defined $row ? $row->{id} : 0;
};
$self->{pbot}->{logger}->log($@) if $@;
return $id if not $ancestor;
return $ancestor < $id ? $ancestor : $id;
}
# End of public API, the remaining are internal support routines for this module
sub get_new_account_id {