3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-11-20 10:59:29 +01:00
pbot/lib/PBot/Core/MessageHistory.pm

547 lines
20 KiB
Perl
Raw Normal View History

# File: MessageHistory.pm
#
# Purpose: Keeps track of who has said what and when, as well as their
2019-06-26 18:34:19 +02:00
# nickserv accounts and alter-hostmasks.
#
# Used in conjunction with AntiFlood and Quotegrabs for kick/ban on
# flood/ban-evasion and grabbing quotes, respectively.
2021-07-11 00:00:22 +02:00
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
License project under MPL2 This patch adds the file LICENSE which is the verbatim copy of the Mozilla Public License Version 2.0 as retreived from https://www.mozilla.org/media/MPL/2.0/index.815ca599c9df.txt on 2017-03-05. This patch also places license headers for the MPL2 type A variant of the license header in the following files: PBot/AntiFlood.pm PBot/BanTracker.pm PBot/BlackList.pm PBot/BotAdminCommands.pm PBot/BotAdmins.pm PBot/ChanOpCommands.pm PBot/ChanOps.pm PBot/Channels.pm PBot/Commands.pm PBot/DualIndexHashObject.pm PBot/EventDispatcher.pm PBot/FactoidCommands.pm PBot/FactoidModuleLauncher.pm PBot/Factoids.pm PBot/HashObject.pm PBot/IRCHandlers.pm PBot/IgnoreList.pm PBot/IgnoreListCommands.pm PBot/Interpreter.pm PBot/LagChecker.pm PBot/Logger.pm PBot/MessageHistory.pm PBot/MessageHistory_SQLite.pm PBot/NickList.pm PBot/PBot.pm PBot/Plugins.pm PBot/Plugins/AntiAway.pm PBot/Plugins/AntiKickAutoRejoin.pm PBot/Plugins/AntiRepeat.pm PBot/Plugins/AntiTwitter.pm PBot/Plugins/AutoRejoin.pm PBot/Plugins/Counter.pm PBot/Plugins/Quotegrabs.pm PBot/Plugins/Quotegrabs/Quotegrabs_Hashtable.pm PBot/Plugins/Quotegrabs/Quotegrabs_SQLite.pm PBot/Plugins/UrlTitles.pm PBot/Plugins/_Example.pm PBot/Refresher.pm PBot/Registerable.pm PBot/Registry.pm PBot/RegistryCommands.pm PBot/SQLiteLogger.pm PBot/SQLiteLoggerLayer.pm PBot/SelectHandler.pm PBot/StdinReader.pm PBot/Timer.pm PBot/Utils/ParseDate.pm PBot/VERSION.pm build/update-version.pl modules/acronym.pl modules/ago.pl modules/c11std.pl modules/c2english.pl modules/c2english/CGrammar.pm modules/c2english/c2eng.pl modules/c99std.pl modules/cdecl.pl modules/cfaq.pl modules/cjeopardy/IRCColors.pm modules/cjeopardy/QStatskeeper.pm modules/cjeopardy/Scorekeeper.pm modules/cjeopardy/cjeopardy.pl modules/cjeopardy/cjeopardy_answer.pl modules/cjeopardy/cjeopardy_filter.pl modules/cjeopardy/cjeopardy_hint.pl modules/cjeopardy/cjeopardy_qstats.pl modules/cjeopardy/cjeopardy_scores.pl modules/cjeopardy/cjeopardy_show.pl modules/codepad.pl modules/compiler_block.pl modules/compiler_client.pl modules/compiler_vm/Diff.pm modules/compiler_vm/cc modules/compiler_vm/compiler_client.pl modules/compiler_vm/compiler_server.pl modules/compiler_vm/compiler_server_vbox_win32.pl modules/compiler_vm/compiler_server_watchdog.pl modules/compiler_vm/compiler_vm_client.pl modules/compiler_vm/compiler_vm_server.pl modules/compiler_vm/compiler_watchdog.pl modules/compiler_vm/languages/_c_base.pm modules/compiler_vm/languages/_default.pm modules/compiler_vm/languages/bash.pm modules/compiler_vm/languages/bc.pm modules/compiler_vm/languages/bf.pm modules/compiler_vm/languages/c11.pm modules/compiler_vm/languages/c89.pm modules/compiler_vm/languages/c99.pm modules/compiler_vm/languages/clang.pm modules/compiler_vm/languages/clang11.pm modules/compiler_vm/languages/clang89.pm modules/compiler_vm/languages/clang99.pm modules/compiler_vm/languages/clangpp.pm modules/compiler_vm/languages/clisp.pm modules/compiler_vm/languages/cpp.pm modules/compiler_vm/languages/freebasic.pm modules/compiler_vm/languages/go.pm modules/compiler_vm/languages/haskell.pm modules/compiler_vm/languages/java.pm modules/compiler_vm/languages/javascript.pm modules/compiler_vm/languages/ksh.pm modules/compiler_vm/languages/lua.pm modules/compiler_vm/languages/perl.pm modules/compiler_vm/languages/python.pm modules/compiler_vm/languages/python3.pm modules/compiler_vm/languages/qbasic.pm modules/compiler_vm/languages/scheme.pm modules/compiler_vm/languages/server/_c_base.pm modules/compiler_vm/languages/server/_default.pm modules/compiler_vm/languages/server/c11.pm modules/compiler_vm/languages/server/c89.pm modules/compiler_vm/languages/server/c99.pm modules/compiler_vm/languages/server/clang.pm modules/compiler_vm/languages/server/clang11.pm modules/compiler_vm/languages/server/clang89.pm modules/compiler_vm/languages/server/clang99.pm modules/compiler_vm/languages/server/cpp.pm modules/compiler_vm/languages/server/freebasic.pm modules/compiler_vm/languages/server/haskell.pm modules/compiler_vm/languages/server/java.pm modules/compiler_vm/languages/server/qbasic.pm modules/compiler_vm/languages/server/tendra.pm modules/compiler_vm/languages/sh.pm modules/compiler_vm/languages/tendra.pm modules/compliment modules/cstd.pl modules/define.pl modules/dice_roll.pl modules/excuse.sh modules/expand_macros.pl modules/fnord.pl modules/funnyish_quote.pl modules/g.pl modules/gdefine.pl modules/gen_cfacts.pl modules/gencstd.pl modules/get_title.pl modules/getcfact.pl modules/google.pl modules/gspy.pl modules/gtop10.pl modules/gtop15.pl modules/headlines.pl modules/horoscope modules/horrorscope modules/ideone.pl modules/insult.pl modules/love_quote.pl modules/man.pl modules/map.pl modules/math.pl modules/prototype.pl modules/qalc.pl modules/random_quote.pl modules/seen.pl modules/urban modules/weather.pl modules/wikipedia.pl pbot.pl pbot.sh It is highly recommended that this list of files is reviewed to ensure that all files are the copyright of the sole maintainer of the repository. If any files with license headers contain the intellectual property of anyone else, it is recommended that a request is made to revise this patch or that the explicit permission of the co-author is gained to allow for the license of the work to be changed. I (Tomasz Kramkowski), the contributor, take no responsibility for any legal action taken against the maintainer of this repository for incorrectly claiming copyright to any work not owned by the maintainer of this repository.
2017-03-05 22:33:31 +01:00
2021-07-21 07:44:51 +02:00
package PBot::Core::MessageHistory;
use parent 'PBot::Core::Class';
2021-06-19 06:23:34 +02:00
use PBot::Imports;
2019-07-11 03:40:53 +02:00
use Getopt::Long qw(GetOptionsFromArray);
use Time::HiRes qw(time tv_interval);
use Time::Duration;
2021-07-21 07:44:51 +02:00
use PBot::Core::MessageHistory::Storage::SQLite;
sub initialize {
2020-02-15 23:38:32 +01:00
my ($self, %conf) = @_;
$self->{filename} = $conf{filename} // $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/message_history.sqlite3';
2021-07-21 07:44:51 +02:00
$self->{database} = PBot::Core::MessageHistory::Storage::SQLite->new(
pbot => $self->{pbot},
filename => $self->{filename}
);
2020-02-15 23:38:32 +01:00
$self->{database}->begin();
$self->{database}->devalidate_all_channels();
2020-02-15 23:38:32 +01:00
$self->{pbot}->{registry}->add_default('text', 'messagehistory', 'max_recall_time', $conf{max_recall_time} // 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_recall_message(@_) }, "recall", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_rebuild_aliases(@_) }, "rebuildaliases", 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_list_also_known_as(@_) }, "aka", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_aka_link(@_) }, "akalink", 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_aka_unlink(@_) }, "akaunlink", 1);
# add capabilities to admin group
2020-02-15 23:38:32 +01:00
$self->{pbot}->{capabilities}->add('admin', 'can-akalink', 1);
$self->{pbot}->{capabilities}->add('admin', 'can-akaunlink', 1);
2020-02-15 23:38:32 +01:00
$self->{pbot}->{atexit}->register(sub { $self->{database}->end(); return; });
}
sub cmd_list_also_known_as {
my ($self, $context) = @_;
2020-02-15 23:38:32 +01:00
my $usage = "Usage: aka [-hilngr] <nick> [-sort <by>]; -h show hostmasks; -i show ids; -l show last seen, -n show nickserv accounts; -g show gecos, -r show relationships";
2020-02-15 23:38:32 +01:00
if (not length $context->{arguments}) {
return $usage;
}
2020-02-15 23:38:32 +01:00
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
Getopt::Long::Configure("bundling_override");
2020-02-15 23:38:32 +01:00
my $sort_method = undef;
my ($show_hostmasks, $show_gecos, $show_nickserv, $show_id, $show_relationship, $show_weak, $show_last_seen, $dont_use_aliases_table);
my @opt_args = $self->{pbot}->{interpreter}->split_line($context->{arguments}, strip_quotes => 1);
GetOptionsFromArray(
\@opt_args,
2020-02-15 23:38:32 +01:00
'h' => \$show_hostmasks,
'l' => \$show_last_seen,
2020-02-15 23:38:32 +01:00
'n' => \$show_nickserv,
'r' => \$show_relationship,
'g' => \$show_gecos,
'w' => \$show_weak,
'z' => \$dont_use_aliases_table,
'i' => \$show_id,
'sort|s=s' => \$sort_method,
2020-02-15 23:38:32 +01:00
);
return "/say $getopt_error -- $usage" if defined $getopt_error;
return "Too many arguments -- $usage" if @opt_args > 1;
return "Missing argument -- $usage" if @opt_args != 1;
2020-02-15 23:38:32 +01:00
$sort_method = 'seen' if $show_last_seen and not defined $sort_method;
$sort_method = 'nick' if not defined $sort_method;
my %sort = (
'id' => sub {
if ($_[1] eq '+') {
return $_[0]->{$a}->{id} <=> $_[0]->{$b}->{id};
} else {
return $_[0]->{$b}->{id} <=> $_[0]->{$a}->{id};
}
},
'seen' => sub {
if ($_[1] eq '+') {
return $_[0]->{$b}->{last_seen} <=> $_[0]->{$a}->{last_seen};
} else {
return $_[0]->{$a}->{last_seen} <=> $_[0]->{$b}->{last_seen};
}
},
'nickserv' => sub {
if ($_[1] eq '+') {
return lc $_[0]->{$a}->{nickserv} cmp lc $_[0]->{$b}->{nickserv};
} else {
return lc $_[0]->{$b}->{nickserv} cmp lc $_[0]->{$a}->{nickserv};
}
},
'nick' => sub {
if ($_[1] eq '+') {
return lc $_[0]->{$a}->{nick} cmp lc $_[0]->{$b}->{nick};
} else {
return lc $_[0]->{$b}->{nick} cmp lc $_[0]->{$a}->{nick};
}
},
'user' => sub {
if ($_[1] eq '+') {
return lc $_[0]->{$a}->{user} cmp lc $_[0]->{$b}->{user};
} else {
return lc $_[0]->{$b}->{user} cmp lc $_[0]->{$a}->{user};
}
},
'host' => sub {
if ($_[1] eq '+') {
return lc $_[0]->{$a}->{host} cmp lc $_[0]->{$b}->{host};
} else {
return lc $_[0]->{$b}->{host} cmp lc $_[0]->{$a}->{host};
}
},
2020-10-23 09:21:27 +02:00
'hostmask' => sub {
if ($_[1] eq '+') {
return lc $_[0]->{$a}->{hostmask} cmp lc $_[0]->{$b}->{hostmask};
} else {
return lc $_[0]->{$b}->{hostmask} cmp lc $_[0]->{$a}->{hostmask};
}
},
2020-10-23 09:21:27 +02:00
'gecos' => sub {
if ($_[1] eq '+') {
return lc $_[0]->{$a}->{gecos} cmp lc $_[0]->{$b}->{gecos};
} else {
return lc $_[0]->{$b}->{gecos} cmp lc $_[0]->{$a}->{gecos};
}
},
);
my $sort_direction = '+';
if ($sort_method =~ s/^(\+|\-)//) {
$sort_direction = $1;
}
if (not exists $sort{$sort_method}) {
return "Invalid sort method '$sort_method'; valid methods are: " . join(', ', sort keys %sort) . "; prefix with - to invert sort direction.";
}
my %akas = $self->{database}->get_also_known_as($opt_args[0], $dont_use_aliases_table);
2020-02-15 23:38:32 +01:00
if (%akas) {
my $result = "$opt_args[0] also known as:\n";
2020-02-15 23:38:32 +01:00
my %nicks;
my $sep = "";
foreach my $aka (sort { $sort{$sort_method}->(\%akas, $sort_direction) } keys %akas) {
2020-02-15 23:38:32 +01:00
next if $aka =~ /^Guest\d+(?:!.*)?$/;
next if exists $akas{$aka}->{type} and $akas{$aka}->{type} == $self->{database}->{alias_type}->{WEAK} && not $show_weak;
2020-02-15 23:38:32 +01:00
if (not $show_hostmasks) {
my ($nick) = $aka =~ m/([^!]+)/;
next if exists $nicks{$nick};
$nicks{$nick}->{id} = $akas{$aka}->{id};
$result .= "$sep$nick";
} else {
$result .= "$sep$aka";
}
$result .= "?" if $akas{$aka}->{nickchange} == 1;
$result .= " ($akas{$aka}->{nickserv})" if $show_nickserv and exists $akas{$aka}->{nickserv};
$result .= " {$akas{$aka}->{gecos}}" if $show_gecos and exists $akas{$aka}->{gecos};
if ($show_relationship) {
if ($akas{$aka}->{id} == $akas{$aka}->{alias}) {
$result .= " [$akas{$aka}->{id}]";
} else {
$result .= " [$akas{$aka}->{id} -> $akas{$aka}->{alias}]";
}
2020-02-15 23:38:32 +01:00
} elsif ($show_id) {
$result .= " [$akas{$aka}->{id}]";
}
$result .= " [WEAK]" if exists $akas{$aka}->{type} and $akas{$aka}->{type} == $self->{database}->{alias_type}->{WEAK};
2020-02-15 23:38:32 +01:00
if ($show_last_seen) {
my $seen = concise ago (time - $akas{$aka}->{last_seen});
$result .= " (seen $seen)";
}
if ($show_hostmasks or $show_nickserv or $show_gecos or $show_id or $show_relationship) {
$sep = ",\n";
} else {
$sep = ", ";
}
2020-02-15 23:38:32 +01:00
}
return $result;
} else {
return "I don't know anybody named $opt_args[0].";
}
}
sub cmd_recall_message {
my ($self, $context) = @_;
my $usage = 'Usage: recall [nick [history [channel]]] [-c <channel>] [-t <text>] [-b <context before>] [-a <context after>] [-x <filter to nick>] [-n <count>] [-r raw mode] [+ ...]';
my $arguments = $context->{arguments};
if (not length $arguments) {
return $usage;
}
2020-02-15 23:38:32 +01:00
$arguments = lc $arguments;
2020-02-15 23:38:32 +01:00
my @recalls = split /\s\+\s/, $arguments;
2020-02-15 23:38:32 +01:00
my $getopt_error;
local $SIG{__WARN__} = sub {
$getopt_error = shift;
chomp $getopt_error;
};
my $result = '';
Getopt::Long::Configure("bundling_override");
# global state
my ($recall_channel, $raw, $random);
2020-02-15 23:38:32 +01:00
foreach my $recall (@recalls) {
my ($recall_nick, $recall_text, $recall_history, $recall_before, $recall_after, $recall_context, $recall_count);
my @opt_args = $self->{pbot}->{interpreter}->split_line($recall, strip_quotes => 1);
GetOptionsFromArray(
\@opt_args,
'channel|c=s' => \$recall_channel,
'history|h=s' => \$recall_history,
'text|t=s' => \$recall_text,
'before|b=i' => \$recall_before,
'after|a=i' => \$recall_after,
'count|n=i' => \$recall_count,
'context|x=s' => \$recall_context,
'raw|r' => \$raw,
'random' => \$random,
2020-02-15 23:38:32 +01:00
);
2020-02-15 23:38:32 +01:00
return "/say $getopt_error -- $usage" if defined $getopt_error;
2020-06-05 23:56:53 +02:00
if (defined $recall_history and defined $recall_text) {
return "/say $context->{nick}: The -h and -t options cannot be used together.";
}
# we swap these $recall variables around so much later on that we
# need to remember which flags were explicitly set...
2020-02-15 23:38:32 +01:00
my $channel_arg = 1 if defined $recall_channel;
my $history_arg = 1 if defined $recall_history;
$recall_nick = shift @opt_args if @opt_args;
$recall_history = shift @opt_args if @opt_args and not $history_arg and not defined $recall_text;
if (not $channel_arg) {
$recall_channel = "@opt_args" if @opt_args;
} else {
if (defined $recall_history) {
$recall_history .= ' ';
}
$recall_history .= "@opt_args" if @opt_args;
}
if (defined $recall_text and not defined $recall_history) {
$recall_history = $recall_text;
}
2015-06-16 02:58:25 +02:00
my $max_count = $self->{pbot}->{registry}->get_value('messagehistory', 'max_recall_count') // 50;
if ((not defined $recall_count) || ($recall_count <= 0)) {
$recall_count = 1;
}
if ($recall_count > $max_count) {
return "You may only select a count of up to $max_count messages.";
}
2015-06-16 02:58:25 +02:00
2020-02-15 23:38:32 +01:00
$recall_before = 0 if not defined $recall_before;
$recall_after = 0 if not defined $recall_after;
# imply -x if -n > 1 and -x isn't already set to somebody
if ($recall_count > 1 and not defined $recall_context) {
$recall_context = $recall_nick;
}
# make -n behave like -b if -n > 1 and no history is specified
if (not defined $recall_history and $recall_count > 1) {
2020-02-15 23:38:32 +01:00
$recall_before = $recall_count - 1;
$recall_count = 0;
}
if ($recall_before + $recall_after > 100) { return "You may only select up to 100 lines of surrounding context."; }
2015-06-16 02:58:25 +02:00
if ($recall_count > 1 and ($recall_before > 0 or $recall_after > 0)) { return "The `count` and `before/after` options cannot be used together."; }
2020-02-15 23:38:32 +01:00
# swap nick and channel if recall nick looks like channel and channel wasn't specified
if (not $channel_arg and $recall_nick =~ m/^#/) {
my $temp = $recall_nick;
$recall_nick = $recall_channel;
$recall_channel = $temp;
}
2020-02-15 23:38:32 +01:00
$recall_history = 1 if not defined $recall_history;
2020-02-15 23:38:32 +01:00
# swap history and channel if history looks like a channel and neither history or channel were specified
if (not $channel_arg and not $history_arg and $recall_history =~ m/^#/) {
my $temp = $recall_history;
$recall_history = $recall_channel;
$recall_channel = $temp;
}
2020-02-15 23:38:32 +01:00
# skip recall command if recalling self without arguments
if (defined $recall_nick and not defined $recall_history) {
$recall_history = $context->{nick} eq $recall_nick ? 2 : 1;
}
2020-02-15 23:38:32 +01:00
# set history to most recent message if not specified
$recall_history = '1' if not defined $recall_history;
2020-02-15 23:38:32 +01:00
# set channel to current channel if not specified
$recall_channel = $context->{from} if not defined $recall_channel;
# yet another sanity check for people using it wrong
2020-02-15 23:38:32 +01:00
if ($recall_channel !~ m/^#/) {
$recall_history = "$recall_history $recall_channel";
$recall_channel = $context->{from};
2020-02-15 23:38:32 +01:00
}
2017-04-11 04:18:20 +02:00
# set nick argument to -x argument if no nick was provided but -x was
if (not defined $recall_nick and defined $recall_context) {
$recall_nick = $recall_context;
}
# message account and stored nickname with proper typographical casing
2020-02-15 23:38:32 +01:00
my ($account, $found_nick);
# get message account and found nick if a nick was provided
2020-02-15 23:38:32 +01:00
if (defined $recall_nick) {
# account and hostmask
2020-02-15 23:38:32 +01:00
($account, $found_nick) = $self->{database}->find_message_account_by_nick($recall_nick);
if (not defined $account) {
return "I don't know anybody named $recall_nick.";
}
# keep only nick portion of hostmask
2020-02-15 23:38:32 +01:00
$found_nick =~ s/!.*$//;
}
# matching message found in database, if any
2020-02-15 23:38:32 +01:00
my $message;
if ($random) {
# get a random message
$message = $self->{database}->get_random_message($account, $recall_channel, $recall_nick);
} elsif ($recall_history =~ /^\d+$/ and not defined $recall_text) {
2020-02-15 23:38:32 +01:00
# integral history
# if a nick was given, ensure requested history is within range of nick's history count
2020-02-15 23:38:32 +01:00
if (defined $account) {
my $max_messages = $self->{database}->get_max_messages($account, $recall_channel, $recall_nick);
2020-02-15 23:38:32 +01:00
if ($recall_history < 1 || $recall_history > $max_messages) {
if ($max_messages == 0) {
return "No messages for $recall_nick in $recall_channel yet.";
2020-02-15 23:38:32 +01:00
} else {
return "Please choose a history between 1 and $max_messages";
}
}
}
2020-02-15 23:38:32 +01:00
$recall_history--;
$message = $self->{database}->recall_message_by_count($account, $recall_channel, $recall_history, '(?:recall|mock|ftfy|fix|clapper)', $recall_nick);
2020-02-15 23:38:32 +01:00
if (not defined $message) {
if (defined $account) {
return "No message found at index $recall_history for $found_nick in $recall_channel.";
} else {
return "No message found at index $recall_history in $recall_channel.";
}
}
2020-02-15 23:38:32 +01:00
} else {
# regex history
$message = $self->{database}->recall_message_by_text($account, $recall_channel, $recall_history, '(?:recall|mock|ftfy|fix|clapper)', $recall_nick);
2020-02-15 23:38:32 +01:00
if (not defined $message) {
if (defined $account) {
return "No message for $found_nick in $recall_channel containing \"$recall_history\"";
} else {
return "No message in $recall_channel containing \"$recall_history\".";
}
}
}
my ($context_account, $context_nick);
2020-02-15 23:38:32 +01:00
if (defined $recall_context) {
($context_account, $context_nick) = $self->{database}->find_message_account_by_nick($recall_context);
if (not defined $context_account) {
return "I don't know anybody named $recall_context.";
}
# keep only nick portion of hostmask
$context_nick =~ s/!.*$//;
}
my $messages = $self->{database}->get_message_context($message, $recall_before, $recall_after, $recall_count, $recall_history, $context_account, $context_nick);
2020-02-15 23:38:32 +01:00
my $max_recall_time = $self->{pbot}->{registry}->get_value('messagehistory', 'max_recall_time');
2020-02-15 23:38:32 +01:00
foreach my $msg (@$messages) {
# optionally limit messages by by a maximum recall duration from the current time, for privacy
if ($max_recall_time && time - $msg->{timestamp} > $max_recall_time
&& not $self->{pbot}->{users}->loggedin_admin($context->{from}, $context->{hostmask}))
{
$max_recall_time = duration $max_recall_time;
$result .= "Sorry, you can not recall messages older than $max_recall_time.";
return $result;
2020-02-15 23:38:32 +01:00
}
2020-02-15 23:38:32 +01:00
my $text = $msg->{msg};
my $ago = concise ago (time - $msg->{timestamp});
my $nick;
if (not $raw) {
if ($msg->{hostmask}) {
($nick) = $msg->{hostmask} =~ /^([^!]+)!/;
} else {
$nick = $self->{database}->find_most_recent_hostmask($msg->{id});
($nick) = $nick =~ m/^([^!]+)/;
}
}
2020-02-15 23:38:32 +01:00
if ( $text =~ s/^(NICKCHANGE)\b/changed nick to/
or $text =~ s/^(KICKED|QUIT)\b/lc "$1"/e
or $text =~ s/^MODE ([^ ]+) (.*)/set mode $1 on $2/
or $text =~ s/^(JOIN|PART)\b/lc "$1ed"/e)
{
$text =~ s/^(quit) (.*)/$1 ($2)/; # fix ugly "[nick] quit Quit: Leaving."
$result .= $raw ? "$text\n" : "[$ago] $nick $text\n";
}
elsif ($text =~ s/^\/me\s+//) {
$result .= $raw ? "$text\n" : "[$ago] * $nick $text\n";
}
else {
$result .= $raw ? "$text\n" : "[$ago] <$nick> $text\n";
2020-02-15 23:38:32 +01:00
}
}
}
return $result;
}
sub cmd_rebuild_aliases {
my ($self, $context) = @_;
$self->{database}->rebuild_aliases_table;
}
sub cmd_aka_link {
my ($self, $context) = @_;
my ($id, $alias, $type) = split /\s+/, $context->{arguments};
$type = $self->{database}->{alias_type}->{STRONG} if not defined $type;
if (not $id or not $alias) {
return "Usage: link <target id> <alias id> [type]";
}
my $source = $self->{database}->find_most_recent_hostmask($id);
my $target = $self->{database}->find_most_recent_hostmask($alias);
if (not $source) {
return "No such id $id found.";
}
if (not $target) {
return "No such id $alias found.";
}
if ($self->{database}->link_alias($id, $alias, $type)) {
return "/say $source " . ($type == $self->{database}->{alias_type}->{WEAK} ? "weakly" : "strongly") . " linked to $target.";
} else {
return "Link failed.";
}
}
sub cmd_aka_unlink {
my ($self, $context) = @_;
my ($id, $alias) = split /\s+/, $context->{arguments};
if (not $id or not $alias) {
return "Usage: unlink <target id> <alias id>";
}
my $source = $self->{database}->find_most_recent_hostmask($id);
my $target = $self->{database}->find_most_recent_hostmask($alias);
if (not $source) {
return "No such id $id found.";
}
if (not $target) {
return "No such id $alias found.";
}
if ($self->{database}->unlink_alias($id, $alias)) {
return "/say $source unlinked from $target.";
} else {
return "Unlink failed.";
}
}
sub get_message_account {
my ($self, $nick, $user, $host) = @_;
return $self->{database}->get_message_account($nick, $user, $host);
}
sub add_message {
my ($self, $account, $mask, $channel, $text, $mode) = @_;
$self->{database}->add_message($account, $mask, $channel, { timestamp => scalar time, msg => $text, mode => $mode });
}
1;