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