html/friends_peder.pl
1 # friends - irssi 0.8.4.CVS
2 #
3 # $Id: friends.pl,v 1.34 2004/03/08 21:47:12 peder Exp $
4 #
5 # Copyright (C) 2001, 2002, 2003 by Peder Stray <peder@ninja.no>
6 #
7
8 use strict;
9 use Irssi 20020427.2353;
10 use Irssi::Irc;
11 use Irssi::TextUI;
12
13 use Data::Dumper;
14 $Data::Dumper::Indent = 1;
15
16 # ======[ Script Header ]===============================================
17
18 use vars qw{$VERSION %IRSSI};
19 ($VERSION) = '$Revision: 1.34 $' =~ / (\d+\.\d+) /;
20 %IRSSI = (
21 name => 'friends',
22 authors => 'Peder Stray',
23 contact => 'peder@ninja.no',
24 url => 'http://ninja.no/irssi/friends.pl',
25 license => 'GPL',
26 description => 'Basicly an autoop script with a nice interface and nick coloring ;)',
27 );
28
29 # ======[ Variables ]===================================================
30
31 my(%friends, @friends);
32
33 my(%flagshort) = (
34 op => 'o',
35 voice => 'v',
36 color => 'c',
37 );
38 my(%flaglong) = map { $flagshort{$_} => $_ } keys %flagshort;
39
40 # ======[ Helper functions ]============================================
41
42 # --------[ crap ]------------------------------------------------------
43
44 sub crap {
45 my $template = shift;
46 my $msg = sprintf $template, @_;
47 Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'friends_crap', $msg);
48 }
49
50 # --------[ load_friends ]----------------------------------------------
51
52 sub load_friends {
53 my($file) = Irssi::get_irssi_dir."/friends";
54 my($count) = 0;
55 my($mask,$net,$channel,$flags,$flag);
56 local(*FILE);
57
58 %friends = ();
59 open FILE, "< $file";
60 while (<FILE>) {
61 ($mask,$net,$channel,$flags) = split;
62 for (split //, $flags) {
63 if ($flag = $flaglong{$_}) {
64 $friends{$mask}{lc $net}{lc $channel}{$flag} = 1;
65 }
66 }
67 }
68 close FILE;
69 $count = keys %friends;
70
71 crap("Loaded $count friends from $file");
72 }
73
74 # --------[ save_friends ]----------------------------------------------
75
76 sub save_friends {
77 my($auto) = @_;
78 my($file) = Irssi::get_irssi_dir."/friends";
79 my($count) = 0;
80 local(*FILE);
81
82 return if $auto && !Irssi::settings_get_bool('friends_autosave');
83
84 open FILE, "> $file";
85 for my $mask (keys %friends) {
86 $count++;
87 for my $net (keys %{$friends{$mask}}) {
88 for my $channel (keys %{$friends{$mask}{$net}}) {
89 print FILE "$mask\t$net\t$channel\t".
90 join("", sort map {$flagshort{$_}} keys %{$friends{$mask}{$net}{$channel}}).
91 "\n";
92 }
93 }
94 }
95 close FILE;
96
97 crap("Saved $count friends to $file")
98 unless $auto;
99 }
100
101 # --------[ is_friends_window ]-----------------------------------------
102
103 sub is_friends_window {
104 my($win) = @_;
105 return $win->{name} eq '<Friends>';
106 }
107
108 # --------[ get_friends_window ]----------------------------------------
109
110 sub get_friends_window {
111 my($win) = Irssi::window_find_name('<Friends>');
112 if ($win) {
113 $win->set_active;
114 } else {
115 Irssi::command("window new hide");
116 $win = Irssi::active_win;
117 $win->set_name('<Friends>');
118 $win->set_history('<Friends>');
119 }
120 return $win;
121 }
122
123 # --------[ get_friend ]------------------------------------------------
124
125 sub get_friend {
126 my($channel,$nick) = @_;
127 my($server) = $channel->{server};
128 my($chan) = lc $channel->{name};
129 my($net) = lc $server->{chatnet};
130 my($flags,@friend);
131
132 for my $mask (keys %friends) {
133 next unless $server->mask_match_address($mask,
134 $nick->{nick},
135 $nick->{host});
136 for my $n ('*', $net) {
137 for my $c ('*', $chan) {
138 if (exists $friends{$mask}{$n}{$c}) {
139 for my $flag (keys %{$friends{$mask}{$n}{$c}}) {
140 $flags->{$flag} = 1;
141 }
142 }
143 }
144 }
145 return $flags if $flags;
146 }
147 return undef;
148 }
149
150 # --------[ check_friends ]---------------------------------------------
151
152 sub check_friends {
153 my($channel, @nicks) = @_;
154 my(%op,%voice);
155 my($nick,$friend,$list);
156 my(@friends);
157
158 return unless $channel->{chanop} || $channel->{ownnick}{op};
159
160 for $nick (@nicks) {
161 $friend = get_friend($channel, $nick);
162 next unless $friend;
163 next if $nick->{nick} eq $channel->{server}{nick};
164 if ($friend->{op} && !$nick->{op}) {
165 $op{$nick->{nick}} = 1;
166 }
167 if ($friend->{voice} && !$nick->{voice}) {
168 $voice{$nick->{nick}} = 1;
169 }
170 push @friends, ($nick->{op}?'@':'').
171 ($nick->{voice}?'+':'').$nick->{nick};
172 }
173
174 if (@friends && Irssi::settings_get_bool("friends_show_check")) {
175 my($max) = Irssi::settings_get_int("friends_max_nicks");
176 @friends = sort @friends;
177 $channel->printformat(MSGLEVEL_CLIENTCRAP,
178 @friends>$max
179 ? 'friends_check_more' : 'friends_check',
180 join(" ", splice @friends, 0, $max),
181 scalar @friends);
182 }
183
184 if ($list = join " ", sort keys %op) {
185 $channel->command("op $list");
186 }
187 if ($list = join " ", sort keys %voice) {
188 $channel->command("voice $list");
189 }
190 }
191
192 # --------[ update_friends_hash ]---------------------------------------
193
194 sub update_friends_hash {
195 %friends = ();
196 for (@friends) {
197 my($num,$mask,$chan,$net,$flags) = @$_;
198 for (split //, $flags) {
199 $friends{$mask}{$net}{$chan}{$flaglong{$_}} = 1;
200 }
201 }
202 }
203
204 # --------[ update_friends_window ]-------------------------------------
205
206 sub update_friends_window {
207 my($win) = Irssi::window_find_name('<Friends>');
208 my($view);
209 my($num) = 0;
210 my($mask,$net,$channel,$flags);
211
212 my(%net);
213
214 if ($win) {
215 @friends = ();
216 for $mask (sort keys %friends) {
217 for $net (sort keys %{$friends{$mask}}) {
218 for $channel (sort keys %{$friends{$mask}{$net}}) {
219 $flags = join "", sort map {$flagshort{$_}}
220 keys %{$friends{$mask}{$net}{$channel}};
221 push @friends, [ ++$num, $mask, $channel, $net, $flags ];
222 }
223 }
224 }
225
226 $view = $win->view;
227 $view->remove_all_lines();
228 $view->clear();
229 $win->printformat(MSGLEVEL_NEVER, 'friends_header',
230 '##', 'Mask', 'Channel', 'ChatNet', 'Flags');
231 for (@friends) {
232 ($num,$mask,$channel,$net,$flags) = @$_;
233 if (!$net{$net}) {
234 my($n) = Irssi::chatnet_find($net);
235 $net{$net} = $n?$n->{name}:$net;
236 }
237 $win->printformat(MSGLEVEL_NEVER, 'friends_line',
238 $num, $mask, $channel, $net{$net}, $flags);
239 }
240 $win->printformat(MSGLEVEL_NEVER, 'friends_footer', scalar @friends);
241 }
242 }
243
244 # ======[ Signal Hooks ]================================================
245
246 # --------[ sig_send_command ]------------------------------------------
247
248 sub sig_send_command {
249 my($win) = Irssi::active_win;
250 if (is_friends_window($win)) {
251 my($cmd,@param) = split " ", $_[0];
252 my($changed) = 0;
253
254 Irssi::signal_stop;
255
256 for (lc $cmd) {
257 s,^/,,;
258 if (/^m(ask)?$/) {
259 $changed = subcmd_friends_mask($win,@param);
260
261 } elsif (/^c(han(nel)?)?$/) {
262 $changed = subcmd_friends_channel($win,@param);
263
264 } elsif (/^(?:n(et)?|chat(net)?)$/) {
265 $changed = subcmd_friends_net($win,@param);
266
267 } elsif (/^del(ete)?$/) {
268 $changed = subcmd_friends_delete($win,@param);
269
270 } elsif (/^f(lags?)?$/) {
271 $changed = subcmd_friends_flags($win,@param);
272
273 } elsif (/^s(ave)?/) {
274 save_friends();
275
276 } elsif (/^(?:e(xit)?|q(uit)?)$/) {
277 $win->destroy;
278
279 } elsif (/^(?:h(elp)?|\?)$/) {
280 subcmd_friends_help($win);
281
282 } else {
283 $win->print("CMD: $cmd @{[map{\"[$_]\"}@param]}");
284
285 }
286 }
287
288 if ($changed) {
289 update_friends_hash();
290 update_friends_window();
291 save_friends(1);
292 }
293 }
294 }
295
296 # --------[ sig_massjoin ]----------------------------------------------
297
298 sub sig_massjoin {
299 my($channel, $nicks) = @_;
300 check_friends($channel, @$nicks);
301 }
302
303 # --------[ sig_nick_mode_changed ]-------------------------------------
304
305 sub sig_nick_mode_changed {
306 my($channel, $nick) = @_;
307 if ($channel->{synced} && $channel->{server}{nick} eq $nick->{nick}) {
308 check_friends($channel, $channel->nicks);
309 }
310 }
311
312 # --------[ sig_channel_sync ]------------------------------------------
313
314 sub sig_channel_sync {
315 my($channel) = @_;
316 check_friends($channel, $channel->nicks);
317 }
318
319 # --------[ sig_setup_reread ]------------------------------------------
320
321 sub sig_setup_reread {
322 load_friends;
323 }
324
325 # --------[ sig_setup_save ]--------------------------------------------
326
327 sub sig_setup_save {
328 my($mainconf,$auto) = @_;
329 save_friends($auto);
330 }
331
332 # --------[ sig_window_changed ]----------------------------------------
333
334 sub sig_window_changed {
335 my($new,$old) = @_;
336 if (is_friends_window($new)) {
337 update_friends_window();
338 }
339 }
340
341 # --------[ sig_message_public ]----------------------------------------
342
343 sub sig_message_public {
344 my($server, $msg, $nick, $addr, $target) = @_;
345 my($window,$theme,$friend,$oform,$nform);
346 my($channel) = $server->channel_find($target);
347
348 return unless $channel;
349
350 my($color) = Irssi::settings_get_str("friends_nick_color");
351
352 $friend = get_friend($channel, $channel->nick_find($nick));
353
354 if ($friend && $color =~ /^[rgbcmykpwRGBCMYKPWFU0-9_]$/) {
355 $window = $server->window_find_item($target);
356 $theme = $window->{theme} || Irssi::current_theme;
357
358 $oform = $nform = $theme->get_format('fe-common/core', 'pubmsg');
359 $nform =~ s/(\$(\[-?\d+\])?0)/%$color$1%n/g;
360
361 $window->command("^format pubmsg $nform");
362 Irssi::signal_continue(@_);
363 $window->command("^format pubmsg $oform");
364 }
365 }
366
367 # --------[ sig_message_irc_action ]------------------------------------
368
369 sub sig_message_irc_action {
370 my($server, $msg, $nick, $addr, $target) = @_;
371 my($window,$theme,$friend,$oform,$nform);
372 my($channel) = $server->channel_find($target);
373
374 return unless $channel;
375
376 my($color) = Irssi::settings_get_str("friends_nick_color");
377
378 $friend = get_friend($channel, $channel->nick_find($nick));
379
380 if ($friend && $color =~ /^[rgbcmykpwRGBCMYKPWFU0-9_]$/) {
381 $window = $server->window_find_item($target);
382 $theme = $window->{theme} || Irssi::current_theme;
383
384 $oform = $nform = $theme->get_format('fe-common/irc',
385 'action_public');
386 $nform =~ s/(\$(\[-?\d+\])?0)/%$color$1%n/g;
387
388 $window->command("^format action_public $nform");
389 Irssi::signal_continue(@_);
390 $window->command("^format action_public $oform");
391 }
392 }
393
394 # ======[ Commands ]====================================================
395
396 # --------[ FRIENDS ]---------------------------------------------------
397
398 # Usage: /FRIENDS
399 sub cmd_friends {
400 my($win) = get_friends_window;
401 update_friends_window();
402 }
403
404 # --------[ subcmd_friends_channel ]------------------------------------
405
406 sub subcmd_friends_channel {
407 my($win,$num,$chan) = @_;
408
409 unless ($chan && defined $num) {
410 $win->print("Syntax: CHANNEL <num> <channel>", MSGLEVEL_NEVER);
411 return;
412 }
413
414 unless (0 < $num && $num <= @friends) {
415 $win->print("Error: Element $num not in list", MSGLEVEL_NEVER);
416 return;
417 }
418
419 $friends[$num-1][2] = $chan;
420
421 return 1;
422 }
423
424 # --------[ subcmd_friends_delete ]-------------------------------------
425
426 sub subcmd_friends_delete {
427 my($win,$num) = @_;
428
429 unless (defined $num) {
430 $win->print("Syntax: DELETE <num>", MSGLEVEL_NEVER);
431 return;
432 }
433
434 unless (0 < $num && $num <= @friends) {
435 $win->print("Error: Element $num not in list", MSGLEVEL_NEVER);
436 return;
437 }
438
439 splice @friends, $num-1, 1;
440
441 return 1;
442 }
443
444 # --------[ subcmd_friends_flags ]--------------------------------------
445
446 sub subcmd_friends_flags {
447 my($win,$num,$flags) = @_;
448 my(%f);
449
450 unless ($flags && defined $num) {
451 $win->print("Syntax: FLAGS <num> <flags>", MSGLEVEL_NEVER);
452 return;
453 }
454
455 unless (0 < $num && $num <= @friends) {
456 $win->print("Error: Element $num not in list", MSGLEVEL_NEVER);
457 return;
458 }
459
460 $friends[$num-1][4] = join "", sort grep {!$f{$_}++}
461 split //, $flags;
462
463 return 1;
464 }
465
466 # --------[ subcmd_friends_help ]---------------------------------------
467
468 sub subcmd_friends_help {
469 my($win) = @_;
470
471 $win->print(q{CHANNEL <num> <channel> - set channel
472
473 <channel> is either a channel name or * for all
474 }, MSGLEVEL_NEVER);
475
476 $win->print(q{DELETE <num> - delete entry
477 }, MSGLEVEL_NEVER);
478
479 $win->print(q{FLAGS <num> <flags> - set flags
480
481 <flags> is a list of c (color), o (give op), v (give voice)
482 }, MSGLEVEL_NEVER);
483
484 $win->print(q{MASK <num> <mask> - set mask
485
486 <mask> is in the usual nick!user@host format
487 }, MSGLEVEL_NEVER);
488
489 $win->print(q{NET <num> <net> - set net
490
491 <net> is one of your defined ircnets or * for all
492 }, MSGLEVEL_NEVER);
493
494 }
495
496 # --------[ subcmd_friends_mask ]---------------------------------------
497
498 sub subcmd_friends_mask {
499 my($win, $num, $mask) = @_;
500
501 unless ($mask && defined $num) {
502 $win->print("Syntax: MASK <num> <mask>", MSGLEVEL_NEVER);
503 return;
504 }
505
506 unless (0 < $num && $num <= @friends) {
507 $win->print("Error: Element $num not in list", MSGLEVEL_NEVER);
508 return;
509 }
510
511 unless ($mask =~ /^.+!.+@.+$/) {
512 $win->print("Error: Mask $mask is not valid", MSGLEVEL_NEVER);
513 }
514
515 $friends[$num-1][1] = $mask;
516
517 return 1;
518 }
519
520 # --------[ subcmd_friends_net ]----------------------------------------
521
522 sub subcmd_friends_net {
523 my($win,$num,$net) = @_;
524 my($n);
525
526 unless ($net && defined $num) {
527 $win->print("Syntax: NET <num> <net>", MSGLEVEL_NEVER);
528 return;
529 }
530
531 unless (0 < $num && $num <= @friends) {
532 $win->print("Error: Element $num not in list", MSGLEVEL_NEVER);
533 return;
534 }
535
536 if ($net eq '*') {
537 # all is well
538 } elsif ($n = Irssi::chatnet_find($net)) {
539 $net = $n->{name};
540 } else {
541 $win->print("Error: No defined chatnet named $net",
542 MSGLEVEL_NEVER);
543 return;
544 }
545
546 $friends[$num-1][3] = $net;
547
548 return 1;
549 }
550
551 # --------[ ADDFRIEND ]-------------------------------------------------
552
553 # Usage: /ADDFRIEND <nick>|<mask> [<channel>|* [<net>|*]]
554 # [-mask host|normal|domain|full]
555 # [-flags <flags>]
556 sub cmd_addfriend {
557 my($param,$serv,$chan) = @_;
558 my(@param,@flags);
559 my($type) = Irssi::Irc::MASK_USER | Irssi::Irc::MASK_DOMAIN;
560 my($mask,$flags,$channel,$net);
561 my(@split) = split " ", $param;
562
563 while (@split) {
564 $_ = shift @split;
565 if (/^-m(ask)?$/) {
566 $_ = shift @split;
567 if (/^h(ost)?$/) {
568 $type = Irssi::Irc::MASK_HOST;
569 } elsif (/^n(ormal)?$/) {
570 $type = Irssi::Irc::MASK_USER
571 | Irssi::Irc::MASK_DOMAIN;
572 } elsif (/^d(omain)?$/) {
573 $type = Irssi::Irc::MASK_DOMAIN;
574 } elsif (/^f(ull)?$/) {
575 $type = Irssi::Irc::MASK_NICK
576 | Irssi::Irc::MASK_USER
577 | Irssi::Irc::MASK_HOST;
578 } else {
579 # fjekk
580 }
581 } elsif (/^-flags?$/) {
582 $flags = shift @split;
583 } else {
584 push @param, $_;
585 }
586 }
587 ($mask,$channel,$net) = @param;
588
589 unless ($mask) {
590 crap("/ADDFRIEND [-mask full|normal|host|domain] [-flags <[o][v][c]>] <nick|mask> [<channel> [<chatnet>]]]");
591 return;
592 }
593
594 $flags ||= "o";
595
596 unless ($channel) {
597 if ($chan) {
598 $channel = $chan->{name};
599 } else {
600 crap("/ADDFRIEND needs a channel.");
601 return;
602 }
603 }
604
605 unless ($net) {
606 if ($serv) {
607 $net = $serv->{chatnet};
608 } else {
609 crap("/ADDFRIEND needs a chatnet.");
610 return;
611 }
612 }
613
614 # is this a nick we need to expand?
615 unless ($mask =~ /.+!.+@.+/) {
616 my($nick);
617 if ($net ne '*') {
618 unless ($serv = Irssi::server_find_chatnet($net)) {
619 crap("Error locating server for $net.");
620 return;
621 }
622 } else {
623 unless ($serv) {
624 crap("Need a server for nick expansion");
625 return
626 }
627 }
628 if ($channel ne '*') {
629 unless ($chan = $serv->channel_find($channel)) {
630 crap("Error locating channel $channel.");
631 return;
632 }
633 } else {
634 unless ($chan) {
635 crap("Need a channel for nick expansion");
636 return;
637 }
638 }
639 unless ($nick = $chan->nick_find($mask)) {
640 crap("Error locating nick $mask.");
641 return;
642 }
643 $mask = Irssi::Irc::get_mask($nick->{nick}, $nick->{host}, $type);
644 }
645
646 for my $flag (split //, $flags) {
647 unless ($flag = $flaglong{$flag}) {
648 crap("Unknown flag [$flag]");
649 next;
650 }
651 push @flags, $flag;
652 $friends{$mask}{lc $net}{lc $channel}{$flag} = 1;
653 }
654
655 if (@flags) {
656 crap("Added %s for %s in %s on %s.",
657 join(",", @flags), $mask, $channel, $net);
658 }
659
660 save_friends(1);
661 }
662
663 # ======[ Setup ]=======================================================
664
665 # --------[ Register settings ]-----------------------------------------
666
667 Irssi::settings_add_bool('friends', 'friends_autosave', 1);
668 Irssi::settings_add_int('friends', 'friends_max_nicks', 10);
669 Irssi::settings_add_bool('friends', 'friends_show_check', 1);
670
671 Irssi::settings_add_str('friends', 'friends_nick_color', '');
672
673 # --------[ Register formats ]------------------------------------------
674
675 Irssi::theme_register(
676 [
677 'friends_crap',
678 '{line_start}{hilight Friends:} $0',
679
680 'friends_check',
681 '{line_start}{hilight Friends} checked: $0',
682
683 'friends_check_more',
684 '{line_start}{hilight Friends} checked: $0 (+$1 more)',
685
686 'friends_header',
687 '<%W$[2]0%n> <%W$[33]1%n> <%W$[13]2%n> <%W$[13]3%n> <%W$[5]4%n>',
688
689 'friends_line',
690 '[%R$[-2]0%n] $[35]1 $[15]2 $[15]3 $[7]4',
691
692 'friends_footer',
693 "\n".'%4 List contains $0 friends %>%n',
694
695 ]);
696
697 # --------[ Register signals ]------------------------------------------
698
699 Irssi::signal_add_first("send command", "sig_send_command");
700
701 Irssi::signal_add_last("massjoin", "sig_massjoin");
702 Irssi::signal_add_last("nick mode changed", "sig_nick_mode_changed");
703 Irssi::signal_add_last("channel sync", "sig_channel_sync");
704
705 Irssi::signal_add('setup saved', 'sig_setup_save');
706 Irssi::signal_add('setup reread', 'sig_setup_reread');
707
708 Irssi::signal_add('window changed', 'sig_window_changed');
709
710 Irssi::signal_add_first('message public', 'sig_message_public');
711 Irssi::signal_add_first('message irc action', 'sig_message_irc_action');
712
713 # --------[ Register commands ]-----------------------------------------
714
715 Irssi::command_bind('friends', 'cmd_friends');
716 Irssi::command_bind('addfriend', 'cmd_addfriend');
717
718 # --------[ Register timers ]-------------------------------------------
719
720 # --------[ Load config ]-----------------------------------------------
721
722 load_friends;
723
724 # ======[ END ]=========================================================
725
726 # Local Variables:
727 # header-initial-hide: t
728 # mode: header-minor
729 # end: