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 8use strict; 9use Irssi 20020427.2353; 10use Irssi::Irc; 11use Irssi::TextUI; 12 13use Data::Dumper; 14$Data::Dumper::Indent = 1; 15 16# ======[ Script Header ]=============================================== 17 18use 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 31my(%friends, @friends); 32 33my(%flagshort) = ( 34 op => 'o', 35 voice => 'v', 36 color => 'c', 37 ); 38my(%flaglong) = map { $flagshort{$_} => $_ } keys %flagshort; 39 40# ======[ Helper functions ]============================================ 41 42# --------[ crap ]------------------------------------------------------ 43 44sub crap { 45 my $template = shift; 46 my $msg = sprintf $template, @_; 47 Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'friends_crap', $msg); 48} 49 50# --------[ load_friends ]---------------------------------------------- 51 52sub 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 76sub 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 103sub is_friends_window { 104 my($win) = @_; 105 return $win->{name} eq '<Friends>'; 106} 107 108# --------[ get_friends_window ]---------------------------------------- 109 110sub 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 125sub 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 152sub 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 194sub 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 206sub 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 248sub 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 298sub sig_massjoin { 299 my($channel, $nicks) = @_; 300 check_friends($channel, @$nicks); 301} 302 303# --------[ sig_nick_mode_changed ]------------------------------------- 304 305sub 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 314sub sig_channel_sync { 315 my($channel) = @_; 316 check_friends($channel, $channel->nicks); 317} 318 319# --------[ sig_setup_reread ]------------------------------------------ 320 321sub sig_setup_reread { 322 load_friends; 323} 324 325# --------[ sig_setup_save ]-------------------------------------------- 326 327sub sig_setup_save { 328 my($mainconf,$auto) = @_; 329 save_friends($auto); 330} 331 332# --------[ sig_window_changed ]---------------------------------------- 333 334sub 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 343sub 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 369sub 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 399sub cmd_friends { 400 my($win) = get_friends_window; 401 update_friends_window(); 402} 403 404# --------[ subcmd_friends_channel ]------------------------------------ 405 406sub 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 426sub 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 446sub 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 468sub 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 498sub 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 522sub 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>] 556sub 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 667Irssi::settings_add_bool('friends', 'friends_autosave', 1); 668Irssi::settings_add_int('friends', 'friends_max_nicks', 10); 669Irssi::settings_add_bool('friends', 'friends_show_check', 1); 670 671Irssi::settings_add_str('friends', 'friends_nick_color', ''); 672 673# --------[ Register formats ]------------------------------------------ 674 675Irssi::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 699Irssi::signal_add_first("send command", "sig_send_command"); 700 701Irssi::signal_add_last("massjoin", "sig_massjoin"); 702Irssi::signal_add_last("nick mode changed", "sig_nick_mode_changed"); 703Irssi::signal_add_last("channel sync", "sig_channel_sync"); 704 705Irssi::signal_add('setup saved', 'sig_setup_save'); 706Irssi::signal_add('setup reread', 'sig_setup_reread'); 707 708Irssi::signal_add('window changed', 'sig_window_changed'); 709 710Irssi::signal_add_first('message public', 'sig_message_public'); 711Irssi::signal_add_first('message irc action', 'sig_message_irc_action'); 712 713# --------[ Register commands ]----------------------------------------- 714 715Irssi::command_bind('friends', 'cmd_friends'); 716Irssi::command_bind('addfriend', 'cmd_addfriend'); 717 718# --------[ Register timers ]------------------------------------------- 719 720# --------[ Load config ]----------------------------------------------- 721 722load_friends; 723 724# ======[ END ]========================================================= 725 726# Local Variables: 727# header-initial-hide: t 728# mode: header-minor 729# end: 730