2014-11-15 02:18:33 +01:00
|
|
|
# File: NickList.pm
|
|
|
|
# Author: pragma_
|
|
|
|
#
|
|
|
|
# Purpose: Maintains lists of nicks currently present in channels.
|
2019-06-26 18:34:19 +02:00
|
|
|
# Used to retrieve list of channels a nick is present in or to
|
2014-11-15 02:18:33 +01:00
|
|
|
# determine if a nick is present in a channel.
|
|
|
|
|
2017-03-05 22:33:31 +01:00
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
package PBot::NickList;
|
|
|
|
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
|
2019-07-11 03:40:53 +02:00
|
|
|
use feature 'unicode_strings';
|
|
|
|
|
2016-09-25 09:03:37 +02:00
|
|
|
use Text::Levenshtein qw/fastdistance/;
|
2014-11-15 02:18:33 +01:00
|
|
|
use Data::Dumper;
|
2018-02-28 20:13:56 +01:00
|
|
|
$Data::Dumper::Sortkeys = 1;
|
2014-11-15 02:18:33 +01:00
|
|
|
use Carp ();
|
2016-09-25 09:03:37 +02:00
|
|
|
use Time::HiRes qw/gettimeofday/;
|
2014-11-15 02:18:33 +01:00
|
|
|
|
|
|
|
sub new {
|
|
|
|
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
|
|
|
|
my ($class, %conf) = @_;
|
|
|
|
my $self = bless {}, $class;
|
|
|
|
$self->initialize(%conf);
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub initialize {
|
|
|
|
my ($self, %conf) = @_;
|
|
|
|
|
|
|
|
$self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
|
|
|
|
$self->{nicklist} = {};
|
|
|
|
|
|
|
|
$self->{pbot}->{registry}->add_default('text', 'nicklist', 'debug', '0');
|
|
|
|
|
2019-12-31 00:57:31 +01:00
|
|
|
$self->{pbot}->{commands}->register(sub { $self->show_nicklist(@_) }, "nicklist", 0);
|
2014-11-15 02:18:33 +01:00
|
|
|
|
2016-09-02 11:17:10 +02:00
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.namreply', sub { $self->on_namreply(@_) });
|
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join(@_) });
|
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_part(@_) });
|
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.quit', sub { $self->on_quit(@_) });
|
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) });
|
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.nick', sub { $self->on_nickchange(@_) });
|
2016-09-25 09:03:37 +02:00
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_activity(@_) });
|
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.caction', sub { $self->on_activity(@_) });
|
2019-06-26 18:34:19 +02:00
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
# handlers for the bot itself joining/leaving channels
|
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('pbot.join', sub { $self->on_join_channel(@_) });
|
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('pbot.part', sub { $self->on_part_channel(@_) });
|
|
|
|
}
|
|
|
|
|
2019-12-31 00:57:31 +01:00
|
|
|
sub show_nicklist {
|
2014-11-15 02:18:33 +01:00
|
|
|
my ($self, $from, $nick, $user, $host, $arguments) = @_;
|
2018-08-03 21:31:45 +02:00
|
|
|
my $nicklist;
|
|
|
|
|
2019-12-31 00:57:31 +01:00
|
|
|
my $admin = $self->{pbot}->{admins}->loggedin($from, "$nick!$user\@$host");
|
|
|
|
|
2018-08-03 21:31:45 +02:00
|
|
|
if (not length $arguments) {
|
2019-12-31 00:57:31 +01:00
|
|
|
if (not $admin) {
|
2019-12-31 01:03:10 +01:00
|
|
|
return "Usage: nicklist <channel> [nick]";
|
2019-12-31 00:57:31 +01:00
|
|
|
}
|
2018-08-03 21:31:45 +02:00
|
|
|
$nicklist = Dumper($self->{nicklist});
|
|
|
|
} else {
|
|
|
|
my @args = split / /, $arguments;
|
|
|
|
|
|
|
|
if (@args == 1) {
|
2019-12-31 00:57:31 +01:00
|
|
|
if (not exists $self->{nicklist}->{$arguments}) {
|
|
|
|
return "No nicklist for $arguments.";
|
|
|
|
}
|
2018-08-03 21:31:45 +02:00
|
|
|
$nicklist = Dumper($self->{nicklist}->{$arguments});
|
|
|
|
} else {
|
2019-12-31 00:57:31 +01:00
|
|
|
if (not exists $self->{nicklist}->{$args[0]}) {
|
|
|
|
return "No nicklist for $args[0].";
|
|
|
|
} elsif (not exists $self->{nicklist}->{$args[0]}->{$args[1]}) {
|
|
|
|
return "No such nick $args[1] in channel $args[0].";
|
|
|
|
}
|
2018-08-03 21:31:45 +02:00
|
|
|
$nicklist = Dumper($self->{nicklist}->{$args[0]}->{$args[1]});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
return $nicklist;
|
|
|
|
}
|
|
|
|
|
2016-09-25 09:03:37 +02:00
|
|
|
sub update_timestamp {
|
|
|
|
my ($self, $channel, $nick) = @_;
|
2016-12-14 00:42:50 +01:00
|
|
|
my $orig_nick = $nick;
|
2016-09-25 09:03:37 +02:00
|
|
|
$channel = lc $channel;
|
|
|
|
$nick = lc $nick;
|
|
|
|
|
|
|
|
if (exists $self->{nicklist}->{$channel} and exists $self->{nicklist}->{$channel}->{$nick}) {
|
|
|
|
$self->{nicklist}->{$channel}->{$nick}->{timestamp} = gettimeofday;
|
2016-12-14 00:42:50 +01:00
|
|
|
} else {
|
|
|
|
$self->{pbot}->{logger}->log("Adding nick '$orig_nick' to channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
|
2020-01-15 03:10:53 +01:00
|
|
|
$self->{nicklist}->{$channel}->{$nick} = { nick => $orig_nick, timestamp => scalar gettimeofday };
|
2016-09-25 09:03:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
sub remove_channel {
|
|
|
|
my ($self, $channel) = @_;
|
|
|
|
delete $self->{nicklist}->{lc $channel};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub add_nick {
|
|
|
|
my ($self, $channel, $nick) = @_;
|
2019-12-31 00:57:31 +01:00
|
|
|
if (not exists $self->{nicklist}->{lc $channel}->{lc $nick}) {
|
|
|
|
$self->{pbot}->{logger}->log("Adding nick '$nick' to channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
|
|
|
|
$self->{nicklist}->{lc $channel}->{lc $nick} = { nick => $nick, timestamp => 0 };
|
|
|
|
}
|
2014-11-15 02:18:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub remove_nick {
|
|
|
|
my ($self, $channel, $nick) = @_;
|
|
|
|
$self->{pbot}->{logger}->log("Removing nick '$nick' from channel '$channel'\n") if $self->{pbot}->{registry}->get_value('nicklist', 'debug');
|
|
|
|
delete $self->{nicklist}->{lc $channel}->{lc $nick};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_channels {
|
|
|
|
my ($self, $nick) = @_;
|
|
|
|
my @channels;
|
|
|
|
|
|
|
|
$nick = lc $nick;
|
|
|
|
|
2017-04-11 04:55:52 +02:00
|
|
|
foreach my $channel (keys %{ $self->{nicklist} }) {
|
2014-11-15 02:18:33 +01:00
|
|
|
if (exists $self->{nicklist}->{$channel}->{$nick}) {
|
|
|
|
push @channels, $channel;
|
|
|
|
}
|
|
|
|
}
|
2019-06-26 18:34:19 +02:00
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
return \@channels;
|
|
|
|
}
|
|
|
|
|
2018-08-03 21:31:45 +02:00
|
|
|
sub set_meta {
|
|
|
|
my ($self, $channel, $nick, $key, $value) = @_;
|
|
|
|
|
|
|
|
$channel = lc $channel;
|
|
|
|
$nick = lc $nick;
|
|
|
|
|
|
|
|
if (not exists $self->{nicklist}->{$channel} or not exists $self->{nicklist}->{$channel}->{$nick}) {
|
2020-01-23 01:50:45 +01:00
|
|
|
if (exists $self->{nicklist}->{$channel} and $nick =~ m/[*?]/) {
|
|
|
|
my $regex = quotemeta $nick;
|
2020-01-23 01:53:18 +01:00
|
|
|
$regex =~ s/\\\*/.*?/g;
|
2020-01-23 01:50:45 +01:00
|
|
|
$regex =~ s/\\\?/./g;
|
|
|
|
|
|
|
|
my $found = 0;
|
|
|
|
foreach my $n (keys %{$self->{nicklist}->{$channel}}) {
|
|
|
|
if ($self->{nicklist}->{$channel}->{$n}->{hostmask} =~ m/$regex/i) {
|
|
|
|
$self->{nicklist}->{$channel}->{$n}->{$key} = $value;
|
|
|
|
$found++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $found;
|
|
|
|
} else {
|
|
|
|
$self->{pbot}->{logger}->log("Nicklist: Attempt to set invalid meta ($key => $value) for $nick in $channel.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
2018-08-03 21:31:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$self->{nicklist}->{$channel}->{$nick}->{$key} = $value;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub delete_meta {
|
|
|
|
my ($self, $channel, $nick, $key) = @_;
|
|
|
|
|
|
|
|
$channel = lc $channel;
|
|
|
|
$nick = lc $nick;
|
|
|
|
|
|
|
|
if (not exists $self->{nicklist}->{$channel}
|
|
|
|
or not exists $self->{nicklist}->{$channel}->{$nick}
|
|
|
|
or not exists $self->{nicklist}->{$channel}->{$nick}->{$key}) {
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
return delete $self->{nicklist}->{$channel}->{$nick}->{$key};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_meta {
|
|
|
|
my ($self, $channel, $nick, $key) = @_;
|
|
|
|
|
|
|
|
$channel = lc $channel;
|
|
|
|
$nick = lc $nick;
|
|
|
|
|
|
|
|
if (not exists $self->{nicklist}->{$channel}
|
|
|
|
or not exists $self->{nicklist}->{$channel}->{$nick}
|
|
|
|
or not exists $self->{nicklist}->{$channel}->{$nick}->{$key}) {
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $self->{nicklist}->{$channel}->{$nick}->{$key};
|
|
|
|
}
|
|
|
|
|
2017-08-12 10:28:23 +02:00
|
|
|
sub is_present_any_channel {
|
|
|
|
my ($self, $nick) = @_;
|
|
|
|
|
|
|
|
$nick = lc $nick;
|
|
|
|
|
|
|
|
foreach my $channel (keys %{ $self->{nicklist} }) {
|
|
|
|
if (exists $self->{nicklist}->{$channel}->{$nick}) {
|
|
|
|
return $self->{nicklist}->{$channel}->{$nick}->{nick};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
sub is_present {
|
|
|
|
my ($self, $channel, $nick) = @_;
|
|
|
|
|
2015-06-08 13:43:00 +02:00
|
|
|
$channel = lc $channel;
|
2015-07-02 00:20:28 +02:00
|
|
|
$nick = lc $nick;
|
2015-06-08 13:43:00 +02:00
|
|
|
|
2015-07-02 00:20:28 +02:00
|
|
|
if (exists $self->{nicklist}->{$channel} and exists $self->{nicklist}->{$channel}->{$nick}) {
|
|
|
|
return $self->{nicklist}->{$channel}->{$nick}->{nick};
|
2014-11-15 02:18:33 +01:00
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-25 09:03:37 +02:00
|
|
|
sub is_present_similar {
|
2017-05-21 08:44:45 +02:00
|
|
|
my ($self, $channel, $nick, $similar) = @_;
|
2016-09-25 09:03:37 +02:00
|
|
|
|
|
|
|
$channel = lc $channel;
|
|
|
|
$nick = lc $nick;
|
|
|
|
|
|
|
|
return 0 if not exists $self->{nicklist}->{$channel};
|
2016-09-25 09:24:49 +02:00
|
|
|
return $self->{nicklist}->{$channel}->{$nick}->{nick} if $self->is_present($channel, $nick);
|
2016-10-14 14:56:54 +02:00
|
|
|
return 0 if $nick =~ m/(?:^\$|\s)/; # not nick-like
|
2016-09-25 21:59:03 +02:00
|
|
|
|
2016-09-25 09:03:37 +02:00
|
|
|
my $percentage = $self->{pbot}->{registry}->get_value('interpreter', 'nick_similarity');
|
|
|
|
$percentage = 0.20 if not defined $percentage;
|
|
|
|
|
2017-05-21 08:44:45 +02:00
|
|
|
$percentage = $similar if defined $similar;
|
|
|
|
|
2016-12-14 00:42:50 +01:00
|
|
|
my $now = gettimeofday;
|
2017-04-11 04:55:52 +02:00
|
|
|
foreach my $person (sort { $self->{nicklist}->{$channel}->{$b}->{timestamp} <=> $self->{nicklist}->{$channel}->{$a}->{timestamp} } keys %{ $self->{nicklist}->{$channel} }) {
|
2016-12-14 00:42:50 +01:00
|
|
|
return 0 if $now - $self->{nicklist}->{$channel}->{$person}->{timestamp} > 3600; # 1 hour
|
2016-09-25 09:03:37 +02:00
|
|
|
my $distance = fastdistance($nick, $person);
|
|
|
|
my $length = length $nick > length $person ? length $nick : length $person;
|
|
|
|
|
|
|
|
if ($length != 0 && $distance / $length <= $percentage) {
|
|
|
|
return $self->{nicklist}->{$channel}->{$person}->{nick};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-06-08 13:43:00 +02:00
|
|
|
sub random_nick {
|
|
|
|
my ($self, $channel) = @_;
|
|
|
|
|
|
|
|
$channel = lc $channel;
|
|
|
|
|
|
|
|
if (exists $self->{nicklist}->{$channel}) {
|
2017-04-11 04:15:08 +02:00
|
|
|
my $now = gettimeofday;
|
2017-04-11 04:55:52 +02:00
|
|
|
my @nicks = grep { $now - $self->{nicklist}->{$channel}->{$_}->{timestamp} < 3600 * 2 } keys %{ $self->{nicklist}->{$channel} };
|
2019-06-26 18:34:19 +02:00
|
|
|
|
2015-06-10 11:26:14 +02:00
|
|
|
my $nick = $nicks[rand @nicks];
|
|
|
|
return $self->{nicklist}->{$channel}->{$nick}->{nick};
|
2015-06-08 13:43:00 +02:00
|
|
|
} else {
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
sub on_namreply {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
my ($channel, $nicks) = ($event->{event}->{args}[2], $event->{event}->{args}[3]);
|
2018-08-03 21:31:45 +02:00
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
foreach my $nick (split ' ', $nicks) {
|
2018-08-03 21:31:45 +02:00
|
|
|
my $stripped_nick = $nick;
|
|
|
|
$stripped_nick =~ s/^[@+%]//g; # remove OP/Voice/etc indicator from nick
|
|
|
|
$self->add_nick($channel, $stripped_nick);
|
|
|
|
|
2019-12-31 00:57:31 +01:00
|
|
|
my ($account_id, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($stripped_nick);
|
2019-12-31 01:12:10 +01:00
|
|
|
|
|
|
|
if (defined $hostmask) {
|
|
|
|
my ($user, $host) = $hostmask =~ m/[^!]+!([^@]+)@(.*)/;
|
|
|
|
$self->set_meta($channel, $stripped_nick, 'hostmask', $hostmask);
|
|
|
|
$self->set_meta($channel, $stripped_nick, 'user', $user);
|
|
|
|
$self->set_meta($channel, $stripped_nick, 'host', $host);
|
|
|
|
}
|
2019-12-31 00:57:31 +01:00
|
|
|
|
2018-08-03 21:31:45 +02:00
|
|
|
if ($nick =~ m/\@/) {
|
|
|
|
$self->set_meta($channel, $stripped_nick, '+o', 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($nick =~ m/\+/) {
|
|
|
|
$self->set_meta($channel, $stripped_nick, '+v', 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($nick =~ m/\%/) {
|
|
|
|
$self->set_meta($channel, $stripped_nick, '+h', 1);
|
|
|
|
}
|
2014-11-15 02:18:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-09-25 09:03:37 +02:00
|
|
|
sub on_activity {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->{to}[0]);
|
|
|
|
$self->update_timestamp($channel, $nick);
|
2016-09-28 23:13:44 +02:00
|
|
|
return 0;
|
2016-09-25 09:03:37 +02:00
|
|
|
}
|
|
|
|
|
2014-11-15 02:18:33 +01:00
|
|
|
sub on_join {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
|
|
|
|
$self->add_nick($channel, $nick);
|
2019-12-31 00:57:31 +01:00
|
|
|
$self->set_meta($channel, $nick, 'hostmask', "$nick!$user\@$host");
|
|
|
|
$self->set_meta($channel, $nick, 'user', $user);
|
|
|
|
$self->set_meta($channel, $nick, 'host', $host);
|
2014-11-15 02:18:33 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub on_part {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to);
|
|
|
|
$self->remove_nick($channel, $nick);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub on_quit {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host);
|
|
|
|
|
2017-04-11 04:55:52 +02:00
|
|
|
foreach my $channel (keys %{ $self->{nicklist} }) {
|
2014-11-15 02:18:33 +01:00
|
|
|
if ($self->is_present($channel, $nick)) {
|
|
|
|
$self->remove_nick($channel, $nick);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub on_kick {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
my ($nick, $channel) = ($event->{event}->to, $event->{event}->{args}[0]);
|
|
|
|
$self->remove_nick($channel, $nick);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub on_nickchange {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
my ($nick, $user, $host, $newnick) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->args);
|
|
|
|
|
2017-04-11 04:55:52 +02:00
|
|
|
foreach my $channel (keys %{ $self->{nicklist} }) {
|
2014-11-15 02:18:33 +01:00
|
|
|
if ($self->is_present($channel, $nick)) {
|
2018-08-03 21:31:45 +02:00
|
|
|
my $meta = delete $self->{nicklist}->{$channel}->{lc $nick};
|
|
|
|
$meta->{nick} = $newnick;
|
|
|
|
$meta->{timestamp} = gettimeofday;
|
|
|
|
$self->{nicklist}->{$channel}->{lc $newnick} = $meta;
|
2014-11-15 02:18:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub on_join_channel {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
$self->remove_channel($event->{channel}); # clear nicklist to remove any stale nicks before repopulating with namreplies
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub on_part_channel {
|
|
|
|
my ($self, $event_type, $event) = @_;
|
|
|
|
$self->remove_channel($event->{channel});
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|