2021-07-21 06:38:07 +02:00
|
|
|
# File: Channel.pm
|
|
|
|
#
|
2021-08-02 00:53:58 +02:00
|
|
|
# Purpose: Handlers for general channel-related IRC events that aren't handled
|
|
|
|
# by any specialized Handler modules.
|
2021-07-21 06:38:07 +02:00
|
|
|
|
|
|
|
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
|
2021-07-24 01:57:33 +02:00
|
|
|
package PBot::Core::Handlers::Channel;
|
2021-07-21 07:44:51 +02:00
|
|
|
use parent 'PBot::Core::Class';
|
2021-07-21 06:38:07 +02:00
|
|
|
|
|
|
|
use PBot::Imports;
|
2021-07-21 07:44:51 +02:00
|
|
|
use PBot::Core::MessageHistory::Constants ':all';
|
2021-07-21 06:38:07 +02:00
|
|
|
|
|
|
|
use Data::Dumper;
|
|
|
|
use Encode;
|
2021-08-02 00:53:58 +02:00
|
|
|
use MIME::Base64;
|
|
|
|
use Time::HiRes qw/time/;
|
2021-07-21 06:38:07 +02:00
|
|
|
|
|
|
|
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 (@_) });
|
2021-08-02 03:02:10 +02:00
|
|
|
$self->{pbot}->{event_dispatcher}->register_handler('irc.modeflag', sub { $self->on_modeflag (@_) });
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2021-08-02 03:02:10 +02:00
|
|
|
my ($modifier, $flag, $mode, $target);
|
2021-07-21 06:38:07 +02:00
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
my $source = "$nick!$user\@$host";
|
|
|
|
|
|
|
|
# split combined modes
|
2021-07-21 06:38:07 +02:00
|
|
|
while ($mode_string =~ m/(.)/g) {
|
2021-08-02 03:02:10 +02:00
|
|
|
$flag = $1;
|
2021-07-21 06:38:07 +02:00
|
|
|
|
2021-08-02 03:02:10 +02:00
|
|
|
if ($flag eq '-' or $flag eq '+') {
|
|
|
|
$modifier = $flag;
|
2021-07-21 06:38:07 +02:00
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2021-08-02 03:02:10 +02:00
|
|
|
$mode = $modifier . $flag;
|
2021-07-21 06:38:07 +02:00
|
|
|
$target = $event->{event}->{args}->[++$i];
|
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
$self->{pbot}->{logger}->log("Mode $channel [$mode" . (length $target ? " $target" : '') . "] by $source\n");
|
|
|
|
|
2021-08-02 03:02:10 +02:00
|
|
|
# dispatch a single mode flag event
|
2021-08-02 00:53:58 +02:00
|
|
|
$self->{pbot}->{event_dispatcher}->dispatch_event(
|
2021-08-02 03:02:10 +02:00
|
|
|
'irc.modeflag',
|
2021-08-02 00:53:58 +02:00
|
|
|
{
|
|
|
|
source => $source,
|
|
|
|
channel => $channel,
|
|
|
|
mode => $mode,
|
|
|
|
target => $target,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
2021-07-21 06:38:07 +02:00
|
|
|
|
2021-08-02 03:02:10 +02:00
|
|
|
sub on_modeflag {
|
2021-08-02 00:53:58 +02:00
|
|
|
my ($self, $event_type, $event) = @_;
|
2021-07-21 06:38:07 +02:00
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
my ($source, $channel, $mode, $target) = (
|
|
|
|
$event->{source},
|
|
|
|
$event->{channel},
|
|
|
|
$event->{mode},
|
|
|
|
$event->{target},
|
|
|
|
);
|
2021-07-21 06:38:07 +02:00
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
# disregard mode set on user instead of channel
|
|
|
|
return if defined $target and length $target;
|
2021-07-21 06:38:07 +02:00
|
|
|
|
2021-08-02 03:02:10 +02:00
|
|
|
my ($modifier, $flag) = split //, $mode;
|
2021-08-02 00:53:58 +02:00
|
|
|
|
|
|
|
my $modes = $self->{pbot}->{channels}->get_meta($channel, 'MODE') // '';
|
|
|
|
|
|
|
|
if ($modifier eq '+') {
|
|
|
|
$modes = '+' if not length $modes;
|
2021-08-02 03:02:10 +02:00
|
|
|
$modes .= $flag if $modes !~ /\Q$flag/;
|
2021-08-02 00:53:58 +02:00
|
|
|
} else {
|
2021-08-02 03:02:10 +02:00
|
|
|
$modes =~ s/\Q$flag//g;
|
2021-08-02 00:53:58 +02:00
|
|
|
$modes = '' if $modes eq '+';
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
$self->{pbot}->{channels}->{storage}->set($channel, 'MODE', $modes, 1);
|
2021-08-02 03:02:10 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2021-08-02 00:53:58 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2021-08-02 00:53:58 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-08-02 00:53:58 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2021-08-02 00:53:58 +02:00
|
|
|
return 1;
|
2021-07-21 06:38:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|