#
# Copyright (C) 2001-2021 by Peder Stray
#
use strict;
use Irssi 20020428.1608;
use Text::Abbrev;
use POSIX;
use vars qw{$VERSION %IRSSI};
($VERSION) = '$Revision: 1.26.1 $' =~ / (\d+(\.\d+)+) /;
%IRSSI = (
name => 'query',
authors => 'Peder Stray',
contact => '[email protected]',
url => 'https://github.com/pstray/irssi-query',
license => 'GPL',
description => 'Give you more control over when to jump to query windows and when to just tell you one has been created. Enhanced autoclose.',
);
use vars qw(%state);
*state = \%Query::state; # used for tracking idletime and state
my($own);
my(%defaults); # used for storing defaults
my($query_opts) = {}; # stores option abbrevs
sub load_defaults {
my $file = Irssi::get_irssi_dir."/query";
local *FILE;
%defaults = ();
open FILE, "<", $file;
while () {
my($mask,$maxage,$immortal) = split;
$defaults{$mask}{maxage} = $maxage;
$defaults{$mask}{immortal} = $immortal;
}
close FILE;
}
sub save_defaults {
my $file = Irssi::get_irssi_dir."/query";
local *FILE;
open FILE, ">", $file;
for (keys %defaults) {
my $d = $defaults{$_};
print FILE join("\t", $_,
exists $d->{maxage} ? $d->{maxage} : -1,
exists $d->{immortal} ? $d->{immortal} : -1,
), "\n";
}
close FILE;
}
sub sec2str {
my($sec) = @_;
my($ret);
use integer;
$ret = ($sec%60)."s ";
$sec /= 60;
$ret = ($sec%60)."m ".$ret;
$sec /= 60;
$ret = ($sec%24)."h ".$ret;
$sec /= 24;
$ret = $sec."d ".$ret;
$ret =~ s/\b0[dhms] //g;
$ret =~ s/ $//;
return $ret;
}
sub str2sec {
my($str) = @_;
for ($str) {
s/\s+//g;
s/d/*24h/g;
s/h/*60m/g;
s/m/*60s/g;
s/s/+/g;
s/\+$//;
}
if ($str =~ /^[0-9*+]+$/) {
$str = eval $str;
}
else {
$str = 0;
}
return $str;
}
sub set_defaults {
my($serv,$nick,$address) = @_;
my $tag = lc $serv->{tag};
return unless $address;
$state{$tag}{$nick}{address} = $address;
for my $mask (sort {userhost_cmp($serv,$a,$b)}keys %defaults) {
if ($serv->mask_match_address($mask, $nick, $address)) {
for my $key (keys %{$defaults{$mask}}) {
$state{$tag}{$nick}{$key} = $defaults{$mask}{$key}
if $defaults{$mask}{$key} >= 0;
}
}
}
}
sub time2str {
my($time) = @_;
return strftime("%c", localtime $time);
}
sub userhost_cmp {
my($serv, $am, $bm) = @_;
my($an,$aa) = split "!", $am;
my($bn,$ba) = split "!", $bm;
my($t1,$t2);
$t1 = $serv->mask_match_address($bm, $an, $aa);
$t2 = $serv->mask_match_address($am, $bn, $ba);
return $t1 - $t2 if $t1 || $t2;
$an = $bn = '*';
$am = "$an!$aa";
$bm = "$bn!$ba";
$t1 = $serv->mask_match_address($bm, $an, $aa);
$t2 = $serv->mask_match_address($am, $bn, $ba);
return $t1 - $t2 if $t1 || $t2;
for ($am, $bm, $aa, $ba) {
s/(\*!)?[^*]*@/$1*/;
}
$t1 = $serv->mask_match_address($bm, $an, $aa);
$t2 = $serv->mask_match_address($am, $bn, $ba);
return $t1 - $t2 if $t1 || $t2;
return 0;
}
sub sig_message_own_private {
my($server,$msg,$nick,$orig_target) = @_;
$own = $nick;
}
sub sig_message_private {
my($server,$msg,$nick,$addr) = @_;
undef $own;
}
sub sig_print_message {
my($dest, $text, $strip) = @_;
return unless $dest->{level} & MSGLEVEL_MSGS;
my $server = $dest->{server};
return unless $server;
my $witem = $server->window_item_find($dest->{target});
my $tag = lc $server->{tag};
return unless $witem->{type} eq 'QUERY';
$state{$tag}{$witem->{name}}{time} = time;
}
sub sig_query_address_changed {
my($query) = @_;
set_defaults($query->{server}, $query->{name}, $query->{address});
}
sub sig_query_created {
my ($query, $auto) = @_;
my $qwin = $query->window();
my $awin = Irssi::active_win();
my $serv = $query->{server};
my $nick = $query->{name};
my $tag = lc $query->{server_tag};
if ($auto && $qwin->{refnum} != $awin->{refnum}) {
if ($own eq $query->{name}) {
if (Irssi::settings_get_bool('query_autojump_own')) {
$qwin->set_active();
} else {
$awin->printformat(MSGLEVEL_CLIENTCRAP, 'query_created',
$nick, $query->{server_tag},
$qwin->{refnum})
if Irssi::settings_get_bool('query_noisy');
}
} else {
if (Irssi::settings_get_bool('query_autojump')) {
$qwin->set_active();
} else {
$awin->printformat(MSGLEVEL_CLIENTCRAP, 'query_created',
$nick, $query->{server_tag},
$qwin->{refnum})
if Irssi::settings_get_bool('query_noisy');
}
}
}
undef $own;
$state{$tag}{$nick} = { time => time };
if (ref($serv) eq 'Irssi::Irc::Server') {
$serv->redirect_event('userhost', 1, ":$nick", -1, undef,
{
"event 302" => "redir query userhost",
"" => "event empty",
});
$serv->send_raw("USERHOST :$nick");
}
}
sub sig_query_destroyed {
my($query) = @_;
delete $state{lc $query->{server_tag}}{$query->{name}};
}
sub sig_query_nick_changed {
my($query,$old_nick) = @_;
my($tag) = lc $query->{server_tag};
$state{$tag}{$query->{name}} = delete $state{$tag}{$old_nick};
}
sub sig_redir_query_userhost {
my($serv,$data) = @_;
$data =~ s/^\S*\s*://;
for (split " ", $data) {
if (/([^=*]+)\*?=.(.+)/) {
set_defaults($serv, $1, $2);
}
}
}
sub sig_session_restore {
open STATE, "<", sprintf "%s/query.state", Irssi::get_irssi_dir;
%state = (); # only needed if bound as command
while () {
chomp;
my($tag,$nick,%data) = split "\t";
for my $key (keys %data) {
$state{lc $tag}{$nick}{$key} ||= $data{$key};
}
}
close STATE;
}
sub sig_session_save {
open STATE, ">", sprintf "%s/query.state", Irssi::get_irssi_dir;
for my $tag (keys %state) {
for my $nick (keys %{$state{$tag}}) {
print STATE join("\t",$tag,$nick,%{$state{$tag}{$nick}}), "\n";
}
}
close STATE;
}
sub check_queries {
my(@queries) = Irssi::queries;
my($defmax) = Irssi::settings_get_time('query_autoclose')/1000;
my($minage) = Irssi::settings_get_time('query_autoclose_grace')/1000;
my($win) = Irssi::active_win;
for my $query (@queries) {
my $tag = lc $query->{server_tag};
my $name = $query->{name};
my $state = $state{$tag}{$name};
my $age = time - $state->{time};
my $maxage = $defmax;
$maxage = $state->{maxage} if defined $state->{maxage};
# skip the ones we have marked as immortal
next if $state->{immortal};
# maxage = 0 means we have disabled autoclose
next unless $maxage;
# not old enough
next if $age < $maxage;
# unseen messages
next if $query->{data_level} > 1;
# active window
next if $query->is_active &&
$query->window->{refnum} == $win->{refnum};
# graceperiod
next if time - $query->{last_unread_msg} < $minage;
# kill it off
Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'query_closed',
$query->{name}, $query->{server_tag})
if Irssi::settings_get_bool('query_noisy');
$query->destroy;
}
}
sub cmd_query {
my($data,$server,$witem) = @_;
my(@data) = split " ", $data;
my(@params,@opts,$query,$tag,$nick);
my($state,$info,$save);
while (@data) {
my $param = shift @data;
if ($param =~ s/^-//) {
my $opt = $query_opts->{lc $param};
if ($opt) {
if ($opt eq 'window') {
push @opts, "-$param";
} elsif ($opt eq 'immortal') {
$state->{immortal} = 1;
} elsif ($opt eq 'info') {
$info = 1;
} elsif ($opt eq 'mortal') {
$state->{immortal} = 0;
} elsif ($opt eq 'timeout') {
$state->{maxage} = str2sec shift @data;
} elsif ($opt eq 'save') {
$save++;
} else {
# unhandled known opt
}
} elsif ($tag = Irssi::server_find_tag($param)) {
$tag = $tag->{tag};
push @opts, "-$tag";
} else {
# bogus opt...
push @opts, "-$param";
}
} else {
# normal parameter
push @params, $param;
}
}
if (@params) {
Irssi::signal_continue("@opts @params",$server,$witem);
# find the query...
my $serv = Irssi::server_find_tag($tag || $server->{tag});
return unless $serv;
$query = $serv->window_item_find($params[0]);
} else {
if ($witem && $witem->{type} eq 'QUERY') {
$query = $witem;
}
}
if ($query) {
$nick = $query->{name};
$tag = lc $query->{server_tag};
my $opts;
for (keys %$state) {
$state{$tag}{$nick}{$_} = $state->{$_};
$opts++;
}
$state = $state{$tag}{$nick};
if ($info) {
Irssi::signal_stop();
my(@items,$key,$val);
my $timeout = Irssi::settings_get_time('query_autoclose')/1000;
$timeout = $state->{maxage} if defined $state->{maxage};
if ($timeout) {
$timeout .= " (".sec2str($timeout).")";
} else {
$timeout .= " (Off)";
}
@items = (
Server => $query->{server_tag},
Nick => $nick,
Address => $state->{address},
Created => time2str($query->{createtime}),
Immortal => $state->{immortal}?'Yes':'No',
Timeout => $timeout,
Idle => sec2str(time - $state->{time}),
);
$query->printformat(MSGLEVEL_CLIENTCRAP, 'query_info_header');
while (($key,$val) = splice @items, 0, 2) {
$query->printformat(MSGLEVEL_CLIENTCRAP, 'query_info',
$key, $val);
}
$query->printformat(MSGLEVEL_CLIENTCRAP, 'query_info_footer');
return;
}
if ($save) {
Irssi::signal_stop;
unless ($state->{address}) {
$query->printformat(MSGLEVEL_CLIENTCRAP,
'query_crap', 'This query has no address yet');
return;
}
my $mask = Irssi::Irc::get_mask($nick, $state->{address},
Irssi::Irc::MASK_USER |
Irssi::Irc::MASK_DOMAIN
);
for (qw(immortal maxage)) {
if (exists $state->{$_}) {
$defaults{$mask}{$_} = $state->{$_};
} else {
delete $defaults{$mask}{$_};
}
}
save_defaults;
return;
}
if (!@params) {
Irssi::signal_stop;
return if $opts;
if ($state{$tag}{$nick}{immortal}) {
$witem->printformat(MSGLEVEL_CLIENTCRAP,
'query_crap', 'This query is immortal');
} else {
$witem->command("unquery")
if Irssi::settings_get_bool('query_unqueries');
}
}
}
}
sub cmd_unquery {
my($data,$server,$witem) = @_;
my($param) = split " ", $data;
my($query,$tag,$nick);
if ($param) {
$query = $server->query_find($param) if $server;
} else {
$query = $witem if $witem && $witem->{type} eq 'QUERY';
}
if ($query) {
$nick = $query->{name};
$tag = lc $query->{server_tag};
if ($state{$tag}{$nick}{immortal}) {
if ($param) {
$witem->printformat(MSGLEVEL_CLIENTCRAP,
'query_crap',
"Query with $nick is immortal");
} else {
$witem->printformat(MSGLEVEL_CLIENTCRAP,
'query_crap',
'This query is immortal');
}
Irssi::signal_stop;
}
}
}
Irssi::command_bind('query', 'cmd_query');
Irssi::command_bind('unquery', 'cmd_unquery');
Irssi::command_set_options('query', 'immortal mortal info save +timeout');
abbrev $query_opts, qw(window immortal mortal info save timeout);
#Irssi::command_bind('debug', sub { print Dumper \%state });
#Irssi::command_bind('query_save', 'sig_session_save');
#Irssi::command_bind('query_restore', 'sig_session_restore');
Irssi::theme_register(
[
'query_created',
'{line_start}{hilight Query:} started with {nick $0} [$1] in window $2',
'query_closed',
'{line_start}{hilight Query:} closed with {nick $0} [$1]',
'query_info_header', '',
'query_info_footer', '',
'query_crap',
'{line_start}{hilight Query:} $0',
'query_warn',
'{line_start}{hilight Query:} {error Warning:} $0',
'query_info',
'%#$[8]0: $1',
]);
Irssi::settings_add_bool('query', 'query_autojump_own', 1);
Irssi::settings_add_bool('query', 'query_autojump', 0);
Irssi::settings_add_bool('query', 'query_noisy', 1);
Irssi::settings_add_bool('query', 'query_unqueries',
Irssi::version < 20020919.1507 ||
Irssi::version >= 20021006.1620 );
Irssi::settings_add_time('query', 'query_autoclose', 0);
Irssi::settings_add_time('query', 'query_autoclose_grace', '5min');
Irssi::signal_add_last('message own_private', 'sig_message_own_private');
Irssi::signal_add_last('message private', 'sig_message_private');
Irssi::signal_add_last('query created', 'sig_query_created');
Irssi::signal_add('print text', 'sig_print_message');
Irssi::signal_add('query address changed', 'sig_query_address_changed');
Irssi::signal_add('query destroyed', 'sig_query_destroyed');
Irssi::signal_add('query nick changed', 'sig_query_nick_changed');
Irssi::signal_add('redir query userhost', 'sig_redir_query_userhost');
Irssi::signal_add('session save', 'sig_session_save');
Irssi::signal_add('session restore', 'sig_session_restore');
Irssi::timeout_add(5000, 'check_queries', undef);
load_defaults;
for my $query (Irssi::queries) {
my($tag) = lc $query->{server_tag};
my($nick) = $query->{name};
$state{$tag}{$nick}{time}
||= $query->{last_unread_msg} || $query->{createtime} || time;
set_defaults($query->{server}, $nick, $query->{address});
}
if (Irssi::settings_get_time("autoclose_query")) {
Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'query_warn',
"autoclose_query is set, please set to 0");
}