1use Irssi; 2use strict; 3 4use vars qw($VERSION %IRSSI); 5 6$VERSION="0.3.10"; 7%IRSSI = ( 8 authors=> 'BC-bd', 9 contact=> 'bd@bc-bd.org', 10 name=> 'nm', 11 description=> 'right aligned nicks depending on longest nick', 12 license=> 'GPL v2', 13 url=> 'http://bc-bd.org/blog/irssi/', 14); 15 16# $Id: 9cb009e8b7e6f5ce60294334faf88715ef01413e $ 17# nm.pl 18# for irssi 0.8.4 by bd@bc-bd.org 19# 20# right aligned nicks depending on longest nick 21# 22# inspired by neatmsg.pl from kodgehopper <kodgehopper@netscape.net 23# formats taken from www.irssi.de 24# thanks to adrianel <adrinael@nuclearzone.org> for some hints 25# thanks to Eric Wald <eswald@gmail.com> for the left alignment patch 26# inspired by nickcolor.pl by Timo Sirainen and Ian Peters 27# thanks to And1 <and1@meinungsverstaerker.de> for a small patch 28# thanks to berber@tzi.de for the save/load patch 29# thanks to Dennis Heimbert <dennis.heimbert@gmail.com> for a bug report/patch 30# thanks to Roy Sigurd Karlsbakk <roy@karlsbakk.net> for an autosave patch 31# 32######### 33# USAGE 34### 35# 36# use 37# 38# /neatcolor help 39# 40# for help on available commands 41# 42######### 43# OPTIONS 44######### 45 46my $help = " 47/set neat_colorize <ON|OFF> 48 * ON : colorize nicks 49 * OFF : do not colorize nicks 50 51/set neat_colors <string> 52 Use these colors when colorizing nicks, eg: 53 54 /set neat_colors yYrR 55 56 See the file formats.txt on an explanation of what colors are 57 available. 58 59/set neat_left_actions <ON|OFF> 60 * ON : print nicks left-aligned on actions 61 * OFF : print nicks right-aligned on actions 62 63/set neat_left_messages <ON|OFF> 64 * ON : print nicks left-aligned on messages 65 * OFF : print nicks right-aligned on messages 66 67/set neat_right_mode <ON|OFF> 68 * ON : print the mode of the nick e.g @%+ after the nick 69 * OFF : print it left of the nick 70 71/set neat_maxlength <number> 72 * number : Maximum length of Nicks to display. Longer nicks are truncated. 73 * 0 : Do not truncate nicks. 74 75/set neat_melength <number> 76 * number : number of spaces to substract from /me padding 77 78/set neat_ignorechars <str> 79 * str : regular expression used to filter out unwanted characters in 80 nicks. this can be used to assign the same color for similar 81 nicks, e.g. foo and foo_: 82 83 /set neat_ignorechars [_] 84 85/set neat_allow_shrinking <ON|OFF> 86 * ON : shrink padding when longest nick disappears 87 * OFF : do not shrink, only allow growing 88 89/set neat_autosave <number> 90 * number : autosave after <number> seconds, defaults to 60. Set to 0 to 91 disable. 92"; 93 94# 95### 96################ 97### 98# 99# Changelog 100# 101# Version 0.3.11 102# - added autosave, idea from Roy Sigurd Karlsbakk 103# 104# Version 0.3.10 105# - fix losing of saved color when changing nick shares more than one channel 106# with you 107# 108# Version 0.3.9 109# - fix longest nick calculation for nicks shorter than the current longest 110# nick 111# - updated url 112# 113# Version 0.3.8 114# - fixed error in the nickchange tracking code, reported by Kevin Ballard 115# - added --all switch to reset command 116# - skip broken lines in saved_colors 117# 118# Version 0.3.7 119# - fixed crash when calling /neatcolor without parameters 120# - fixed url 121# 122# Version 0.3.6 123# - added option to ignore certain characters from color hash building, see 124# https://bc-bd.org/trac/irssi/ticket/22 125# - added option to save and specify colors for nicks, see 126# https://bc-bd.org/trac/irssi/ticket/23 127# - added option to disallow shrinking, see 128# https://bc-bd.org/trac/irssi/ticket/12 129# 130# Version 0.3.5 131# - now also aligning own messages in queries 132# 133# Version 0.3.4 134# - fxed off by one error in nick_to_color, patch by jrib, see 135# https://bc-bd.org/trac/irssi/ticket/24 136# 137# Version 0.3.3 138# - added support for alignment in queries, see 139# https://bc-bd.org/trac/irssi/ticket/21 140# 141# Version 0.3.2 142# - integrated left alignment patch from Eric Wald <eswald@gmail.com>, see 143# https://bc-bd.org/trac/irssi/ticket/18 144# 145# Version 0.3.1 146# - /me padding, see https://bc-bd.org/trac/irssi/ticket/17 147# 148# Version 0.3.0 149# - integrate nick coloring support 150# 151# Version 0.2.1 152# - moved neat_maxlength check to reformat() (thx to Jerome De Greef <jdegreef@brutele.be>) 153# 154# Version 0.2.0 155# - by adrianel <adrinael@nuclearzone.org> 156# * reformat after setup reload 157# * maximum length of nicks 158# 159# Version 0.1.0 160# - got lost somewhere 161# 162# Version 0.0.2 163# - ugly typo fixed 164# 165# Version 0.0.1 166# - initial release 167# 168### 169################ 170### 171# 172# BUGS 173# 174# Empty nicks, eg "<> message" 175# This seems to be triggered by some themes. As of now there is no known 176# fix other than changing themes, see 177# https://bc-bd.org/trac/irssi/ticket/19 178# 179# Well, it's a feature: due to the lacking support of extendable themes 180# from irssi it is not possible to just change some formats per window. 181# This means that right now all windows are aligned with the same nick 182# length, which can be somewhat annoying. 183# If irssi supports extendable themes, I will include per-server indenting 184# and a setting where you can specify servers you don't want to be indented 185# 186### 187################ 188 189my ($longestNick, %saved_colors, @colors, $alignment, $sign, %commands,); 190my ($pending_save); 191 192my $colorize = -1; 193 194sub reformat() { 195 my $max = Irssi::settings_get_int('neat_maxlength'); 196 my $actsign = Irssi::settings_get_bool('neat_left_actions')? '': '-'; 197 $sign = Irssi::settings_get_bool('neat_left_messages')? '': '-'; 198 199 if ($max && $max < $longestNick) { 200 $longestNick = $max; 201 } 202 203 my $me = $longestNick - Irssi::settings_get_int('neat_melength'); 204 $me = 0 if ($me < 0); 205 206 Irssi::command('^format own_action {ownaction $['.$actsign.$me.']0} $1'); 207 Irssi::command('^format action_public {pubaction $['.$actsign.$me.']0}$1'); 208 Irssi::command('^format action_private {pvtaction $['.$actsign.$me.']0}$1'); 209 Irssi::command('^format action_private_query {pvtaction_query $['.$actsign.$me.']0} $2'); 210 211 my $length = $sign . $longestNick; 212 if (Irssi::settings_get_bool('neat_right_mode') == 0) { 213 Irssi::command('^format own_msg {ownmsgnick $2 {ownnick $['.$length.']0}}$1'); 214 Irssi::command('^format own_msg_channel {ownmsgnick $3 {ownnick $['.$length.']0}{msgchannel $1}}$2'); 215 Irssi::command('^format pubmsg_me {pubmsgmenick $2 {menick $['.$length.']0}}$1'); 216 Irssi::command('^format pubmsg_me_channel {pubmsgmenick $3 {menick $['.$length.']0}{msgchannel $1}}$2'); 217 Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $3 $['.$length.']1%n}$2'); 218 Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $4 $['.$length.']1{msgchannel $2}}$3'); 219 Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick $['.$length.']0}}$1'); 220 Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick $['.$length.']0}}$1'); 221 } else { 222 Irssi::command('^format own_msg {ownmsgnick {ownnick $['.$length.']0$2}}$1'); 223 Irssi::command('^format own_msg_channel {ownmsgnick {ownnick $['.$length.']0$3}{msgchannel $1}}$2'); 224 Irssi::command('^format pubmsg_me {pubmsgmenick {menick $['.$length.']0}$2}$1'); 225 Irssi::command('^format pubmsg_me_channel {pubmsgmenick {menick $['.$length.']0$3}{msgchannel $1}}$2'); 226 Irssi::command('^format pubmsg_hilight {pubmsghinick $0 $0 $['.$length.']1$3%n}$2'); 227 Irssi::command('^format pubmsg_hilight_channel {pubmsghinick $0 $['.$length.']1$4{msgchannel $2}}$3'); 228 Irssi::command('^format pubmsg {pubmsgnick {pubnick $['.$length.']0$2}}$1'); 229 Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick $['.$length.']0$2}}$1'); 230 } 231 232 # format queries 233 Irssi::command('^format own_msg_private_query {ownprivmsgnick {ownprivnick $['.$length.']2}}$1'); 234 Irssi::command('^format msg_private_query {privmsgnick $['.$length.']0}$2'); 235}; 236 237sub findLongestNick { 238 $longestNick = 0; 239 240 # get own nick length 241 map { 242 my $len = length($_->{nick}); 243 244 $longestNick = $len if ($len > $longestNick); 245 } Irssi::servers(); 246 247 # find longest other nick 248 foreach (Irssi::channels()) { 249 foreach ($_->nicks()) { 250 my $len = length($_->{nick}); 251 252 $longestNick = $len if ($len > $longestNick); 253 } 254 } 255 256 reformat(); 257} 258 259sub delayed_save 260{ 261 # skip if we have already a save pending. we don't reset the timeout 262 # here, else you could end up with changes never being automatically 263 # saved if they happen more often than <neat_autosave> seconds 264 return if $pending_save; 265 266 return unless Irssi::settings_get_int('neat_autosave'); 267 268 Irssi::timeout_add_once(Irssi::settings_get_int('neat_autosave') * 1000, 269 \&save_colors, undef); 270} 271 272# a new nick was created 273sub sig_newNick 274{ 275 my ($channel, $nick) = @_; 276 277 my $len = length($nick->{nick}); 278 279 if ($len > $longestNick) { 280 $longestNick = $len; 281 reformat(); 282 } 283 284 return if (exists($saved_colors{$nick->{nick}})); 285 286 $saved_colors{$nick->{nick}} = "%".nick_to_color($nick->{nick}); 287 delayed_save(); 288} 289 290# something changed 291sub sig_changeNick 292{ 293 my ($channel, $nick, $old_nick) = @_; 294 295 # if no saved color exists, we already handled this nickchange. irssi 296 # generates one signal per channel the nick is in, so if you share more 297 # than one channel with this nick, you'd lose the coloring. 298 return unless exists($saved_colors{$old_nick}); 299 300 # we need to update the saved colorors hash independent of nick lenght 301 $saved_colors{$nick->{nick}} = $saved_colors{$old_nick}; 302 delete $saved_colors{$old_nick}; 303 delayed_save(); 304 305 my $new = length($nick->{nick}); 306 307 # in case the new nick is longer than the old one, simply remember this 308 # as the new longest nick and reformat. 309 # 310 # if the new nick is as long as the known longest nick nothing has to be 311 # done 312 # 313 # if the new nick is shorter than the current longest one and if the 314 # user allows us to shrink, find new longest nick and reformat. 315 if ($new > $longestNick) { 316 $longestNick = $new; 317 } elsif ($new == $longestNick) { 318 return; 319 } else { 320 return unless Irssi::settings_get_bool('neat_allow_shrinking'); 321 findLongestNick(); 322 } 323 324 reformat(); 325} 326 327sub sig_removeNick 328{ 329 my ($channel, $nick) = @_; 330 331 my $thisLen = length($nick->{nick}); 332 333 # we only need to recalculate if this was the longest nick and we are 334 # allowed to shrink 335 if ($thisLen == $longestNick && Irssi::settings_get_bool('neat_allow_shrinking')) { 336 findLongestNick(); 337 reformat(); 338 } 339 340 # we do not remove a known color for a gone nick, as they may return 341} 342 343# based on simple_hash from nickcolor.pl 344sub nick_to_color($) { 345 my ($string) = @_; 346 chomp $string; 347 348 my $ignore = Irssi::settings_get_str("neat_ignorechars"); 349 $string =~ s/$ignore//g; 350 351 my $counter; 352 foreach my $char (split(//, $string)) { 353 $counter += ord $char; 354 } 355 356 return $colors[$counter % ($#colors + 1)]; 357} 358 359sub color_left($) { 360 Irssi::command('^format pubmsg {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1'); 361 Irssi::command('^format pubmsg_channel {pubmsgnick $2 {pubnick '.$_[0].'$['.$sign.$longestNick.']0}}$1'); 362} 363 364sub color_right($) { 365 Irssi::command('^format pubmsg {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1'); 366 Irssi::command('^format pubmsg_channel {pubmsgnick {pubnick '.$_[0].'$['.$sign.$longestNick.']0}$2}$1'); 367} 368 369sub sig_public { 370 my ($server, $msg, $nick, $address, $target) = @_; 371 372 &$alignment($saved_colors{$nick}); 373} 374 375sub sig_setup { 376 @colors = split(//, Irssi::settings_get_str('neat_colors')); 377 378 # check left or right alignment 379 if (Irssi::settings_get_bool('neat_right_mode') == 0) { 380 $alignment = \&color_left; 381 } else { 382 $alignment = \&color_right; 383 } 384 385 # check if we switched coloring on or off 386 my $new = Irssi::settings_get_bool('neat_colorize'); 387 if ($new != $colorize) { 388 if ($new) { 389 Irssi::signal_add('message public', 'sig_public'); 390 } else { 391 if ($colorize >= 0) { 392 Irssi::signal_remove('message public', 'sig_public'); 393 } 394 } 395 } 396 $colorize = $new; 397 398 reformat(); 399 &$alignment('%w'); 400} 401 402# make sure that every nick has an assigned color 403sub assert_colors() { 404 foreach (Irssi::channels()) { 405 foreach ($_->nicks()) { 406 next if (exists($saved_colors{$_->{nick}})); 407 408 $saved_colors{$_->{nick}} = "%".nick_to_color($_->{nick}); 409 delayed_save(); 410 } 411 } 412} 413 414# load colors from file 415sub load_colors() { 416 open(FID, "<", $ENV{HOME}."/.irssi/saved_colors") || return; 417 418 while (<FID>) { 419 chomp; 420 my ($k, $v) = split(/:/); 421 422 # skip broken lines, those may have been introduced by nm.pl 423 # version 0.3.7 and earlier 424 if ($k eq '' || $v eq '') { 425 neat_log(Irssi::active_win(), "Warning, broken line in saved_colors file, skipping '$k:$v'"); 426 next; 427 } 428 429 $saved_colors{$k} = $v; 430 } 431 432 close(FID); 433} 434 435# save colors to file 436sub save_colors() { 437 open(FID, ">", $ENV{HOME}."/.irssi/saved_colors"); 438 439 print FID $_.":".$saved_colors{$_}."\n" foreach (keys(%saved_colors)); 440 441 close(FID); 442 443 # clear possible pending save. 444 Irssi::timeout_remove($pending_save) if $pending_save; 445 $pending_save = undef; 446} 447 448# log a line to a window item 449sub neat_log($@) { 450 my ($witem, @text) = @_; 451 452 $witem->print("nm.pl: ".$_) foreach(@text); 453} 454 455# show available colors 456sub cmd_neatcolor_colors($) { 457 my ($witem, undef, undef) = @_; 458 459 neat_log($witem, "Available colors: ".join("", map { "%".$_.$_ } @colors)); 460} 461 462# display the configured color for a nick 463sub cmd_neatcolor_get() { 464 my ($witem, $nick, undef) = @_; 465 466 if (!exists($saved_colors{$nick})) { 467 neat_log($witem, "Error: no such nick '$nick'"); 468 return; 469 } 470 471 neat_log($witem, "Color for ".$saved_colors{$nick}.$nick); 472} 473 474# display help 475sub cmd_neatcolor_help() { 476 my ($witem, $cmd, undef) = @_; 477 478 if ($cmd) { 479 if (!exists($commands{$cmd})) { 480 neat_log($witem, "Error: no such command '$cmd'"); 481 return; 482 } 483 484 if (!exists($commands{$cmd}{verbose})) { 485 neat_log($witem, "No additional help for '$cmd' available"); 486 return; 487 } 488 489 neat_log($witem, ( "", "Help for ".uc($cmd), "" ) ); 490 neat_log($witem, @{$commands{$cmd}{verbose}}); 491 return; 492 } 493 494 neat_log($witem, split(/\n/, $help)); 495 neat_log($witem, "Available options for /neatcolor"); 496 neat_log($witem, " ".$_.": ".$commands{$_}{text}) foreach(sort(keys(%commands))); 497 498 my @verbose; 499 foreach (sort(keys(%commands))) { 500 push(@verbose, $_) if exists($commands{$_}{verbose}); 501 } 502 503 neat_log($witem, "Verbose help available for: '".join(", ", @verbose)."'"); 504} 505 506# list configured nicks 507sub cmd_neatcolor_list() { 508 my ($witem, undef, undef) = @_; 509 510 neat_log($witem, "Configured nicks: ".join(", ", map { $saved_colors{$_}.$_ } sort(keys(%saved_colors)))); 511} 512 513# reset a nick to its default color 514sub cmd_neatcolor_reset() { 515 my ($witem, $nick, undef) = @_; 516 517 if ($nick eq '--all') { 518 %saved_colors = (); 519 assert_colors(); 520 neat_log($witem, "Reset all colors"); 521 return; 522 } 523 524 if (!exists($saved_colors{$nick})) { 525 neat_log($witem, "Error: no such nick '$nick'"); 526 return; 527 } 528 529 $saved_colors{$nick} = "%".nick_to_color($nick); 530 delayed_save(); 531 neat_log($witem, "Reset color for ".$saved_colors{$nick}.$nick); 532} 533 534# save configured colors to disk 535sub cmd_neatcolor_save() { 536 my ($witem, undef, undef) = @_; 537 538 save_colors(); 539 540 neat_log($witem, "color information saved"); 541} 542 543# set a color for a nick 544sub cmd_neatcolor_set() { 545 my ($witem, $nick, $color) = @_; 546 547 my @found = grep(/$color/, @colors); 548 if ($#found) { 549 neat_log($witem, "Error: trying to set unknown color '%$color$color%n'"); 550 cmd_neatcolor_colors($witem); 551 return; 552 } 553 554 if ($witem->{type} ne "CHANNEL" && $witem->{type} ne "QUERY") { 555 neat_log($witem, "Warning: not a Channel/Query, can not check nick!"); 556 neat_log($witem, "Remember, nicks are case sensitive to nm.pl"); 557 } else { 558 my @nicks = grep(/^$nick$/i, map { $_->{nick} } ($witem->nicks())); 559 560 if ($#nicks < 0) { 561 neat_log($witem, "Warning: could not find nick '$nick' here"); 562 } else { 563 if ($nicks[0] ne $nick) { 564 neat_log($witem, "Warning: using '$nicks[0]' instead of '$nick'"); 565 $nick = $nicks[0]; 566 } 567 } 568 } 569 570 $saved_colors{$nick} = "%".$color; 571 delayed_save(); 572 neat_log($witem, "Set color for $saved_colors{$nick}$nick"); 573} 574 575%commands = ( 576 colors => { 577 text => "show available colors", 578 verbose => [ 579 "COLORS", 580 "", 581 "displays all available colors", 582 "", 583 "You can restrict/define the list of available colors ". 584 "with the help of the neat_colors setting" 585 ], 586 func => \&cmd_neatcolor_colors, 587 }, 588 get => { 589 text => "retrieve color for a nick", 590 verbose => [ 591 "GET <nick>", 592 "", 593 "displays color used for <nick>" 594 ], 595 func => \&cmd_neatcolor_get, 596 }, 597 help => { 598 text => "print this help message", 599 func => \&cmd_neatcolor_help, 600 }, 601 list => { 602 text => "list configured nick/color pairs", 603 func => \&cmd_neatcolor_list, 604 }, 605 reset => { 606 text => "reset color to default", 607 verbose => [ 608 "RESET --all|<nick>", 609 "", 610 "resets the color used for all nicks or for <nick> to ", 611 "its internal default", 612 ], 613 func => \&cmd_neatcolor_reset, 614 }, 615 save => { 616 text => "save color information to disk", 617 verbose => [ 618 "SAVE", 619 "", 620 "saves color information to disk, so that it survives ". 621 "an irssi restart.", 622 "", 623 "Color information will be automatically saved on /quit", 624 ], 625 func => \&cmd_neatcolor_save, 626 }, 627 set => { 628 text => "set a specific color for a nick", 629 verbose => [ 630 "SET <nick> <color>", 631 "", 632 "use <color> for <nick>", 633 "", 634 "This command will perform a couple of sanity checks, ". 635 "when called from a CHANNEL/QUERY window", 636 "", 637 "EXAMPLE:", 638 " /neatcolor set bc-bd r", 639 "", 640 "use /neatcolor COLORS to see available colors" 641 ], 642 func => \&cmd_neatcolor_set, 643 }, 644); 645 646# the main command callback that gets called for all neatcolor commands 647sub cmd_neatcolor() { 648 my ($data, $server, $witem) = @_; 649 my ($cmd, $nick, $color) = split (/ /, $data); 650 651 $cmd = lc($cmd); 652 653 # make sure we have a valid witem to print text to 654 $witem = Irssi::active_win() unless ($witem); 655 656 if (!exists($commands{$cmd})) { 657 neat_log($witem, "Error: unknown command '$cmd'"); 658 &{$commands{"help"}{"func"}}($witem) if (exists($commands{"help"})); 659 return; 660 } 661 662 &{$commands{$cmd}{"func"}}($witem, $nick, $color); 663} 664 665Irssi::settings_add_bool('misc', 'neat_left_messages', 0); 666Irssi::settings_add_bool('misc', 'neat_left_actions', 0); 667Irssi::settings_add_bool('misc', 'neat_right_mode', 1); 668Irssi::settings_add_int('misc', 'neat_maxlength', 0); 669Irssi::settings_add_int('misc', 'neat_melength', 2); 670Irssi::settings_add_bool('misc', 'neat_colorize', 1); 671Irssi::settings_add_str('misc', 'neat_colors', 'rRgGyYbBmMcC'); 672Irssi::settings_add_str('misc', 'neat_ignorechars', ''); 673Irssi::settings_add_bool('misc', 'neat_allow_shrinking', 1); 674Irssi::settings_add_int('misc', 'neat_autosave', 60); 675 676Irssi::command_bind('neatcolor', 'cmd_neatcolor'); 677 678Irssi::signal_add('nicklist new', 'sig_newNick'); 679Irssi::signal_add('nicklist changed', 'sig_changeNick'); 680Irssi::signal_add('nicklist remove', 'sig_removeNick'); 681 682Irssi::signal_add('setup changed', 'sig_setup'); 683Irssi::signal_add_last('setup reread', 'sig_setup'); 684 685findLongestNick(); 686sig_setup; 687 688load_colors(); 689assert_colors(); 690 691# we need to add this signal _after_ the colors have been loaded, to make sure 692# no race condition exists wrt color saving 693Irssi::signal_add('gui exit', 'save_colors'); 694