pbot/PBot/MessageHistory.pm

477 lines
18 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.
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
# 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/.
package PBot::MessageHistory;
use parent 'PBot::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(gettimeofday tv_interval);
use Time::Duration;
use PBot::MessageHistory_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';
2020-02-15 23:38:32 +01:00
$self->{database} = PBot::MessageHistory_SQLite->new(pbot => $self->{pbot}, filename => $self->{filename});
$self->{database}->begin();
$self->{database}->devalidate_all_channels();
2020-02-15 23:38:32 +01:00
$self->{MSG_CHAT} = 0; # PRIVMSG, ACTION
$self->{MSG_JOIN} = 1; # JOIN
$self->{MSG_DEPARTURE} = 2; # PART, QUIT, KICK
$self->{MSG_NICKCHANGE} = 3; # CHANGED NICK
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}->{registry}->add_default('text', 'messagehistory', 'max_messages', 32);
$self->{pbot}->{commands}->register(sub { $self->cmd_list_also_known_as(@_) }, "aka", 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_aka_link(@_) }, "akalink", 1);
$self->{pbot}->{commands}->register(sub { $self->cmd_aka_unlink(@_) }, "akaunlink", 1);
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}]"; }
} 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 (gettimeofday - $akas{$aka}->{last_seen});
$result .= " last seen: $seen";
}
2020-02-15 23:38:32 +01:00
if ($show_hostmasks or $show_nickserv or $show_gecos or $show_id or $show_relationship) { $sep = ",\n"; }
else { $sep = ", "; }
}
return $result;
} else {
return "I don't know anybody named $opt_args[0].";
}
}
sub cmd_recall_message {
my ($self, $context) = @_;
if (not defined $context->{from}) {
2020-02-15 23:38:32 +01:00
$self->{pbot}->{logger}->log("Command missing ~from parameter!\n");
return "";
}
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,
2020-02-15 23:38:32 +01:00
'channel|c:s' => \$recall_channel,
'history|h:s' => \$recall_history,
'text|t:s' => \$recall_text,
2020-02-15 23:38:32 +01:00
'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;
2020-02-15 23:38:32 +01:00
$recall_count = 1 if (not defined $recall_count) || ($recall_count <= 0);
return "You may only select a count of up to $max_count messages." if $recall_count > $max_count;
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 no history and -x isn't already set to somebody
if ($recall_count > 1 and not defined $recall_history 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
$recall_history = $context->{nick} eq $recall_nick ? 2 : 1 if defined $recall_nick and not defined $recall_history;
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
2020-02-15 23:38:32 +01:00
if (not defined $recall_nick and defined $recall_context) { $recall_nick = $recall_context; }
2020-02-15 23:38:32 +01:00
my ($account, $found_nick);
2020-02-15 23:38:32 +01:00
if (defined $recall_nick) {
($account, $found_nick) = $self->{database}->find_message_account_by_nick($recall_nick);
2020-02-15 23:38:32 +01:00
if (not defined $account) { return "I don't know anybody named $recall_nick."; }
2020-02-15 23:38:32 +01:00
$found_nick =~ s/!.*$//;
}
2020-02-15 23:38:32 +01:00
my $message;
if ($random) {
$message = $self->{database}->get_random_message($account, $recall_channel);
} elsif ($recall_history =~ /^\d+$/ and not defined $recall_text) {
2020-02-15 23:38:32 +01:00
# integral history
if (defined $account) {
my $max_messages = $self->{database}->get_max_messages($account, $recall_channel);
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)');
2020-02-15 23:38:32 +01:00
if (not defined $message) { return "No message found at index $recall_history in channel $recall_channel."; }
} else {
# regex history
$message = $self->{database}->recall_message_by_text($account, $recall_channel, $recall_history, '(?:recall|mock|ftfy|fix|clapper)');
2020-02-15 23:38:32 +01:00
if (not defined $message) {
if (defined $account) { return "No message for nick $found_nick in channel $recall_channel containing \"$recall_history\""; }
else { return "No message in channel $recall_channel containing \"$recall_history\"."; }
}
}
2020-02-15 23:38:32 +01:00
my $context_account;
2020-02-15 23:38:32 +01:00
if (defined $recall_context) {
($context_account) = $self->{database}->find_message_account_by_nick($recall_context);
2020-02-15 23:38:32 +01:00
if (not defined $context_account) { return "I don't know anybody named $recall_context."; }
}
2020-02-15 23:38:32 +01:00
my $messages = $self->{database}->get_message_context($message, $recall_before, $recall_after, $recall_count, $recall_history, $context_account);
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) {
if ($max_recall_time && gettimeofday - $msg->{timestamp} > $max_recall_time && not $self->{pbot}->{users}->loggedin_admin($context->{from}, $context->{hostmask})) {
2020-02-15 23:38:32 +01:00
$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(gettimeofday - $msg->{timestamp});
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] $msg->{nick} $text\n";
2020-02-15 23:38:32 +01:00
} elsif ($text =~ s/^\/me\s+//) {
$result .= $raw ? "$text\n" : "[$ago] * $msg->{nick} $text\n";
2020-02-15 23:38:32 +01:00
} else {
$result .= $raw ? "$text\n" : "[$ago] <$msg->{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 gettimeofday, msg => $text, mode => $mode});
}
1;