From: Haw Loeung Date: Mon, 5 Dec 2022 01:25:46 +0000 (+1100) Subject: [matterircd-complete] Update to v2.0 X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=7a1dd35e8fde99f87df8b3acfa857cce366459eb;p=irssi-scripts.irssi.org.git [matterircd-complete] Update to v2.0 --- diff --git a/scripts/matterircd_complete.pl b/scripts/matterircd_complete.pl index f8b55d3..9e8e3ee 100644 --- a/scripts/matterircd_complete.pl +++ b/scripts/matterircd_complete.pl @@ -56,28 +56,13 @@ use strict; use warnings; use experimental 'smartmatch'; -use Irssi::TextUI; -use Irssi qw( - command_bind - gui_input_get_pos - gui_input_set - gui_input_set_pos - parse_special - settings_add_bool - settings_add_int - settings_add_str - settings_get_bool - settings_get_int - settings_get_str - signal_add - signal_add_last - signal_continue -); +require Irssi::TextUI; +require Irssi; # Enable for debugging purposes only. # use Data::Dumper; -our $VERSION = '1.00'; +our $VERSION = '2.00'; # e2973ff our %IRSSI = ( name => 'Matterircd Tab Auto Complete', description => 'Adds tab completion for Matterircd message threads', @@ -94,58 +79,220 @@ my $KEY_SPC = 32; my $KEY_B = 66; my $KEY_O = 79; -settings_add_str('matterircd_complete', 'matterircd_complete_networks', ''); -settings_add_str('matterircd_complete', 'matterircd_complete_nick_ignore', ''); +Irssi::settings_add_str('matterircd_complete', 'matterircd_complete_networks', ''); +Irssi::settings_add_str('matterircd_complete', 'matterircd_complete_nick_ignore', ''); +Irssi::settings_add_str('matterircd_complete', 'matterircd_complete_channel_dont_ignore', ''); + +sub _wi_print { + my ($wi, $msg) = @_; + + if ($wi) { + $wi->print($msg); + } else { + Irssi::print($msg); + } +} #============================================================================== +my %color_config; + +# Taken from nickcolor_expando irssi script +# These are all the colors, sorted by main color class +# To display and select colors you want/want to avoid based on your background, use /cubes_text from cubes.pl +my @all_colors = ( + qw[20 30 40 50 04 66 0C 61 60 67 6L], # RED + qw[37 3D 36 4C 46 5C 56 6C 6J 47 5D 6K 6D 57 6E 5E 4E 4K 4J 5J 4D 5K 6R], # ORANGE + qw[3C 4I 5I 6O 6I 06 4O 5O 3U 0E 5U 6U 6V 6P 6Q 6W 5P 4P 4V 4W 5W 4Q 5Q 5R 6Y 6X], # YELLOW + qw[26 2D 2C 3I 3O 4U 5V 2J 3V 3P 3J 5X], # YELLOW-GREEN + qw[16 1C 2I 2U 2O 1I 1O 1V 1P 02 0A 1U 2V 4X], # GREEN + qw[1D 1J 1Q 1W 1X 2Y 2S 2R 3Y 3Z 3S 3R 2K 3K 4S 5Z 5Y 4R 3Q 2Q 2X 2W 3X 3W 2P 4Y], # GREEN-TURQUOIS + qw[17 1E 1L 1K 1R 1S 03 1M 1N 1T 0B 1Y 1Z 2Z 4Z], # TURQUOIS + qw[28 2E 18 1F 19 1G 1A 1B 1H 2N 2H 09 3H 3N 2T 3T 2M 2G 2A 2F 2L 3L 3F 4M 3M 3G 29 4T 5T], # LIGHT-BLUE + qw[22 33 44 0D 45 5B 6A 5A 5H 3B 4H 3A 4G 39 4F 6S 6T 5L 5N], # VIOLET + qw[21 32 42 53 63 52 43 34 35 55 65 6B 4B 4A 48 5G 6H 5M 6M 6N], # PINK + qw[38 31 05 64 54 41 51 62 69 68 59 5F 6F 58 49 6G], # ROSE + qw[11 12 23 25 24 13 14 01 15 2B 4N], # DARK-BLUE + qw[7A 00 10 7B 7C 7D 7E 7G 7F], # DARK-GRAY + qw[7H 7I 27 7K 7J 08 7L 3E 7O 7Q 7N 7M 7P], # GRAY + qw[7S 7T 7R 4L 7W 7U 7V 5S 07 7X 6Z 0F], # LIGHT-GRAY +); +# These are the colors unwanted with a dark theme +my @dark_theme_unwanted = ( + qw[11 12 23 25 24 13 14 01 15 2B 4N], # DARK-BLUE + qw[7A 00 10 7B 7C 7D 7E 7G 7F], # DARK-GRAY + qw[7H 7I 27 7K 7J 08 7L 3E 7O 7Q 7N 7M 7P], # GRAY + qw[7S 7T 7R 4L 7W 7U 7V 5S 07 7X 6Z 0F], # LIGHT-GRAY +); +# These are the colors unwanted with a light theme +my @solarized_light_theme_unwanted = ( + qw[4U 4V 4W 4X 4Y 4Z 5U 5V 5W 5X 5Y 5Z 6U 6V 6W 6X 6Y 6Z 6O 6P 6Q 6R 6S 6T], # too light flashy colors + qw[7T 7U 7V 7W 7X 07 7R 7S 7T 7U 7V 7W 7X 7B 7C 7D 7E 7F 7H 7I 7J 7K 7M 7N 7O 7P], # too light grayscales + qw[5S 4L 7Q 5T 4T 4M 5M 6M 6N 1Z 2Z 1Y 2X 2W 3X 3W 1U 2V 1X 2Y 3V 3Z 3Y 2U], # hand picked too light + redundant +); + +Irssi::settings_add_int('matterircd_complete', 'matterircd_complete_thread_id_color', -1); +# Default color theme to none, so we use all available colors. +Irssi::settings_add_str('matterircd_complete', 'matterircd_complete_thread_id_color_theme', ''); +Irssi::settings_add_bool('matterircd_complete', 'matterircd_complete_thread_id_allow_bold', 0); +Irssi::settings_add_bool('matterircd_complete', 'matterircd_complete_thread_id_allow_italic', 0); +Irssi::settings_add_bool('matterircd_complete', 'matterircd_complete_thread_id_allow_underline', 0); +# Allowed colors will be applied first +# These can be a list of 20 30 40 50 5F colors, or without spaces 203040505F +Irssi::settings_add_str('matterircd_complete', 'matterircd_complete_thread_id_allowed_colors', ''); +Irssi::settings_add_str('matterircd_complete', 'matterircd_complete_thread_id_unwanted_colors', ''); +$color_config{'color_theme'} = ''; +$color_config{'allowed_colors'} = ''; +$color_config{'unwanted_colors'} = ''; +# Initialize +my @thread_id_selected_colors = (); # Rely on message/thread IDs stored in message cache so we can shorten # to save on screen real-estate. -settings_add_int('matterircd_complete', 'matterircd_complete_shorten_message_thread_id', 5); -settings_add_bool('matterircd_complete', 'matterircd_complete_shorten_message_thread_id_hide_prefix', 1); +Irssi::settings_add_int('matterircd_complete', 'matterircd_complete_shorten_message_thread_id', 5); +Irssi::settings_add_bool('matterircd_complete', 'matterircd_complete_shorten_message_thread_id_hide_prefix', 1); +Irssi::settings_add_str('matterircd_complete', 'matterircd_complete_override_reply_prefix', '↪'); + +# Taken from nickcolor_expando irssi script and adapted for our use +sub xcolor_to_irssi { + # Set to foreground xcolor + my $c = "X".$_[0]; + my @ext_colour_off = ( + '.', '-', ',', + '+', "'", '&', + ); + if ($c =~ /^(X)(?:0([[:xdigit:]])|([1-6])(?:([0-9])|([a-z]))|7([a-x]))$/i) { + my $bg = $1 eq 'x'; + my $col = defined $2 ? hex $2 + : defined $6 ? 232 + (ord lc $6) - (ord 'a') + : 16 + 36 * ($3 - 1) + (defined $4 ? $4 : 10 + (ord lc $5) - (ord 'a')); + if ($col < 0x10) { + my $chr = chr $col + ord '0'; + return "\cD" . ($bg ? "/$chr" : "$chr/"); + } + else { + return "\cD" . $ext_colour_off[($col - 0x10) / 0x50 + $bg * 3] . chr (($col - 0x10) % 0x50 - 1 + ord '0'); + } + } else { + return $c; + } +} + +sub get_thread_format { + my ($str) = @_; + my @nums = (0..9,'a'..'z'); + my $chr=join('',@nums); + my %nums = map { $nums[$_] => $_ } 0..$#nums; + my $n = 0; + $str = lc $str; + foreach ($str =~ /[$chr]/g) { + $n += $nums{$_} * 36; + } + my @colors = @thread_id_selected_colors; + my $color_count = @colors; + + # We have normal, bold, italic, underline + my $allow_bold = Irssi::settings_get_bool('matterircd_complete_thread_id_allow_bold'); + my $allow_italic = Irssi::settings_get_bool('matterircd_complete_thread_id_allow_italic'); + my $allow_underline = Irssi::settings_get_bool('matterircd_complete_thread_id_allow_underline'); + my @classes_prepend; + push @classes_prepend, "\x02" if $allow_bold; + push @classes_prepend, "\x1d" if $allow_italic; + push @classes_prepend, "\x1f" if $allow_underline; + my $classes = 1 + @classes_prepend; + $n = $n % $color_count*$classes; + my $random = $n; + my $prepend = ""; + if ($classes == 4 and $n >= $color_count*3) { + $n -= $color_count*3; + $prepend = $classes_prepend[2]; + } elsif ($classes ge 3 and $n >= $color_count*2) { + $n -= $color_count*2; + $prepend = $classes_prepend[1]; + } elsif ($classes ge 2 and $n >= $color_count) { + $n -= $color_count; + $prepend = $classes_prepend[0]; + } + $n = $colors[$n-1]; + return $n, $prepend; +} + +sub thread_color { + my ($str) = @_; + my ($n, $prepend) = get_thread_format($str); + # Pick the color in the allowed_color list. + # n should be comprised between 1 and the array length. + $n = xcolor_to_irssi($n); + $n = "$prepend\x03$n"; + return $n; +} +sub cmd_matterircd_complete_thread_id_get_color { + my ($data, $server, $wi) = @_; + my ($color, $prepend) = get_thread_format($_[0]); + my $n = xcolor_to_irssi($color); + _wi_print($wi, "Thread color for $prepend\x03$n$_[0]\x0f is $color"); +} +Irssi::command_bind('matterircd_complete_thread_id_get_color', 'cmd_matterircd_complete_thread_id_get_color'); + sub update_msgthreadid { my($server, $msg, $nick, $address, $target) = @_; - return unless settings_get_int('matterircd_complete_shorten_message_thread_id'); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + return unless Irssi::settings_get_int('matterircd_complete_shorten_message_thread_id'); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; my $prefix = ''; my $msgthreadid = ''; + my $msgpostid = ''; + my $reply_prefix = Irssi::settings_get_str('matterircd_complete_override_reply_prefix'); - if ($msg =~ s/\[(->|↪)?\@\@([0-9a-z]{26})\]/\@\@PLACEHOLDER\@\@/) { - $prefix = $1 ? $1 : ''; + if ($msg =~ s/\[(->|↪)?\@\@([0-9a-z]{26})(?:,\@\@([0-9a-z]{26}))?\]/\@\@PLACEHOLDER\@\@/) { + $prefix = $reply_prefix ? $reply_prefix : $1 if $1; $msgthreadid = $2; + $msgpostid = $3 ? $3 : ''; } return unless $msgthreadid; + my $thread_color = Irssi::settings_get_int('matterircd_complete_thread_id_color'); + if ($thread_color == -1) { + $thread_color = thread_color($msgthreadid); + } else { + $thread_color = "\x03${thread_color}"; + } + # Show that message is reply to a thread. (backwards compatibility # when matterircd doesn't show reply) if ((not $prefix) && ($msg =~ /\(re \@.*\)/)) { - $prefix = '↪'; + $prefix = $reply_prefix; } - if (not settings_get_bool('matterircd_complete_shorten_message_thread_id_hide_prefix')) { + if (not Irssi::settings_get_bool('matterircd_complete_shorten_message_thread_id_hide_prefix')) { $prefix = "${prefix}\@\@"; } - my $len = settings_get_int('matterircd_complete_shorten_message_thread_id'); + my $len = Irssi::settings_get_int('matterircd_complete_shorten_message_thread_id'); if ($len < 25) { # Shorten to length configured. We use unicode ellipsis (...) # here to both allow word selection to just select parts of # the message/thread ID when copying & pasting and save on # screen real estate. $msgthreadid = substr($msgthreadid, 0, $len) . '…'; + if ($msgpostid ne '') { + $msgpostid = substr($msgpostid, 0, $len) . '…'; + } + } + if ($msgpostid eq '') { + $msg =~ s/\@\@PLACEHOLDER\@\@/${thread_color}[${prefix}${msgthreadid}]\x0f/; + } else { + $msg =~ s/\@\@PLACEHOLDER\@\@/${thread_color}[${prefix}${msgthreadid},${msgpostid}]\x0f/; } - $msg =~ s/\@\@PLACEHOLDER\@\@/\x0314[${prefix}${msgthreadid}]\x0f/; - signal_continue($server, $msg, $nick, $address, $target); + Irssi::signal_continue($server, $msg, $nick, $address, $target); } -signal_add_last('message irc action', 'update_msgthreadid'); -signal_add_last('message irc notice', 'update_msgthreadid'); -signal_add_last('message private', 'update_msgthreadid'); -signal_add_last('message public', 'update_msgthreadid'); +Irssi::signal_add_last('message irc action', 'update_msgthreadid'); +Irssi::signal_add_last('message irc notice', 'update_msgthreadid'); +Irssi::signal_add_last('message private', 'update_msgthreadid'); +Irssi::signal_add_last('message public', 'update_msgthreadid'); sub cache_store { my ($cache_ref, $item, $cache_size) = @_; @@ -153,7 +300,7 @@ sub cache_store { return unless $item ne ''; my $changed = 0; - if (@$cache_ref[0] && @$cache_ref[0] eq $item) { + if ((@$cache_ref[0]) && (@$cache_ref[0] eq $item)) { return $changed; } $changed = 1; @@ -162,7 +309,7 @@ sub cache_store { # per-channel cache. But as a trade off in favor of # speed/performance, rather than traverse the entire per-channel # cache, we cap/limit it. - my $limit = 8; + my $limit = 16; my $max = ($#$cache_ref < $limit)? $#$cache_ref : $limit; for my $i (0 .. $max) { if ((@$cache_ref[$i]) && (@$cache_ref[$i] eq $item)) { @@ -187,44 +334,45 @@ sub cache_store { my %MSGTHREADID_CACHE; -settings_add_int('matterircd_complete', 'matterircd_complete_message_thread_id_cache_size', 50); -command_bind 'matterircd_complete_msgthreadid_cache_dump' => sub { +Irssi::settings_add_int('matterircd_complete', 'matterircd_complete_message_thread_id_cache_size', 50); +sub cmd_matterircd_complete_msgthreadid_cache_dump { my ($data, $server, $wi) = @_; if (not $data) { return unless ref $wi and ($wi->{type} eq 'CHANNEL' or $wi->{type} eq 'QUERY'); } - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; my $channel = $data ? $data : $wi->{name}; # Remove leading and trailing whitespace. $channel =~ tr/ //d; - Irssi::print("${channel}: Message/Thread ID cache"); + _wi_print($wi, "${channel}: Message/Thread ID cache"); if ((not exists($MSGTHREADID_CACHE{$channel})) || (scalar @{$MSGTHREADID_CACHE{$channel}} == 0)) { - Irssi::print("${channel}: Empty"); + _wi_print($wi, "${channel}: Empty"); return; } foreach my $msgthread_id (@{$MSGTHREADID_CACHE{$channel}}) { - Irssi::print("${channel}: ${msgthread_id}"); + _wi_print($wi, "${channel}: ${msgthread_id}"); } - Irssi::print("${channel}: Total: " . scalar @{$MSGTHREADID_CACHE{$channel}}); + _wi_print($wi, "${channel}: Total: " . scalar @{$MSGTHREADID_CACHE{$channel}}); }; +Irssi::command_bind('matterircd_complete_msgthreadid_cache_dump', 'cmd_matterircd_complete_msgthreadid_cache_dump'); my $MSGTHREADID_CACHE_SEARCH_ENABLED = 0; my $MSGTHREADID_CACHE_INDEX = 0; -command_bind 'message_thread_id_search' => sub { +sub cmd_message_thread_id_search { my ($data, $server, $wi) = @_; - return unless settings_get_int('matterircd_complete_message_thread_id_cache_size'); + return unless Irssi::settings_get_int('matterircd_complete_message_thread_id_cache_size'); return unless ref $wi and ($wi->{type} eq 'CHANNEL' or $wi->{type} eq 'QUERY'); return unless exists($MSGTHREADID_CACHE{$wi->{name}}); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; $MSGTHREADID_CACHE_SEARCH_ENABLED = 1; @@ -237,24 +385,25 @@ command_bind 'message_thread_id_search' => sub { if ($msgthreadid) { # Save input text. - my $input = parse_special('$L'); + my $input = Irssi::parse_special('$L'); # Remove existing thread. $input =~ s/^@@(?:[0-9a-z]{26}|[0-9a-f]{3}) //; # Insert message/thread ID from cache. - gui_input_set_pos(0); - gui_input_set("\@\@${msgthreadid} ${input}"); + Irssi::gui_input_set_pos(0); + Irssi::gui_input_set("\@\@${msgthreadid} ${input}"); } }; +Irssi::command_bind('message_thread_id_search', 'cmd_message_thread_id_search'); my $ESC_PRESSED = 0; my $O_PRESSED = 0; -signal_add_last 'gui key pressed' => sub { +sub signal_gui_key_pressed_msgthreadid { my ($key) = @_; return unless $MSGTHREADID_CACHE_SEARCH_ENABLED; my $server = Irssi::active_server(); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; if (($key == $KEY_RET) || ($key == $KEY_CTRL_U)) { @@ -267,14 +416,14 @@ signal_add_last 'gui key pressed' => sub { # Cancel/abort, so remove thread stuff. elsif ($key == $KEY_CTRL_C) { - my $input = parse_special('$L'); + my $input = Irssi::parse_special('$L'); # Remove the Ctrl+C character. $input =~ tr///d; my $pos = 0; if ($input =~ s/^(@@(?:[0-9a-z]{26}|[0-9a-f]{3}) )//) { - $pos = gui_input_get_pos() - length($1); + $pos = Irssi::gui_input_get_pos() - length($1); } # We also want to move the input position back one for Ctrl+C @@ -284,8 +433,8 @@ signal_add_last 'gui key pressed' => sub { # Replace the text in the input box with our modified version, # then move cursor positon to where it was without the # message/thread ID. - gui_input_set($input); - gui_input_set_pos($pos); + Irssi::gui_input_set($input); + Irssi::gui_input_set_pos($pos); $MSGTHREADID_CACHE_INDEX = 0; $MSGTHREADID_CACHE_SEARCH_ENABLED = 0; @@ -314,19 +463,20 @@ signal_add_last 'gui key pressed' => sub { $O_PRESSED = 0; } }; +Irssi::signal_add_last('gui key pressed', 'signal_gui_key_pressed_msgthreadid'); -signal_add_last 'complete word' => sub { +sub signal_complete_word_msgthread_id { my ($complist, $window, $word, $linestart, $want_space) = @_; # We only want to tab-complete message/thread if this is the first # word on the line. return if $linestart; - return unless settings_get_int('matterircd_complete_message_thread_id_cache_size'); + return unless Irssi::settings_get_int('matterircd_complete_message_thread_id_cache_size'); return if (substr($word, 0, 1) eq '@' and substr($word, 0, 2) ne '@@'); return unless $window->{active} and ($window->{active}->{type} eq 'CHANNEL' || $window->{active}->{type} eq 'QUERY'); return unless exists($MSGTHREADID_CACHE{$window->{active}->{name}}); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$window->{active_server}->{chatnet}}; if (substr($word, 0, 2) eq '@@') { @@ -339,20 +489,26 @@ signal_add_last 'complete word' => sub { } } }; +Irssi::signal_add_last('complete word', 'signal_complete_word_msgthread_id'); +my $MSGTHREADID_CACHE_STATS = 0; sub cache_msgthreadid { my($server, $msg, $nick, $address, $target) = @_; - return unless settings_get_int('matterircd_complete_message_thread_id_cache_size'); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + return unless Irssi::settings_get_int('matterircd_complete_message_thread_id_cache_size'); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; - my $msgid = ''; + my @msgids = (); - my @ignore_nicks = split(/\s+/, settings_get_str('matterircd_complete_nick_ignore')); + my @ignore_nicks = split(/\s+/, Irssi::settings_get_str('matterircd_complete_nick_ignore')); # Ignore nicks configured to be ignored such as bots. if ($nick ~~ @ignore_nicks) { - return; + # But not if the channel is in matterircd_complete_channel_dont_ignore. + my @channel_dont_ignore = split(/\s+/, Irssi::settings_get_str('matterircd_complete_channel_dont_ignore')); + if ($target !~ @channel_dont_ignore) { + return; + } } # We also want to ignore reactions as we can't reply to those @@ -362,16 +518,22 @@ sub cache_msgthreadid { } # Mattermost message/thread IDs. - if ($msg =~ /\[(?:->|↪)?\@\@([0-9a-z]{26})\]/) { - $msgid = $1; + if ($msg =~ /\[(?:->|↪)?\@\@([0-9a-z]{26})(?:,\@\@([0-9a-z]{26}))?\]/) { + my $msgthreadid = $1; + my $msgpostid = $2 ? $2 : ''; + + if ($msgpostid ne '') { + push(@msgids, $msgpostid); + } + push(@msgids, $msgthreadid); } # matterircd generated 3-letter hexadecimal. elsif ($msg =~ /(?:^\[([0-9a-f]{3})\])|(?:\[([0-9a-f]{3})\]\s*$)/) { - $msgid = $1 ? $1 : $2; + push(@msgids, $1 ? $1 : $2); } # matterircd generated 3-letter hexadecimal replying to threads. elsif ($msg =~ /(?:^\[[0-9a-f]{3}->([0-9a-f]{3})\])|(?:\[[0-9a-f]{3}->([0-9a-f]{3})\]\s*$)/) { - $msgid = $1 ? $1 : $2; + push(@msgids, $1 ? $1 : $2); } else { return; @@ -386,23 +548,26 @@ sub cache_msgthreadid { $key = $nick } - my $cache_size = settings_get_int('matterircd_complete_message_thread_id_cache_size'); - if (cache_store(\@{$MSGTHREADID_CACHE{$key}}, $msgid, $cache_size)) { - $MSGTHREADID_CACHE_INDEX = 0; + my $cache_size = Irssi::settings_get_int('matterircd_complete_message_thread_id_cache_size'); + for my $msgid (@msgids) { + if (cache_store(\@{$MSGTHREADID_CACHE{$key}}, $msgid, $cache_size)) { + $MSGTHREADID_CACHE_INDEX = 0; + stats_increment(\$MSGTHREADID_CACHE_STATS); + } } } -signal_add('message irc action', 'cache_msgthreadid'); -signal_add('message irc notice', 'cache_msgthreadid'); -signal_add('message private', 'cache_msgthreadid'); -signal_add('message public', 'cache_msgthreadid'); +Irssi::signal_add('message irc action', 'cache_msgthreadid'); +Irssi::signal_add('message irc notice', 'cache_msgthreadid'); +Irssi::signal_add('message private', 'cache_msgthreadid'); +Irssi::signal_add('message public', 'cache_msgthreadid'); -settings_add_bool('matterircd_complete', 'matterircd_complete_reply_msg_thread_id_at_start', 1); +Irssi::settings_add_bool('matterircd_complete', 'matterircd_complete_reply_msg_thread_id_at_start', 1); -signal_add_last 'message own_public' => sub { +sub signal_message_own_public_msgthreadid { my($server, $msg, $target) = @_; - return unless settings_get_int('matterircd_complete_message_thread_id_cache_size'); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + return unless Irssi::settings_get_int('matterircd_complete_message_thread_id_cache_size'); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; if ($msg !~ /^@@((?:[0-9a-z]{26})|(?:[0-9a-f]{3}))/) { @@ -410,14 +575,22 @@ signal_add_last 'message own_public' => sub { } my $msgid = $1; - my $cache_size = settings_get_int('matterircd_complete_message_thread_id_cache_size'); + my $cache_size = Irssi::settings_get_int('matterircd_complete_message_thread_id_cache_size'); if (cache_store(\@{$MSGTHREADID_CACHE{$target}}, $msgid, $cache_size)) { $MSGTHREADID_CACHE_INDEX = 0; + stats_increment(\$MSGTHREADID_CACHE_STATS); } my $msgthreadid = $1; - my $len = settings_get_int('matterircd_complete_shorten_message_thread_id'); + my $thread_color = Irssi::settings_get_int('matterircd_complete_thread_id_color'); + if ($thread_color == -1) { + $thread_color = thread_color($msgthreadid); + } else { + $thread_color = "\x03${thread_color}"; + } + + my $len = Irssi::settings_get_int('matterircd_complete_shorten_message_thread_id'); if ($len < 25) { # Shorten to length configured. We use unicode ellipsis (...) # here to both allow word selection to just select parts of @@ -426,21 +599,23 @@ signal_add_last 'message own_public' => sub { $msgthreadid = substr($msgid, 0, $len) . "…"; } - if (settings_get_bool('matterircd_complete_reply_msg_thread_id_at_start')) { - $msg =~ s/^@@[0-9a-z]{26} /\x0314[↪${msgthreadid}]\x0f /; + my $reply_prefix = Irssi::settings_get_str('matterircd_complete_override_reply_prefix'); + if (Irssi::settings_get_bool('matterircd_complete_reply_msg_thread_id_at_start')) { + $msg =~ s/^@@[0-9a-z]{26} /${thread_color}[${reply_prefix}${msgthreadid}]\x0f /; } else { $msg =~ s/^@@[0-9a-z]{26} //; - $msg =~ s/$/ \x0314[↪${msgthreadid}]\x0f/; + $msg =~ s/$/ ${thread_color}[${reply_prefix}${msgthreadid}]\x0f/; } - signal_continue($server, $msg, $target); + Irssi::signal_continue($server, $msg, $target); }; +Irssi::signal_add_last('message own_public', 'signal_message_own_public_msgthreadid'); -signal_add_last 'message own_private' => sub { +sub signal_message_own_private { my($server, $msg, $target, $orig_target) = @_; - return unless settings_get_int('matterircd_complete_message_thread_id_cache_size'); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + return unless Irssi::settings_get_int('matterircd_complete_message_thread_id_cache_size'); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; if ($msg !~ /^@@((?:[0-9a-z]{26})|(?:[0-9a-f]{3}))/) { @@ -448,14 +623,22 @@ signal_add_last 'message own_private' => sub { } my $msgid = $1; - my $cache_size = settings_get_int('matterircd_complete_message_thread_id_cache_size'); + my $cache_size = Irssi::settings_get_int('matterircd_complete_message_thread_id_cache_size'); if (cache_store(\@{$MSGTHREADID_CACHE{$target}}, $msgid, $cache_size)) { $MSGTHREADID_CACHE_INDEX = 0; + stats_increment(\$MSGTHREADID_CACHE_STATS); } my $msgthreadid = $1; - my $len = settings_get_int('matterircd_complete_shorten_message_thread_id'); + my $thread_color = Irssi::settings_get_int('matterircd_complete_thread_id_color'); + if ($thread_color == -1) { + $thread_color = thread_color($msgthreadid); + } else { + $thread_color = "\x03${thread_color}"; + } + + my $len = Irssi::settings_get_int('matterircd_complete_shorten_message_thread_id'); if ($len < 25) { # Shorten to length configured. We use unicode ellipsis (...) # here to both allow word selection to just select parts of @@ -464,15 +647,17 @@ signal_add_last 'message own_private' => sub { $msgthreadid = substr($msgid, 0, $len) . "…"; } - if (settings_get_bool('matterircd_complete_reply_msg_thread_id_at_start')) { - $msg =~ s/^@@[0-9a-z]{26} /\x0314[↪${msgthreadid}]\x0f /; + my $reply_prefix = Irssi::settings_get_str('matterircd_complete_override_reply_prefix'); + if (Irssi::settings_get_bool('matterircd_complete_reply_msg_thread_id_at_start')) { + $msg =~ s/^@@[0-9a-z]{26} /${thread_color}[${reply_prefix}${msgthreadid}]\x0f /; } else { $msg =~ s/^@@[0-9a-z]{26} //; - $msg =~ s/$/ \x0314[↪${msgthreadid}]\x0f/; + $msg =~ s/$/ ${thread_color}[${reply_prefix}${msgthreadid}]\x0f/; } - signal_continue($server, $msg, $target, $orig_target); + Irssi::signal_continue($server, $msg, $target, $orig_target); }; +Irssi::signal_add_last('message own_private', 'signal_message_own_private'); #============================================================================== @@ -483,49 +668,50 @@ signal_add_last 'message own_private' => sub { my %NICKNAMES_CACHE; -settings_add_int('matterircd_complete', 'matterircd_complete_nick_cache_size', 20); -command_bind 'matterircd_complete_nick_cache_dump' => sub { +Irssi::settings_add_int('matterircd_complete', 'matterircd_complete_nick_cache_size', 20); +sub cmd_matterircd_complete_nick_cache_dump { my ($data, $server, $wi) = @_; if (not $data) { - return unless ref $wi and $wi->{type} eq 'CHANNEL'; + return unless ref $wi and ($wi->{type} eq 'CHANNEL' or $wi->{type} eq 'QUERY'); } - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; my $channel = $data ? $data : $wi->{name}; # Remove leading and trailing whitespace. $channel =~ tr/ //d; - Irssi::print("${channel}: Nicknames cache"); + _wi_print($wi, "${channel}: Nicknames cache"); if ((not exists($NICKNAMES_CACHE{$channel})) || (scalar @{$NICKNAMES_CACHE{$channel}} == 0)) { - Irssi::print("${channel}: Empty"); + _wi_print($wi,"${channel}: Empty"); return; } foreach my $nick (@{$NICKNAMES_CACHE{$channel}}) { - Irssi::print("${channel}: ${nick}"); + _wi_print($wi, "${channel}: ${nick}"); } - Irssi::print("${channel}: Total: " . scalar @{$NICKNAMES_CACHE{$channel}}); + _wi_print($wi, "${channel}: Total: " . scalar @{$NICKNAMES_CACHE{$channel}}); }; +Irssi::command_bind('matterircd_complete_nick_cache_dump', 'cmd_matterircd_complete_nick_cache_dump'); -signal_add 'complete word' => sub { +sub signal_complete_word_nicks { my ($complist, $window, $word, $linestart, $want_space) = @_; return if substr($word, 0, 2) eq '@@'; return unless $window->{active} and $window->{active}->{type} eq 'CHANNEL'; - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$window->{active_server}->{chatnet}}; if (substr($word, 0, 1) eq '@') { $word = substr($word, 1); } - my $compl_char = settings_get_str('completion_char'); + my $compl_char = Irssi::settings_get_str('completion_char'); my $own_nick = $window->{active}->{ownnick}->{nick}; - my @ignore_nicks = split(/\s+/, settings_get_str('matterircd_complete_nick_ignore')); + my @ignore_nicks = split(/\s+/, Irssi::settings_get_str('matterircd_complete_nick_ignore')); # We need to store the results in a temporary array so we can # sort. @@ -585,30 +771,34 @@ signal_add 'complete word' => sub { } } }; +Irssi::signal_add('complete word', 'signal_complete_word_nicks'); +my $NICKNAMES_CACHE_STATS = 0; sub cache_ircnick { my($server, $msg, $nick, $address, $target) = @_; - return unless settings_get_int('matterircd_complete_nick_cache_size'); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + return unless Irssi::settings_get_int('matterircd_complete_nick_cache_size'); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; - my $cache_size = settings_get_int('matterircd_complete_nick_cache_size'); - my @ignore_nicks = split(/\s+/, settings_get_str('matterircd_complete_nick_ignore')); + my $cache_size = Irssi::settings_get_int('matterircd_complete_nick_cache_size'); + my @ignore_nicks = split(/\s+/, Irssi::settings_get_str('matterircd_complete_nick_ignore')); # Ignore nicks configured to be ignored such as bots. if ($nick !~ @ignore_nicks) { - cache_store(\@{$NICKNAMES_CACHE{$target}}, $nick, $cache_size); + if (cache_store(\@{$NICKNAMES_CACHE{$target}}, $nick, $cache_size)) { + stats_increment(\$NICKNAMES_CACHE_STATS); + } } } -signal_add('message irc action', 'cache_ircnick'); -signal_add('message irc notice', 'cache_ircnick'); -signal_add('message public', 'cache_ircnick'); +Irssi::signal_add('message irc action', 'cache_ircnick'); +Irssi::signal_add('message irc notice', 'cache_ircnick'); +Irssi::signal_add('message public', 'cache_ircnick'); -signal_add_last 'message own_public' => sub { +sub signal_message_own_public_nicks { my($server, $msg, $target) = @_; - return unless settings_get_int('matterircd_complete_nick_cache_size'); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + return unless Irssi::settings_get_int('matterircd_complete_nick_cache_size'); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; if ($msg !~ /^@([^@ \t:,\)]+)/) { @@ -616,34 +806,37 @@ signal_add_last 'message own_public' => sub { } my $nick = $1; - my $cache_size = settings_get_int('matterircd_complete_nick_cache_size'); + my $cache_size = Irssi::settings_get_int('matterircd_complete_nick_cache_size'); # We want to make sure that the nick or user is still online and # in the channel. - my $wi = Irssi::active_win()->{active}; + my $wi = $server->window_item_find($target); if (not defined $wi) { return; } foreach my $cur ($wi->nicks()) { if ($nick eq $cur->{nick}) { - cache_store(\@{$NICKNAMES_CACHE{$target}}, $nick, $cache_size); + if (cache_store(\@{$NICKNAMES_CACHE{$target}}, $nick, $cache_size, 1)) { + stats_increment(\$NICKNAMES_CACHE_STATS); + } last; } } }; +Irssi::signal_add_last('message own_public', 'signal_message_own_public_nicks'); my @NICKNAMES_CACHE_SEARCH; my $NICKNAMES_CACHE_SEARCH_ENABLED = 0; my $NICKNAMES_CACHE_INDEX = 0; -command_bind 'nicknames_search' => sub { +sub cmd_nicknames_search { my ($data, $server, $wi) = @_; return unless ref $wi and $wi->{type} eq 'CHANNEL'; - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; my $own_nick = $wi->{ownnick}->{nick}; - my @ignore_nicks = split(/\s+/, settings_get_str('matterircd_complete_nick_ignore')); + my @ignore_nicks = split(/\s+/, Irssi::settings_get_str('matterircd_complete_nick_ignore')); @NICKNAMES_CACHE_SEARCH = (); foreach my $cur ($wi->nicks()) { @@ -689,26 +882,27 @@ command_bind 'nicknames_search' => sub { if ($nickname) { # Save input text. - my $input = parse_special('$L'); - my $compl_char = settings_get_str('completion_char'); + my $input = Irssi::parse_special('$L'); + my $compl_char = Irssi::settings_get_str('completion_char'); # Remove any existing nickname and insert one from the cache. my $msgid = ""; if ($input =~ s/^(\@\@(?:[0-9a-z]{26}|[0-9a-f]{3}) )//) { $msgid = $1; } $input =~ s/^\@[^${compl_char}]+$compl_char //; - gui_input_set_pos(0); - gui_input_set("${msgid}\@${nickname}${compl_char} ${input}"); + Irssi::gui_input_set_pos(0); + Irssi::gui_input_set("${msgid}\@${nickname}${compl_char} ${input}"); } }; +Irssi::command_bind('nicknames_search', 'cmd_nicknames_search'); -signal_add_last 'gui key pressed' => sub { +sub signal_gui_key_pressed_nicks { my ($key) = @_; return unless $NICKNAMES_CACHE_SEARCH_ENABLED; my $server = Irssi::active_server(); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; if (($key == $KEY_RET) || ($key == $KEY_CTRL_U)) { @@ -719,15 +913,15 @@ signal_add_last 'gui key pressed' => sub { # Cancel/abort, so remove current nickname. elsif ($key == $KEY_CTRL_C) { - my $input = parse_special('$L'); + my $input = Irssi::parse_special('$L'); # Remove the Ctrl+C character. $input =~ tr///d; - my $compl_char = settings_get_str('completion_char'); + my $compl_char = Irssi::settings_get_str('completion_char'); my $pos = 0; if ($input =~ s/^(\@[^${compl_char}]+$compl_char )//) { - $pos = gui_input_get_pos() - length($1); + $pos = Irssi::gui_input_get_pos() - length($1); } # We also want to move the input position back one for Ctrl+C @@ -737,14 +931,15 @@ signal_add_last 'gui key pressed' => sub { # Replace the text in the input box with our modified version, # then move cursor positon to where it was without the # current nickname. - gui_input_set($input); - gui_input_set_pos($pos); + Irssi::gui_input_set($input); + Irssi::gui_input_set_pos($pos); $NICKNAMES_CACHE_INDEX = 0; $NICKNAMES_CACHE_SEARCH_ENABLED = 0; @NICKNAMES_CACHE_SEARCH = (); } }; +Irssi::signal_add_last('gui key pressed', 'signal_gui_key_pressed_nicks'); #============================================================================== @@ -755,39 +950,115 @@ signal_add_last 'gui key pressed' => sub { my %REPLIED_CACHE; -settings_add_int('matterircd_complete', 'matterircd_complete_replied_cache_size', 50); -command_bind 'matterircd_complete_replied_cache_dump' => sub { +Irssi::settings_add_int('matterircd_complete', 'matterircd_complete_replied_cache_size', 50); +sub cmd_matterircd_complete_replied_cache_dump { my ($data, $server, $wi) = @_; if (not $data) { - return unless ref $wi and $wi->{type} eq 'CHANNEL'; + return unless ref $wi and ($wi->{type} eq 'CHANNEL' or $wi->{type} eq 'QUERY'); } - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; my $channel = $data ? $data : $wi->{name}; # Remove leading and trailing whitespace. $channel =~ tr/ //d; - Irssi::print("${channel}: Replied cache"); + _wi_print($wi, "${channel}: Replied cache"); if ((not exists($REPLIED_CACHE{$channel})) || (scalar @{$REPLIED_CACHE{$channel}} == 0)) { - Irssi::print("${channel}: Empty"); + _wi_print($wi, "${channel}: Empty"); return; } foreach my $threadid (@{$REPLIED_CACHE{$channel}}) { - Irssi::print("${channel}: ${threadid}"); + _wi_print($wi, "${channel}: ${threadid}"); + } + _wi_print($wi, "${channel}: Total: " . scalar @{$REPLIED_CACHE{$channel}}); +}; +Irssi::command_bind('matterircd_complete_replied_cache_dump', 'cmd_matterircd_complete_replied_cache_dump'); + +my $REPLIED_CACHE_STATS = 0; +sub cmd_matterircd_complete_replied_cache_clear { + my ($data, $server, $wi) = @_; + + my $channel; + my @msgids = (); + my @args = (); + if ($data) { + @args = split(/\s+/, $data); + } + + if (scalar(@args) == 0 || $args[0] eq '*') { + stats_increment(\$REPLIED_CACHE_STATS); + _wi_print($wi, "matterircd_complete replied cache cleared"); + return; + } + + if (exists($REPLIED_CACHE{$args[0]}) || exists($REPLIED_CACHE{"#${args[0]}"})) { + $channel = shift(@args); + if (rindex($channel, "#", 0) == -1) { + $channel = "#${channel}"; + } + } elsif ($wi->{name}) { + $channel = $wi->{name}; + } else { + return; + } + @msgids = @args; + + if (scalar(@msgids) > 0) { + foreach my $id (@msgids) { + my $i = 0; + if (rindex($id, "@@", 0) == 0) { + $id = substr($id, 2); + } + foreach my $msgid (@{$REPLIED_CACHE{$channel}}) { + if ($id eq $msgid) { + splice(@{$REPLIED_CACHE{$channel}}, $i, 1); + stats_increment(\$REPLIED_CACHE_STATS); + _wi_print($wi, "matterircd_complete replied cache removed ${id} from ${channel} cache"); + last; + } + $i += 1; + } + } + } else { + @{$REPLIED_CACHE{$channel}} = (); + stats_increment(\$REPLIED_CACHE_STATS); + _wi_print($wi, "matterircd_complete replied cache cleared for channel ${channel}"); + } +}; +Irssi::command_bind('matterircd_complete_replied_cache_clear', 'cmd_matterircd_complete_replied_cache_clear'); + +my $REPLIED_CACHE_CLEARED = 0; +Irssi::settings_add_bool('matterircd_complete', 'matterircd_complete_clear_replied_cache_on_away', 0); +sub signal_away_mode_changed { + my ($server) = @_; + + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); + return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; + + # When you visit the web UI when marked away, it retriggers this + # event. Let's avoid that. + if (! $server->{usermode_away}) { + $REPLIED_CACHE_CLEARED = 0; + } + + if (Irssi::settings_get_bool('matterircd_complete_clear_replied_cache_on_away') && $server->{usermode_away} && (! $REPLIED_CACHE_CLEARED)) { + %REPLIED_CACHE = (); + $REPLIED_CACHE_CLEARED = 1; + Irssi::print("matterircd_complete replied cache cleared"); } - Irssi::print("${channel}: Total: " . scalar @{$REPLIED_CACHE{$channel}}); }; +Irssi::signal_add('away mode changed', 'signal_away_mode_changed'); -signal_add 'message own_public' => sub { +sub signal_message_own_public_replied { my($server, $msg, $target) = @_; - return unless settings_get_int('matterircd_complete_replied_cache_size'); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + return unless Irssi::settings_get_int('matterircd_complete_replied_cache_size'); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; if ($msg !~ /^@@((?:[0-9a-z]{26})|(?:[0-9a-f]{3}))/) { @@ -795,20 +1066,23 @@ signal_add 'message own_public' => sub { } my $msgid = $1; - my $cache_size = settings_get_int('matterircd_complete_replied_cache_size'); - cache_store(\@{$REPLIED_CACHE{$target}}, $msgid, $cache_size); + my $cache_size = Irssi::settings_get_int('matterircd_complete_replied_cache_size'); + if (cache_store(\@{$REPLIED_CACHE{$target}}, $msgid, $cache_size)) { + stats_increment(\$REPLIED_CACHE_STATS); + } }; +Irssi::signal_add('message own_public', 'signal_message_own_public_replied'); -signal_add 'message public' => sub { +sub signal_message_public { my($server, $msg, $nick, $address, $target) = @_; - return unless settings_get_int('matterircd_complete_replied_cache_size'); - my %chatnets = map { $_ => 1 } split(/\s+/, settings_get_str('matterircd_complete_networks')); + return unless Irssi::settings_get_int('matterircd_complete_replied_cache_size'); + my %chatnets = map { $_ => 1 } split(/\s+/, Irssi::settings_get_str('matterircd_complete_networks')); return unless exists $chatnets{'*'} || exists $chatnets{$server->{chatnet}}; # For '/me' actions, it has trailing space so we need to use # \s* here. - $msg =~ /\[(?:->|↪)?\@\@([0-9a-z]{26})\]/; + $msg =~ /\[(?:->|↪)?\@\@([0-9a-z]{26})[\],]/; my $msgthreadid = $1; return unless $msgthreadid; @@ -820,5 +1094,246 @@ signal_add 'message public' => sub { } } - signal_continue($server, $msg, $nick, $address, $target); + Irssi::signal_continue($server, $msg, $nick, $address, $target); }; +Irssi::signal_add('message public', 'signal_message_public'); + +# Remove an array's elements per their values +sub array_splice_values { + my ($ar_ref, $uw_ref) = @_; + my @array = @{$ar_ref}; + my @unwanted = @{$uw_ref}; + my %removals = map { $_ => 1 } @unwanted; + my @keys = keys %removals; + my @indices = grep { exists($removals{$array[$_]}) } 0..$#array; + # Each time we remove index from @arr, the next correct index to delete will be reduced of $o + my $o = 0; + for (@indices) { + splice(@array, $_-$o, 1); + $o++; + } + return @array; +} + +sub setup_colors { + # Skip colors setup if we're using a fixed color + my $fixed_color = Irssi::settings_get_int('matterircd_complete_thread_id_color'); + return if $fixed_color ne -1; + + my $allowed_colors = Irssi::settings_get_str('matterircd_complete_thread_id_allowed_colors'); + $allowed_colors = uc $allowed_colors; + my $unwanted_colors = Irssi::settings_get_str('matterircd_complete_thread_id_unwanted_colors'); + $unwanted_colors = uc $unwanted_colors; + my $color_theme = Irssi::settings_get_str('matterircd_complete_thread_id_color_theme'); + $color_theme = lc $color_theme; + my @colors; + + if ($allowed_colors =~ /^[0-9A-Z]{2}( [0-9A-Z]{2})*$/) { + @colors = split(' ', $allowed_colors); + Irssi::print("[matterircd_complete] Setting allowed colors: @colors"); + } elsif ($allowed_colors =~ /^[0-9A-Z]{2}([0-9A-Z]{2})*$/ and length($allowed_colors) % 2 == 0) { + @colors = ( $allowed_colors =~ m/../g ); + Irssi::print("[matterircd_complete] Setting allowed colors: @colors"); + } elsif (length($allowed_colors) != 0) { + Irssi::print("[matterircd_complete] Ignoring matterircd_complete_thread_id_allowed_colors: invalid format ($allowed_colors)"); + } else { + Irssi::print("[matterircd_complete] Setting allowed colors to all colors"); + @colors = @all_colors; + } + + if (length($color_theme) != 0) { + if ($color_theme eq "dark") { + Irssi::print("[matterircd_complete] Removing colors incompatible with dark theme"); + @colors = array_splice_values(\@colors, \@dark_theme_unwanted); + } elsif ($color_theme eq 'solarized-light') { + Irssi::print("[matterircd_complete] Removing colors incompatible with solarized-light theme"); + @colors = array_splice_values(\@colors, \@solarized_light_theme_unwanted); + } else { + Irssi::print("[matterircd_complete] Ignoring unknown color theme $color_theme"); + Irssi::print("[matterircd_complete] Valid themes are dark, solarized-light"); + } + } + + if ($unwanted_colors =~ /^[0-9A-Z]{2}( [0-9A-Z]{2})*$/) { + my @unwanted = split(' ', $unwanted_colors); + Irssi::print("[matterircd_complete] Removing unwanted colors"); + @colors = array_splice_values(\@colors, \@unwanted); + } elsif ($unwanted_colors =~ /^[0-9A-Z]{2}([0-9A-Z]{2})*$/ and length($unwanted_colors) % 2 == 0) { + my @unwanted = ($unwanted_colors =~ m/../g); + Irssi::print("[matterircd_complete] Removing unwanted colors"); + @colors = array_splice_values(\@colors, \@unwanted); + } elsif (length($unwanted_colors)) { + Irssi::print("[matterircd_complete] Ignoring matterircd_complete_thread_id_unwanted_colors: invalid format ($unwanted_colors)"); + } + + if (@thread_id_selected_colors) { + Irssi::print("[matterircd_complete] Config changed, existing threads might change colors!") + if $allowed_colors ne $color_config{"allowed_colors"} + or $unwanted_colors ne $color_config{"unwanted_colors"} + or $color_theme ne $color_config{"color_theme"}; + $color_config{"allowed_colors"} = $allowed_colors; + $color_config{"unwanted_colors"} = $unwanted_colors; + $color_config{"color_theme"} = $color_theme; + } else { + Irssi::print("[matterircd_complete] Thread colors have been set per your config"); + } + Irssi::print("[matterircd_complete] You can check colors in use with /matterircd_complete_thread_id_get_colors"); + @thread_id_selected_colors = @colors; +} +Irssi::signal_add('setup changed', 'setup_colors'); +Irssi::signal_add('setup reread', 'setup_colors'); + +sub cmd_matterircd_complete_thread_id_get_colors { + my ($data, $server, $wi) = @_; + + # Display a warning if we're using a fixed color + my $fixed_color = Irssi::settings_get_int('matterircd_complete_thread_id_color'); + if ($fixed_color ne -1) { + _wi_print($wi, "Thread_id_color is not set to -1"); + _wi_print($wi, "Threads will always take \x03${fixed_color}this color\x0f"); + return; + } + + my $colors_text = "Selected colors: "; + foreach (@thread_id_selected_colors) { + my $n = xcolor_to_irssi($_); + $colors_text .= "\x03$n$_"; + } + $colors_text .= "\x0f"; + _wi_print($wi, $colors_text); +} +Irssi::command_bind('matterircd_complete_thread_id_get_colors', 'cmd_matterircd_complete_thread_id_get_colors'); + +Irssi::settings_add_bool('matterircd_complete', 'matterircd_complete_stats_output', 0); +sub stats_increment { + my ($stats_ref) = @_; + + $$stats_ref += 1; + + # autosave. + if (($$stats_ref % 100) == 0) { + my $output = Irssi::settings_get_bool('matterircd_complete_stats_output'); + save_cache($output); + } +} + +my $STARTUP_DATE = localtime(); +sub stats_show { + Irssi::print("[matterircd_complete] Started / loaded since ${STARTUP_DATE}"); + + my $total = 0; + my $entries; + my $channels; + + my %cache = ( + 'MSGTHREADID' => \%MSGTHREADID_CACHE, + 'NICKNAMES' => \%NICKNAMES_CACHE, + 'REPLIED' => \%REPLIED_CACHE, + ); + + my %stats = ( + 'MSGTHREADID' => 0, + 'NICKNAMES' => 0, + 'REPLIED' => 0, + ); + foreach my $key (sort keys %cache) { + foreach my $channel (sort keys %{$cache{$key}}) { + my $d = $cache{$key}->{$channel}; + if (scalar(@{$d}) == 0) { + next; + } + $stats{$key} += scalar(@{$d}); + } + $total += $stats{$key}; + } + + $entries = $stats{'MSGTHREADID'}; + $channels = keys %{$cache{'MSGTHREADID'}}; + Irssi::print("[matterircd_complete] ${entries} entries across ${channels} channels for msg/thread IDs cache (${MSGTHREADID_CACHE_STATS} updates)"); + + $entries = $stats{'NICKNAMES'}; + $channels = keys %{$cache{'NICKNAMES'}}; + Irssi::print("[matterircd_complete] ${entries} entries across ${channels} channels for nicknames cache (${NICKNAMES_CACHE_STATS} updates)"); + + $entries = $stats{'REPLIED'}; + $channels = keys %{$cache{'REPLIED'}}; + Irssi::print("[matterircd_complete] ${entries} entries across ${channels} channels for threads replied to cache (${REPLIED_CACHE_STATS} updates)"); + + my $total_updates = $MSGTHREADID_CACHE_STATS + $NICKNAMES_CACHE_STATS + $REPLIED_CACHE_STATS; + Irssi::print("[matterircd_complete] \x03%GSaved total of ${total} entries in the cache (${total_updates} total updates)…"); +} +Irssi::command_bind('matterircd_complete_stats', 'stats_show'); + +my $CACHE_FILE = Irssi::get_irssi_dir() . '/matterircd_complete.cache'; +my $exited; +sub save_cache { + my ($output_stats) = @_; + + open(FH, '>', $CACHE_FILE) or do { + Irssi::print("[matterircd_complete] \x03%RError saving matterircd_complete cache: $!") + unless $exited; + return; + }; + + my %cache = ( + 'MSGTHREADID' => \%MSGTHREADID_CACHE, + 'NICKNAMES' => \%NICKNAMES_CACHE, + 'REPLIED' => \%REPLIED_CACHE, + ); + + foreach my $key (sort keys %cache) { + foreach my $channel (sort keys %{$cache{$key}}) { + my $d = $cache{$key}->{$channel}; + my $entries = join(',', @{$d}); + if (scalar(@{$d}) == 0) { + next; + } + print(FH "${key} ${channel} ${entries}\n"); + } + } + close(FH); + + if ($output_stats == 0) { + return; + } + + stats_show(); +} +Irssi::command_bind('matterircd_complete_cache_save', 'save_cache'); + +sub load_cache { + open(FH, '<', $CACHE_FILE) or return; + + my %cache = ( + 'MSGTHREADID' => \%MSGTHREADID_CACHE, + 'NICKNAMES' => \%NICKNAMES_CACHE, + 'REPLIED' => \%REPLIED_CACHE, + ); + + my $total = 0; + while() { + chomp; + my ($key, $channel, $entries) = split; + my @d = split(',', $entries); + $cache{$key}->{$channel} = \@d; + $total += scalar(@d); + } + close(FH); + + Irssi::print("[matterircd_complete] \x03%GLoaded total of ${total} entries from disk cache…"); +} + +sub UNLOAD { + return if $exited; + exit_save(); +} + +sub exit_save { + $exited = 1; + save_cache(1) +} +Irssi::signal_add('gui exit', 'exit_save'); + +# Set up on load! +setup_colors(); +load_cache();