use Irssi; use strict; use vars qw($VERSION %IRSSI); $VERSION="0.3.10"; %IRSSI = ( authors=> 'BC-bd', contact=> 'bd@bc-bd.org', name=> 'nm', description=> 'right aligned nicks depending on longest nick', license=> 'GPL v2', url=> 'http://bc-bd.org/blog/irssi/', ); # $Id: 9cb009e8b7e6f5ce60294334faf88715ef01413e $ # nm.pl # for irssi 0.8.4 by bd@bc-bd.org # # right aligned nicks depending on longest nick # # inspired by neatmsg.pl from kodgehopper for some hints # thanks to Eric Wald for the left alignment patch # inspired by nickcolor.pl by Timo Sirainen and Ian Peters # thanks to And1 for a small patch # thanks to berber@tzi.de for the save/load patch # thanks to Dennis Heimbert for a bug report/patch # thanks to Roy Sigurd Karlsbakk for an autosave patch # ######### # USAGE ### # # use # # /neatcolor help # # for help on available commands # ######### # OPTIONS ######### my $help = " /set neat_colorize * ON : colorize nicks * OFF : do not colorize nicks /set neat_colors Use these colors when colorizing nicks, eg: /set neat_colors yYrR See the file formats.txt on an explanation of what colors are available. /set neat_left_actions * ON : print nicks left-aligned on actions * OFF : print nicks right-aligned on actions /set neat_left_messages * ON : print nicks left-aligned on messages * OFF : print nicks right-aligned on messages /set neat_right_mode * ON : print the mode of the nick e.g @%+ after the nick * OFF : print it left of the nick /set neat_maxlength * number : Maximum length of Nicks to display. Longer nicks are truncated. * 0 : Do not truncate nicks. /set neat_melength * number : number of spaces to substract from /me padding /set neat_ignorechars * str : regular expression used to filter out unwanted characters in nicks. this can be used to assign the same color for similar nicks, e.g. foo and foo_: /set neat_ignorechars [_] /set neat_allow_shrinking * ON : shrink padding when longest nick disappears * OFF : do not shrink, only allow growing /set neat_autosave * number : autosave after seconds, defaults to 60. Set to 0 to disable. "; # ### ################ ### # # Changelog # # Version 0.3.11 # - added autosave, idea from Roy Sigurd Karlsbakk # # Version 0.3.10 # - fix losing of saved color when changing nick shares more than one channel # with you # # Version 0.3.9 # - fix longest nick calculation for nicks shorter than the current longest # nick # - updated url # # Version 0.3.8 # - fixed error in the nickchange tracking code, reported by Kevin Ballard # - added --all switch to reset command # - skip broken lines in saved_colors # # Version 0.3.7 # - fixed crash when calling /neatcolor without parameters # - fixed url # # Version 0.3.6 # - added option to ignore certain characters from color hash building, see # https://bc-bd.org/trac/irssi/ticket/22 # - added option to save and specify colors for nicks, see # https://bc-bd.org/trac/irssi/ticket/23 # - added option to disallow shrinking, see # https://bc-bd.org/trac/irssi/ticket/12 # # Version 0.3.5 # - now also aligning own messages in queries # # Version 0.3.4 # - fxed off by one error in nick_to_color, patch by jrib, see # https://bc-bd.org/trac/irssi/ticket/24 # # Version 0.3.3 # - added support for alignment in queries, see # https://bc-bd.org/trac/irssi/ticket/21 # # Version 0.3.2 # - integrated left alignment patch from Eric Wald , see # https://bc-bd.org/trac/irssi/ticket/18 # # Version 0.3.1 # - /me padding, see https://bc-bd.org/trac/irssi/ticket/17 # # Version 0.3.0 # - integrate nick coloring support # # Version 0.2.1 # - moved neat_maxlength check to reformat() (thx to Jerome De Greef ) # # Version 0.2.0 # - by adrianel # * reformat after setup reload # * maximum length of nicks # # Version 0.1.0 # - got lost somewhere # # Version 0.0.2 # - ugly typo fixed # # Version 0.0.1 # - initial release # ### ################ ### # # BUGS # # Empty nicks, eg "<> message" # This seems to be triggered by some themes. As of now there is no known # fix other than changing themes, see # https://bc-bd.org/trac/irssi/ticket/19 # # Well, it's a feature: due to the lacking support of extendable themes # from irssi it is not possible to just change some formats per window. # This means that right now all windows are aligned with the same nick # length, which can be somewhat annoying. # If irssi supports extendable themes, I will include per-server indenting # and a setting where you can specify servers you don't want to be indented # ### ################ my ($longestNick, %saved_colors, @colors, $alignment, $sign, %commands,); my ($pending_save); my $colorize = -1; sub reformat() { my $max = Irssi::settings_get_int('neat_maxlength'); my $actsign = Irssi::settings_get_bool('neat_left_actions')? '': '-'; $sign = Irssi::settings_get_bool('neat_left_messages')? '': '-'; if ($max && $max < $longestNick) { $longestNick = $max; } my $me = $longestNick - Irssi::settings_get_int('neat_melength'); $me = 0 if ($me < 0); Irssi::command('^format own_action {ownaction $['.$actsign.$me.']0} $1'); Irssi::command('^format action_public {pubaction $['.$actsign.$me.']0}$1'); Irssi::command('^format action_private {pvtaction $['.$actsign.$me.']0}$1'); Irssi::command('^format action_private_query {pvtaction_query $['.$actsign.$me.']0} $2'); my $length = $sign . $longestNick; if (Irssi::settings_get_bool('neat_right_mode') == 0) { Irssi::command('^format own_msg {ownmsgnick $2 {ownnick $['.$length.']0}}$1'); Irssi::command('^format own_msg_channel {ownmsgnick $3 {ownnick $['.$length.']0}{msgchannel $1}}$2'); Irssi::command('^format pubmsg_me {pubmsgmenick $2 {menick $['.$length.']0}}$1'); Irssi::command('^format pubmsg_me_channel {pubmsgmenick $3 {menick $['.$length.']0}{msgchannel $1}}$2'); Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $3 $['.$length.']1%n}$2'); Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $4 $['.$length.']1{msgchannel $2}}$3'); Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick $['.$length.']0}}$1'); Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick $['.$length.']0}}$1'); } else { Irssi::command('^format own_msg {ownmsgnick {ownnick $['.$length.']0$2}}$1'); Irssi::command('^format own_msg_channel {ownmsgnick {ownnick $['.$length.']0$3}{msgchannel $1}}$2'); Irssi::command('^format pubmsg_me {pubmsgmenick {menick $['.$length.']0}$2}$1'); Irssi::command('^format pubmsg_me_channel {pubmsgmenick {menick $['.$length.']0$3}{msgchannel $1}}$2'); Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $0 $['.$length.']1$3%n}$2'); Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $['.$length.']1$4{msgchannel $2}}$3'); Irssi::command('^format pubmsg {pubmsgnick {pubnick $['.$length.']0$2}}$1'); Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick $['.$length.']0$2}}$1'); } # format queries Irssi::command('^format own_msg_private_query {ownprivmsgnick {ownprivnick $['.$length.']2}}$1'); Irssi::command('^format msg_private_query {privmsgnick $['.$length.']0}$2'); }; sub findLongestNick { $longestNick = 0; # get own nick length map { my $len = length($_->{nick}); $longestNick = $len if ($len > $longestNick); } Irssi::servers(); # find longest other nick foreach (Irssi::channels()) { foreach ($_->nicks()) { my $len = length($_->{nick}); $longestNick = $len if ($len > $longestNick); } } reformat(); } sub delayed_save { # skip if we have already a save pending. we don't reset the timeout # here, else you could end up with changes never being automatically # saved if they happen more often than seconds return if $pending_save; return unless Irssi::settings_get_int('neat_autosave'); Irssi::timeout_add_once(Irssi::settings_get_int('neat_autosave') * 1000, \&save_colors, undef); } # a new nick was created sub sig_newNick { my ($channel, $nick) = @_; my $len = length($nick->{nick}); if ($len > $longestNick) { $longestNick = $len; reformat(); } return if (exists($saved_colors{$nick->{nick}})); $saved_colors{$nick->{nick}} = "%".nick_to_color($nick->{nick}); delayed_save(); } # something changed sub sig_changeNick { my ($channel, $nick, $old_nick) = @_; # if no saved color exists, we already handled this nickchange. irssi # generates one signal per channel the nick is in, so if you share more # than one channel with this nick, you'd lose the coloring. return unless exists($saved_colors{$old_nick}); # we need to update the saved colorors hash independent of nick lenght $saved_colors{$nick->{nick}} = $saved_colors{$old_nick}; delete $saved_colors{$old_nick}; delayed_save(); my $new = length($nick->{nick}); # in case the new nick is longer than the old one, simply remember this # as the new longest nick and reformat. # # if the new nick is as long as the known longest nick nothing has to be # done # # if the new nick is shorter than the current longest one and if the # user allows us to shrink, find new longest nick and reformat. if ($new > $longestNick) { $longestNick = $new; } elsif ($new == $longestNick) { return; } else { return unless Irssi::settings_get_bool('neat_allow_shrinking'); findLongestNick(); } reformat(); } sub sig_removeNick { my ($channel, $nick) = @_; my $thisLen = length($nick->{nick}); # we only need to recalculate if this was the longest nick and we are # allowed to shrink if ($thisLen == $longestNick && Irssi::settings_get_bool('neat_allow_shrinking')) { findLongestNick(); reformat(); } # we do not remove a known color for a gone nick, as they may return } # based on simple_hash from nickcolor.pl sub nick_to_color($) { my ($string) = @_; chomp $string; my $ignore = Irssi::settings_get_str("neat_ignorechars"); $string =~ s/$ignore//g; my $counter; foreach my $char (split(//, $string)) { $counter += ord $char; } return $colors[$counter % ($#colors + 1)]; } sub color_left($) { Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1'); Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1'); } sub color_right($) { Irssi::command('^format pubmsg {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1'); Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1'); } sub sig_public { my ($server, $msg, $nick, $address, $target) = @_; &$alignment($saved_colors{$nick}); } sub sig_setup { @colors = split(//, Irssi::settings_get_str('neat_colors')); # check left or right alignment if (Irssi::settings_get_bool('neat_right_mode') == 0) { $alignment = \&color_left; } else { $alignment = \&color_right; } # check if we switched coloring on or off my $new = Irssi::settings_get_bool('neat_colorize'); if ($new != $colorize) { if ($new) { Irssi::signal_add('message public', 'sig_public'); } else { if ($colorize >= 0) { Irssi::signal_remove('message public', 'sig_public'); } } } $colorize = $new; reformat(); &$alignment('%w'); } # make sure that every nick has an assigned color sub assert_colors() { foreach (Irssi::channels()) { foreach ($_->nicks()) { next if (exists($saved_colors{$_->{nick}})); $saved_colors{$_->{nick}} = "%".nick_to_color($_->{nick}); delayed_save(); } } } # load colors from file sub load_colors() { open(FID, "<", $ENV{HOME}."/.irssi/saved_colors") || return; while () { chomp; my ($k, $v) = split(/:/); # skip broken lines, those may have been introduced by nm.pl # version 0.3.7 and earlier if ($k eq '' || $v eq '') { neat_log(Irssi::active_win(), "Warning, broken line in saved_colors file, skipping '$k:$v'"); next; } $saved_colors{$k} = $v; } close(FID); } # save colors to file sub save_colors() { open(FID, ">", $ENV{HOME}."/.irssi/saved_colors"); print FID $_.":".$saved_colors{$_}."\n" foreach (keys(%saved_colors)); close(FID); # clear possible pending save. Irssi::timeout_remove($pending_save) if $pending_save; $pending_save = undef; } # log a line to a window item sub neat_log($@) { my ($witem, @text) = @_; $witem->print("nm.pl: ".$_) foreach(@text); } # show available colors sub cmd_neatcolor_colors($) { my ($witem, undef, undef) = @_; neat_log($witem, "Available colors: ".join("", map { "%".$_.$_ } @colors)); } # display the configured color for a nick sub cmd_neatcolor_get() { my ($witem, $nick, undef) = @_; if (!exists($saved_colors{$nick})) { neat_log($witem, "Error: no such nick '$nick'"); return; } neat_log($witem, "Color for ".$saved_colors{$nick}.$nick); } # display help sub cmd_neatcolor_help() { my ($witem, $cmd, undef) = @_; if ($cmd) { if (!exists($commands{$cmd})) { neat_log($witem, "Error: no such command '$cmd'"); return; } if (!exists($commands{$cmd}{verbose})) { neat_log($witem, "No additional help for '$cmd' available"); return; } neat_log($witem, ( "", "Help for ".uc($cmd), "" ) ); neat_log($witem, @{$commands{$cmd}{verbose}}); return; } neat_log($witem, split(/\n/, $help)); neat_log($witem, "Available options for /neatcolor"); neat_log($witem, " ".$_.": ".$commands{$_}{text}) foreach(sort(keys(%commands))); my @verbose; foreach (sort(keys(%commands))) { push(@verbose, $_) if exists($commands{$_}{verbose}); } neat_log($witem, "Verbose help available for: '".join(", ", @verbose)."'"); } # list configured nicks sub cmd_neatcolor_list() { my ($witem, undef, undef) = @_; neat_log($witem, "Configured nicks: ".join(", ", map { $saved_colors{$_}.$_ } sort(keys(%saved_colors)))); } # reset a nick to its default color sub cmd_neatcolor_reset() { my ($witem, $nick, undef) = @_; if ($nick eq '--all') { %saved_colors = (); assert_colors(); neat_log($witem, "Reset all colors"); return; } if (!exists($saved_colors{$nick})) { neat_log($witem, "Error: no such nick '$nick'"); return; } $saved_colors{$nick} = "%".nick_to_color($nick); delayed_save(); neat_log($witem, "Reset color for ".$saved_colors{$nick}.$nick); } # save configured colors to disk sub cmd_neatcolor_save() { my ($witem, undef, undef) = @_; save_colors(); neat_log($witem, "color information saved"); } # set a color for a nick sub cmd_neatcolor_set() { my ($witem, $nick, $color) = @_; my @found = grep(/$color/, @colors); if ($#found) { neat_log($witem, "Error: trying to set unknown color '%$color$color%n'"); cmd_neatcolor_colors($witem); return; } if ($witem->{type} ne "CHANNEL" && $witem->{type} ne "QUERY") { neat_log($witem, "Warning: not a Channel/Query, can not check nick!"); neat_log($witem, "Remember, nicks are case sensitive to nm.pl"); } else { my @nicks = grep(/^$nick$/i, map { $_->{nick} } ($witem->nicks())); if ($#nicks < 0) { neat_log($witem, "Warning: could not find nick '$nick' here"); } else { if ($nicks[0] ne $nick) { neat_log($witem, "Warning: using '$nicks[0]' instead of '$nick'"); $nick = $nicks[0]; } } } $saved_colors{$nick} = "%".$color; delayed_save(); neat_log($witem, "Set color for $saved_colors{$nick}$nick"); } %commands = ( colors => { text => "show available colors", verbose => [ "COLORS", "", "displays all available colors", "", "You can restrict/define the list of available colors ". "with the help of the neat_colors setting" ], func => \&cmd_neatcolor_colors, }, get => { text => "retrieve color for a nick", verbose => [ "GET ", "", "displays color used for " ], func => \&cmd_neatcolor_get, }, help => { text => "print this help message", func => \&cmd_neatcolor_help, }, list => { text => "list configured nick/color pairs", func => \&cmd_neatcolor_list, }, reset => { text => "reset color to default", verbose => [ "RESET --all|", "", "resets the color used for all nicks or for to ", "its internal default", ], func => \&cmd_neatcolor_reset, }, save => { text => "save color information to disk", verbose => [ "SAVE", "", "saves color information to disk, so that it survives ". "an irssi restart.", "", "Color information will be automatically saved on /quit", ], func => \&cmd_neatcolor_save, }, set => { text => "set a specific color for a nick", verbose => [ "SET ", "", "use for ", "", "This command will perform a couple of sanity checks, ". "when called from a CHANNEL/QUERY window", "", "EXAMPLE:", " /neatcolor set bc-bd r", "", "use /neatcolor COLORS to see available colors" ], func => \&cmd_neatcolor_set, }, ); # the main command callback that gets called for all neatcolor commands sub cmd_neatcolor() { my ($data, $server, $witem) = @_; my ($cmd, $nick, $color) = split (/ /, $data); $cmd = lc($cmd); # make sure we have a valid witem to print text to $witem = Irssi::active_win() unless ($witem); if (!exists($commands{$cmd})) { neat_log($witem, "Error: unknown command '$cmd'"); &{$commands{"help"}{"func"}}($witem) if (exists($commands{"help"})); return; } &{$commands{$cmd}{"func"}}($witem, $nick, $color); } Irssi::settings_add_bool('misc', 'neat_left_messages', 0); Irssi::settings_add_bool('misc', 'neat_left_actions', 0); Irssi::settings_add_bool('misc', 'neat_right_mode', 1); Irssi::settings_add_int('misc', 'neat_maxlength', 0); Irssi::settings_add_int('misc', 'neat_melength', 2); Irssi::settings_add_bool('misc', 'neat_colorize', 1); Irssi::settings_add_str('misc', 'neat_colors', 'rRgGyYbBmMcC'); Irssi::settings_add_str('misc', 'neat_ignorechars', ''); Irssi::settings_add_bool('misc', 'neat_allow_shrinking', 1); Irssi::settings_add_int('misc', 'neat_autosave', 60); Irssi::command_bind('neatcolor', 'cmd_neatcolor'); Irssi::signal_add('nicklist new', 'sig_newNick'); Irssi::signal_add('nicklist changed', 'sig_changeNick'); Irssi::signal_add('nicklist remove', 'sig_removeNick'); Irssi::signal_add('setup changed', 'sig_setup'); Irssi::signal_add_last('setup reread', 'sig_setup'); findLongestNick(); sig_setup; load_colors(); assert_colors(); # we need to add this signal _after_ the colors have been loaded, to make sure # no race condition exists wrt color saving Irssi::signal_add('gui exit', 'save_colors');