mirror of
https://github.com/pragma-/pbot.git
synced 2024-11-26 22:09:26 +01:00
352 lines
13 KiB
Perl
352 lines
13 KiB
Perl
|
# File: Channel.pm
|
||
|
#
|
||
|
# Purpose: Handlers for channel-related IRC events.
|
||
|
|
||
|
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
||
|
# SPDX-License-Identifier: MIT
|
||
|
|
||
|
package PBot::IRCHandlers::Channel;
|
||
|
use parent 'PBot::Class';
|
||
|
|
||
|
use PBot::Imports;
|
||
|
|
||
|
use PBot::MessageHistory::Constants ':all';
|
||
|
|
||
|
use Time::HiRes qw/time/;
|
||
|
use Data::Dumper;
|
||
|
|
||
|
use MIME::Base64;
|
||
|
use Encode;
|
||
|
|
||
|
sub new {
|
||
|
my ($class, %args) = @_;
|
||
|
|
||
|
# ensure class was passed a PBot instance
|
||
|
if (not exists $args{pbot}) {
|
||
|
Carp::croak("Missing pbot reference to $class");
|
||
|
}
|
||
|
|
||
|
my $self = bless { pbot => $args{pbot} }, $class;
|
||
|
$self->initialize(%args);
|
||
|
return $self;
|
||
|
}
|
||
|
|
||
|
sub initialize {
|
||
|
my ($self, %conf) = @_;
|
||
|
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.mode', sub { $self->on_mode (@_) });
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.join', sub { $self->on_join (@_) });
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_departure (@_) });
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick (@_) });
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.invite', sub { $self->on_invite (@_) });
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.channelmodeis', sub { $self->on_channelmodeis (@_) });
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.topic', sub { $self->on_topic (@_) });
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.topicinfo', sub { $self->on_topicinfo (@_) });
|
||
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.channelcreate', sub { $self->on_channelcreate (@_) });
|
||
|
}
|
||
|
|
||
|
# FIXME: on_mode doesn't handle chanmodes that have parameters, e.g. +l
|
||
|
sub on_mode {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
|
||
|
my ($nick, $user, $host, $mode_string, $channel) = (
|
||
|
$event->{event}->nick,
|
||
|
$event->{event}->user,
|
||
|
$event->{event}->host,
|
||
|
$event->{event}->{args}->[0],
|
||
|
lc $event->{event}->{to}->[0],
|
||
|
);
|
||
|
|
||
|
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
|
||
|
|
||
|
my $i = 0;
|
||
|
my ($modifier, $char, $mode, $target);
|
||
|
|
||
|
while ($mode_string =~ m/(.)/g) {
|
||
|
$char = $1;
|
||
|
|
||
|
if ($char eq '-' or $char eq '+') {
|
||
|
$modifier = $char;
|
||
|
next;
|
||
|
}
|
||
|
|
||
|
$mode = $modifier . $char;
|
||
|
$target = $event->{event}->{args}->[++$i];
|
||
|
|
||
|
$self->{pbot}->{logger}->log("Mode $channel [$mode" . (length $target ? " $target" : '') . "] by $nick!$user\@$host\n");
|
||
|
|
||
|
# TODO: figure out a good way to allow other packages to receive "track_mode" events
|
||
|
# i.e., perhaps by emitting a modechange event or some such and registering handlers
|
||
|
$self->{pbot}->{banlist}->track_mode("$nick!$user\@$host", $channel, $mode, $target);
|
||
|
$self->{pbot}->{chanops}->track_mode("$nick!$user\@$host", $channel, $mode, $target);
|
||
|
|
||
|
if (defined $target and length $target) {
|
||
|
# mode set on user
|
||
|
my $message_account = $self->{pbot}->{messagehistory}->get_message_account($nick, $user, $host);
|
||
|
|
||
|
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, "MODE $mode $target", MSG_CHAT);
|
||
|
|
||
|
# TODO: here as well
|
||
|
if ($modifier eq '-') {
|
||
|
$self->{pbot}->{nicklist}->delete_meta($channel, $target, "+$char");
|
||
|
} else {
|
||
|
$self->{pbot}->{nicklist}->set_meta($channel, $target, $mode, 1);
|
||
|
}
|
||
|
} else {
|
||
|
# mode set on channel
|
||
|
my $modes = $self->{pbot}->{channels}->get_meta($channel, 'MODE');
|
||
|
|
||
|
if (defined $modes) {
|
||
|
if ($modifier eq '+') {
|
||
|
$modes = '+' if not length $modes;
|
||
|
$modes .= $char;
|
||
|
} else {
|
||
|
$modes =~ s/\Q$char//g;
|
||
|
}
|
||
|
|
||
|
# TODO: here as well
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'MODE', $modes, 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
sub on_join {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
|
||
|
my ($nick, $user, $host, $channel) = (
|
||
|
$event->{event}->nick,
|
||
|
$event->{event}->user,
|
||
|
$event->{event}->host,
|
||
|
lc $event->{event}->{to}->[0],
|
||
|
);
|
||
|
|
||
|
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
|
||
|
|
||
|
my $message_account = $self->{pbot}->{messagehistory}->get_message_account($nick, $user, $host);
|
||
|
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, "JOIN", MSG_JOIN);
|
||
|
|
||
|
$self->{pbot}->{messagehistory}->{database}->devalidate_channel($message_account, $channel);
|
||
|
|
||
|
my $msg = 'JOIN';
|
||
|
|
||
|
# IRCv3 extended-join capability provides more details about user
|
||
|
if (exists $self->{pbot}->{irc_capabilities}->{'extended-join'}) {
|
||
|
my ($nickserv, $gecos) = (
|
||
|
$event->{event}->{args}->[0],
|
||
|
$event->{event}->{args}->[1],
|
||
|
);
|
||
|
|
||
|
$msg .= " $nickserv :$gecos";
|
||
|
|
||
|
$self->{pbot}->{messagehistory}->{database}->update_gecos($message_account, $gecos, scalar time);
|
||
|
|
||
|
if ($nickserv ne '*') {
|
||
|
$self->{pbot}->{messagehistory}->{database}->link_aliases($message_account, undef, $nickserv);
|
||
|
$self->{pbot}->{antiflood}->check_nickserv_accounts($nick, $nickserv);
|
||
|
} else {
|
||
|
$self->{pbot}->{messagehistory}->{database}->set_current_nickserv_account($message_account, '');
|
||
|
}
|
||
|
|
||
|
$self->{pbot}->{antiflood}->check_bans($message_account, $event->{event}->from, $channel);
|
||
|
}
|
||
|
|
||
|
$self->{pbot}->{antiflood}->check_flood(
|
||
|
$channel, $nick, $user, $host, $msg,
|
||
|
$self->{pbot}->{registry}->get_value('antiflood', 'join_flood_threshold'),
|
||
|
$self->{pbot}->{registry}->get_value('antiflood', 'join_flood_time_threshold'),
|
||
|
MSG_JOIN,
|
||
|
);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
sub on_invite {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
|
||
|
my ($nick, $user, $host, $target, $channel) = (
|
||
|
$event->{event}->nick,
|
||
|
$event->{event}->user,
|
||
|
$event->{event}->host,
|
||
|
$event->{event}->to,
|
||
|
lc $event->{event}->{args}->[0]
|
||
|
);
|
||
|
|
||
|
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
|
||
|
|
||
|
$self->{pbot}->{logger}->log("$nick!$user\@$host invited $target to $channel!\n");
|
||
|
|
||
|
# if invited to a channel on our channel list, go ahead and join it
|
||
|
if ($target eq $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
|
||
|
if ($self->{pbot}->{channels}->is_active($channel)) {
|
||
|
$self->{pbot}->{interpreter}->add_botcmd_to_command_queue($channel, "join $channel", 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
sub on_kick {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
|
||
|
my ($nick, $user, $host, $target, $channel, $reason) = (
|
||
|
$event->{event}->nick,
|
||
|
$event->{event}->user,
|
||
|
$event->{event}->host,
|
||
|
$event->{event}->to,
|
||
|
lc $event->{event}->{args}->[0],
|
||
|
$event->{event}->{args}->[1]
|
||
|
);
|
||
|
|
||
|
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
|
||
|
|
||
|
$self->{pbot}->{logger}->log("$nick!$user\@$host kicked $target from $channel ($reason)\n");
|
||
|
|
||
|
# hostmask of the person being kicked
|
||
|
my $target_hostmask;
|
||
|
|
||
|
# look up message history account for person being kicked
|
||
|
my ($message_account) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($target);
|
||
|
|
||
|
if (defined $message_account) {
|
||
|
# update target hostmask
|
||
|
$target_hostmask = $self->{pbot}->{messagehistory}->{database}->find_most_recent_hostmask($message_account);
|
||
|
|
||
|
# add "KICKED by" to kicked person's message history
|
||
|
my $text = "KICKED by $nick!$user\@$host ($reason)";
|
||
|
|
||
|
$self->{pbot}->{messagehistory}->add_message($message_account, $target_hostmask, $channel, $text, MSG_DEPARTURE);
|
||
|
|
||
|
# do stuff that happens in check_flood
|
||
|
my ($target_nick, $target_user, $target_host) = $target_hostmask =~ m/^([^!]+)!([^@]+)@(.*)/;
|
||
|
|
||
|
$self->{pbot}->{antiflood}->check_flood(
|
||
|
$channel, $target_nick, $target_user, $target_host, $text,
|
||
|
$self->{pbot}->{registry}->get_value('antiflood', 'join_flood_threshold'),
|
||
|
$self->{pbot}->{registry}->get_value('antiflood', 'join_flood_time_threshold'),
|
||
|
MSG_DEPARTURE,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
# look up message history account for person doing the kicking
|
||
|
$message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account_id("$nick!$user\@$host");
|
||
|
|
||
|
if (defined $message_account) {
|
||
|
# replace target nick with target hostmask if available
|
||
|
if (defined $target_hostmask) {
|
||
|
$target = $target_hostmask;
|
||
|
}
|
||
|
|
||
|
# add "KICKED $target" to kicker's message history
|
||
|
my $text = "KICKED $target from $channel ($reason)";
|
||
|
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, $text, MSG_CHAT);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
sub on_departure {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
|
||
|
my ($nick, $user, $host, $channel, $args) = (
|
||
|
$event->{event}->nick,
|
||
|
$event->{event}->user,
|
||
|
$event->{event}->host,
|
||
|
lc $event->{event}->{to}->[0],
|
||
|
$event->{event}->args
|
||
|
);
|
||
|
|
||
|
($nick, $user, $host) = $self->{pbot}->{irchandlers}->normalize_hostmask($nick, $user, $host);
|
||
|
|
||
|
my $text = uc ($event->{event}->type) . ' ' . $args;
|
||
|
|
||
|
my $message_account = $self->{pbot}->{messagehistory}->get_message_account($nick, $user, $host);
|
||
|
|
||
|
if ($text =~ m/^QUIT/) {
|
||
|
# QUIT messages must be added to the mesasge history of each channel the user is on
|
||
|
my $channels = $self->{pbot}->{nicklist}->get_channels($nick);
|
||
|
foreach my $chan (@$channels) {
|
||
|
next if $chan !~ m/^#/;
|
||
|
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $chan, $text, MSG_DEPARTURE);
|
||
|
}
|
||
|
} else {
|
||
|
$self->{pbot}->{messagehistory}->add_message($message_account, "$nick!$user\@$host", $channel, $text, MSG_DEPARTURE);
|
||
|
}
|
||
|
|
||
|
$self->{pbot}->{antiflood}->check_flood(
|
||
|
$channel, $nick, $user, $host, $text,
|
||
|
$self->{pbot}->{registry}->get_value('antiflood', 'join_flood_threshold'),
|
||
|
$self->{pbot}->{registry}->get_value('antiflood', 'join_flood_time_threshold'),
|
||
|
MSG_DEPARTURE,
|
||
|
);
|
||
|
|
||
|
my $u = $self->{pbot}->{users}->find_user($channel, "$nick!$user\@$host");
|
||
|
|
||
|
# log user out if logged in and not stayloggedin
|
||
|
# TODO: this should probably be in Users.pm with its own part/quit/kick handler
|
||
|
if (defined $u and $u->{loggedin} and not $u->{stayloggedin}) {
|
||
|
$self->{pbot}->{logger}->log("Logged out $nick.\n");
|
||
|
delete $u->{loggedin};
|
||
|
$self->{pbot}->{users}->save;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
sub on_channelmodeis {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
|
||
|
my (undef, $channel, $modes) = $event->{event}->args;
|
||
|
|
||
|
$self->{pbot}->{logger}->log("Channel $channel modes: $modes\n");
|
||
|
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'MODE', $modes, 1);
|
||
|
}
|
||
|
|
||
|
sub on_channelcreate {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
|
||
|
my ($owner, $channel, $timestamp) = $event->{event}->args;
|
||
|
|
||
|
$self->{pbot}->{logger}->log("Channel $channel created by $owner on " . localtime($timestamp) . "\n");
|
||
|
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'CREATED_BY', $owner, 1);
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'CREATED_ON', $timestamp, 1);
|
||
|
}
|
||
|
|
||
|
sub on_topic {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
|
||
|
if (not length $event->{event}->{to}->[0]) {
|
||
|
# on join
|
||
|
my (undef, $channel, $topic) = $event->{event}->args;
|
||
|
$self->{pbot}->{logger}->log("Topic for $channel: $topic\n");
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC', $topic, 1);
|
||
|
} else {
|
||
|
# user changing topic
|
||
|
my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host);
|
||
|
my $channel = $event->{event}->{to}->[0];
|
||
|
my $topic = $event->{event}->{args}->[0];
|
||
|
|
||
|
$self->{pbot}->{logger}->log("$nick!$user\@$host changed topic for $channel to: $topic\n");
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC', $topic, 1);
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC_SET_BY', "$nick!$user\@$host", 1);
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC_SET_ON', time);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
sub on_topicinfo {
|
||
|
my ($self, $event_type, $event) = @_;
|
||
|
my (undef, $channel, $by, $timestamp) = $event->{event}->args;
|
||
|
$self->{pbot}->{logger}->log("Topic for $channel set by $by on " . localtime($timestamp) . "\n");
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC_SET_BY', $by, 1);
|
||
|
$self->{pbot}->{channels}->{storage}->set($channel, 'TOPIC_SET_ON', $timestamp, 1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
1;
|