diff --git a/PBot/BotAdminCommands.pm b/PBot/BotAdminCommands.pm index b2a6a8e2..71dd89ed 100644 --- a/PBot/BotAdminCommands.pm +++ b/PBot/BotAdminCommands.pm @@ -136,6 +136,7 @@ sub join_channel { foreach my $channel (split /\s+/, $arguments) { $self->{pbot}->{logger}->log("$nick!$user\@$host made me join $channel\n"); + $self->{pbot}->{event_dispatcher}->dispatch_event('pbot.join', { channel => $channel }); $self->{pbot}->{conn}->join($channel); } @@ -150,6 +151,7 @@ sub part_channel { foreach my $channel (split /\s+/, $arguments) { $self->{pbot}->{logger}->log("$nick!$user\@$host made me part $channel\n"); + $self->{pbot}->{event_dispatcher}->dispatch_event('pbot.part', { channel => $channel }); $self->{pbot}->{conn}->part($channel); } diff --git a/PBot/IRCHandlers.pm b/PBot/IRCHandlers.pm index bff0487b..5cd6d0db 100644 --- a/PBot/IRCHandlers.pm +++ b/PBot/IRCHandlers.pm @@ -132,6 +132,7 @@ sub on_notice { foreach my $chan (keys %{ $self->{pbot}->{channels}->{channels}->hash }) { if($self->{pbot}->{channels}->{channels}->hash->{$chan}{enabled}) { $self->{pbot}->{logger}->log("Joining channel: $chan\n"); + $self->{pbot}->{event_dispatcher}->dispatch_event('pbot.join', { channel => $chan }); $event->{conn}->join($chan); } } diff --git a/PBot/NickList.pm b/PBot/NickList.pm new file mode 100644 index 00000000..d54b2003 --- /dev/null +++ b/PBot/NickList.pm @@ -0,0 +1,166 @@ +# File: NickList.pm +# Author: pragma_ +# +# Purpose: Maintains lists of nicks currently present in channels. +# Used to retrieve list of channels a nick is present in or to +# determine if a nick is present in a channel. + +package PBot::NickList; + +use warnings; +use strict; + +use Data::Dumper; +use Carp (); + +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'); + + $self->{pbot}->{commands}->register(sub { $self->dumpnicks(@_) }, "dumpnicks", 60); + + $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(@_) }); + + # 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(@_) }); +} + +sub dumpnicks { + my ($self, $from, $nick, $user, $host, $arguments) = @_; + my $nicklist = Dumper($self->{nicklist}); + return $nicklist; +} + +sub remove_channel { + my ($self, $channel) = @_; + delete $self->{nicklist}->{lc $channel}; +} + +sub add_nick { + my ($self, $channel, $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 }; +} + +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; + + foreach my $channel (keys $self->{nicklist}) { + if (exists $self->{nicklist}->{$channel}->{$nick}) { + push @channels, $channel; + } + } + + return \@channels; +} + +sub is_present { + my ($self, $channel, $nick) = @_; + + if (exists $self->{nicklist}->{lc $channel} and exists $self->{nicklist}->{lc $channel}->{lc $nick}) { + return 1; + } else { + return 0; + } +} + +sub on_namreply { + my ($self, $event_type, $event) = @_; + my ($channel, $nicks) = ($event->{event}->{args}[2], $event->{event}->{args}[3]); + + foreach my $nick (split ' ', $nicks) { + $nick =~ s/^[@+%]//; # remove OP/Voice/etc indicator from nick + $self->add_nick($channel, $nick); + } + + return 0; +} + +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); + 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); + + foreach my $channel (keys $self->{nicklist}) { + 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); + + foreach my $channel (keys $self->{nicklist}) { + if ($self->is_present($channel, $nick)) { + $self->remove_nick($channel, $nick); + $self->add_nick($channel, $newnick); + } + } + + 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; diff --git a/PBot/PBot.pm b/PBot/PBot.pm index 2e202b95..b82adb4c 100644 --- a/PBot/PBot.pm +++ b/PBot/PBot.pm @@ -34,6 +34,7 @@ use PBot::EventDispatcher; use PBot::IRCHandlers; use PBot::Channels; use PBot::BanTracker; +use PBot::NickList; use PBot::LagChecker; use PBot::MessageHistory; use PBot::AntiFlood; @@ -104,6 +105,7 @@ sub initialize { $self->{stdin_reader} = PBot::StdinReader->new(pbot => $self, %conf); $self->{admins} = PBot::BotAdmins->new(pbot => $self, filename => delete $conf{admins_file}, %conf); $self->{bantracker} = PBot::BanTracker->new(pbot => $self, %conf); + $self->{nicklist} = PBot::NickList->new(pbot => $self, %conf); $self->{lagchecker} = PBot::LagChecker->new(pbot => $self, %conf); $self->{messagehistory} = PBot::MessageHistory->new(pbot => $self, filename => delete $conf{messagehistory_file}, %conf); $self->{antiflood} = PBot::AntiFlood->new(pbot => $self, %conf); diff --git a/PBot/VERSION.pm b/PBot/VERSION.pm index 2a47eb9c..182dad5f 100644 --- a/PBot/VERSION.pm +++ b/PBot/VERSION.pm @@ -13,8 +13,8 @@ use warnings; # These are set automatically by the build/commit script use constant { BUILD_NAME => "PBot", - BUILD_REVISION => 799, - BUILD_DATE => "2014-10-31", + BUILD_REVISION => 800, + BUILD_DATE => "2014-11-14", }; 1;