#!/usr/local/bin/perl

# dsirc: dumb-mode small irc client in perl
# by orabidoo (roger.espel.llima@ens.fr)
#
# Copyright (C) 1995 Roger Espel Llima
#
# for a full-screen termcap interface, use it with ssfe
# (split-screen front end)
#
# use: dsirc [options] [nick [server[:port[:password]]]]
# options are:
#      -p = specify port number
#      -i = specify IRCNAME
#      -n = specify nickname (quite useless as an option)
#      -s = specify server (quite useless as an option)
#      -l = specify file to be loaded instead of ~/.sircrc.pl
#      -q = don't load ~/.sircrc
#      -r = raw mode (no control-char filtering)
#      -8 = 8-bit mode

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation. See the file LICENSE for more details.
#
# If you make improvements to sirc, please send me the modifications 
# (context diffs appreciated) and they might make it to the next release.
#
# For bug reports, comments, questions, email roger.espel.llima@ens.fr
#
# You can always find the latest version of sirc at the following URL:
# http://www.eleves.ens.fr:8080/home/espel/sirc.html

$version='2.101';
$date='16th Dec 1995';
$add_ons='';

{
  local($me)=(readlink($0));
  $me || ($me=$0);
  if ($me =~ /\/[^\/]+$/) {
    $my_path=$`;
    push (@INC, $my_path);
    @loadpath=($my_path);
  } else {
    $my_path='.';
    @loadpath=();
  }
}

$|=1;
push (@INC, $ENV{"HOME"});
unshift (@loadpath, $ENV{"HOME"}."/.sirc");
push (@loadpath, ".");
$nextfh="sircblah001";

if (!eval "require 'getopts.pl';") {
  print "\
Your perl interpreter is *really* screwed up: the getopts.pl library is not
even there! Have you even bothered to run 'install'?\n";
  exit;
}

if (-f $my_path."/sircsock.ph") {
  do $my_path."/sircsock.ph";
} elsif (-f $ENV{'HOME'}."/sircsock.ph") {
  do $ENV{'HOME'}."/sircsock.ph";
} elsif (!eval "require 'sys/socket.ph';") {
    print "\
Your perl installation is wrong somewhere, the sys/socket.ph include file
couldn't be found. Have you even bothered to run 'install'?\n";
    exit;
}

&Getopts('n:s:p:u:i:l:drq78');

$raw_mode=$opt_r || (! -t STDOUT);
$ansi=!$raw_mode && $ENV{"TERM"} =~ /^vt|^xterm|^ansi/i;
$server=$opt_s || $ARGV[1] || $ENV{"SIRCSERVER"} || $ENV{"IRCSERVER"} ||
                  "us.undernet.org";
$port0=$opt_p || $ENV{"SIRCPORT"} || $ENV{"IRCPORT"} || 6667;
$username=$opt_u || $ENV{"SIRCUSER"} || $ENV{"IRCUSER"} || (getpwuid($<))[0] ||
                $ENV{"USER"} || "blah";
$ircname=$opt_i || $ENV{"SIRCNAME"} || $ENV{"IRCNAME"} || "sirc user";
$nick=$opt_n || $ARGV[0] || $ENV{"SIRCNICK"} || $ENV{"IRCNICK"} || $username;
$finger=$ENV{"IRCFINGER"} || "keep your fingers to yourself";
$userinfo=$ENV{"USERINFO"} || "yep, I'm a user";
($server, $port, $pass)=split(/[\s:]+/, $server);
$port || ($port=$port0);
$server0=$server;
$no_rc=$opt_q;
$initfile=$opt_l || $ENV{'HOME'}."/.sircrc.pl";
$logfile=$ENV{'HOME'}."/sirc.log";
$seven_bit=!$opt_8;

chop($hostname=`hostname`);
$myaddr=(gethostbyname($hostname))[4];
$mynumber=unpack("N", $myaddr);

@ARGV=();  # ignore any more arguments

if (open(H, $my_path."/sirc.help") ||
    ((-f $my_path."/sirc.help.gz") && 
     open(H, "gzip -cd ".$my_path."/sirc.help.gz |"))) {
  $help=1;
  @help=<H>;
  close H;
  foreach(@help) {
    chop;
    s/\$version/$version/g;
    s/\$date/$date/g;
  }
} else {
  $help='';
  print "*** Warning: help file (".$my_path."/sirc.help) not found!\n";
}

sub exit {
  close LOG if $logging;
  exit 0;
}

$SIG{'PIPE'}='IGNORE';
$SIG{'QUIT'}='IGNORE';
$SIG{'INT'}='exit';

sub eq {
  local($a, $b)=@_;
  $a =~ tr/A-Z/a-z/;
  $b =~ tr/A-Z/a-z/;
  return ($a eq $b);
}

sub bindtoserver {
  local($blah, $addr, $otherend);
  @channels=(); $talkchannel='';
  %mode=(); $umode=''; %limit=(); %haveops=(); %chankey=(); $away='';
  $listmin=0; $listmax=100000; $listpat='';
  @waituh=(); @douh=(); @erruh=();
  $buffer{'S'}='';
  &dostatus;
  &tell("*** Connecting to $server, port $port...");
  $tryserver='';
  if ($server =~ /^\s*(\d+)\.(\d+)\.(\d+)\.(\d+)\s*$/) {
    $addr=pack("c4", $1, $2, $3, $4) ;
  } else {
    ($blah, $blah, $blah, $blah, $addr)=gethostbyname($server);
  }
  &print("*\cbE\cb* Server hostname not found"), return '' unless $addr; 
  $otherend=pack("S n a4 x8", &AF_INET, $port, $addr);
  &print("*\cbE\cb* Out of file descriptors.... duh"), return ''
    unless socket(S, &PF_INET, &SOCK_STREAM, 0);
  $SIG{'QUIT'}='sigquit';
  &print("*\cbE\cb* Can't connect to server: $!"), close(S),
    $SIG{'QUIT'}='IGNORE', return unless connect(S, $otherend);
  $SIG{'QUIT'}='IGNORE';
  select(S);
  $|=1;
  select(STDOUT);
  $connected=1;
  &sl("PASS $pass") if $pass;
  &sl("USER $username blah blah :$ircname");
  &sl("NICK $nick");
  @channels=(); $talkchannel=''; %mode=(); $umode=''; %limit=();
  %haveops=(); %chankey=();
}

sub gl {
  if ($buffer{$_[0]} =~ /^([^\n\r]*)\r?\n\r?/) {
    $buffer{$_[0]}=$';
    $_=$1."\n";
    return 1;
  }
  local($buf)=('');
  if (sysread($_[0], $buf, 4096)) {
    $buffer{$_[0]}.=$buf;
    if ($buffer{$_[0]} =~ /^([^\n\r]*)\r?\n\r?/) {
      $buffer{$_[0]}=$';
      $_=$1."\n";
      return 1;
    }
    return '';
  }
  $_='';
  return 1;
}

sub sl {
  &print("*\cbE\cb* Error writing to server: $!"), $connected=0
    unless print S $_[0]."\n";
}

sub dostatus {
  if ($ssfe) {
    local($t, $_)=($talkchannel, "`#ssfe#s [sirc]  ");
    $t =~ tr/A-Z/a-z/;
    $_.="*" if $umode =~ /o/;
    $_.="\@" if $t && $haveops{$t};
    $_.=$nick;
    $_.=" (+".$umode.")" if $umode;
    $_.=" [query: ${query}]" if $query;
    $_.=" (away)" if $away;
    if ($talkchannel ne '') {
      $_.=" on ".$talkchannel." (+".$mode{$t}.")";
      $_.=" <key: ".$chankey{$t}.">" if $chankey{$t};
      $_.=" <limit: ".$limit{$t}.">" if $limit{$t};
    }
    print $_, "\n";
  }
}

$bold="\c[[1m";
$underline="\c[[4m";
$reverse="\c[[7m";
$normal="\c[[m";
$cls="\c[[H\c[[2J";

sub enhance {
  local($what)=@_;
  $what =~ tr/\c@-\c^/@-^/;
  return "\cv".$what."\cv";
}

sub print {
  local($what)=@_;
  $what =~ s/\s+$//;
  # thanks to Toy (wacren@obspm.fr) for this translation
  $what =~ tr/\x80-\xff/\x00-\x1f !cLxY|$_ca<\-\-R_o+23\'mp.,1o>123?AAAAAAACEEEEIIIIDNOOOOO*0UUUUYPBaaaaaaaceeeeiiiidnooooo:0uuuuypy/
    if $seven_bit;
  if ($raw_mode) {
    $logging && print LOG $what."\n";
    print $what, "\n" || &exit;
  } elsif ($ansi) {
    # this is buggy if you combine effects
    $what =~ s/([\ca\cc-\ch\cj-\cu\cw-\c^])/&enhance($1)/eg;
    while ($what =~ /\cb/) {
      ($what =~ s/\cb([^\cb]*)\cb/$bold$1$normal/) || 
      $what =~ s/\cb/$bold/g;
    }
    while ($what =~ /\c_/) {
      ($what =~ s/\c_([^\c_]*)\c_/$underline$1$normal/) ||
      $what =~ s/\c_/$underline/g;
    }
    while ($what =~ /\cv/) {
      ($what =~ s/\cv([^\cv]*)\cv/$reverse$1$normal/) ||
      $what =~ s/\cv/$reverse/g;
    }
    $logging && print LOG $what."\n";
    print $what, $normal, "\n" || &exit;
  } else {
    $what =~ tr/\ca-\ch\cj-\c_//d;
    $logging && print LOG $what."\n";
    print $what, "\n" || &exit;
  }
}

sub tell {
  $silent || &print(@_);
}

sub dohooks {
  local($hooktype, @args)=@_;
  local(@hl);
  eval "\@hl=\@".$hooktype."_hooks;";
  foreach $h (@hl) {
    eval "&".$h."(\@args);";
    $@ =~ s/\n$//;
    &tell("*\cbE\cb* error in $hooktype hook &$h: $@") if ($@ ne '');
  }
}

sub dcerror {
  local($fh, $n)=($_[0], $dcnick{$_[0]});
  $n =~ tr/A-Z/a-z/;
  &tell("*\cbE\cb* DCC chat with $n lost");
  close($fh);
  delete $dcnick{$fh};
  delete $dcfh{$n};
}

sub dgsclose {
  local($sfh, $rfh)=@_;
  &tell("*\cbD\cb* DCC transfer with $dnick{$sfh} terminated; $dtransferred{$sfh} bytes transferred in ".(time-$dstarttime{$rfh}). " seconds");
  close($sfh);
  close($rfh);
  delete $dgrfh{$sfh};
  delete $dsrfh{$sfh};
  delete $dfile{$rfh};
  delete $destarttime{$rfh};
  delete $dtransferred{$sfh};
  delete $dnick{$sfh};
}

sub msg {
  local($towho, $what)=@_;
  print "`#ssfe#t/m ".$towho." \n" if ($ssfe && $towho !~ /^[\&\#]/);
  if ($towho =~ s/^=//) {
    local($n, $fh)=($towho);
    $n =~ tr/A-Z/a-z/;
    $fh=$dcfh{$n};
    if ($fh) {
      &dohooks("send_dcc_chat", $towho, $what);
      (print $fh $what."\n") || &dcerror($fh);
      &tell("|\cb$towho\cb| ".$what);
    } else {
      &tell("*\cbE\cb* No active DCC chat with $towho, sorry");
    }
  } else {
    $what=substr($what, 0, 485);
    &dohooks("send_text", $towho, $what);
    if (&eq($towho, $talkchannel)) {
      &tell("<\c_${nick}\c_> $what");
    } else {
      &tell(">\cb${towho}\cb< $what");
    }
    &sl("PRIVMSG $towho :$what");
  }
}

sub say {
  if ($talkchannel) {
    &msg($talkchannel, @_);
  } else {
    &tell("*\cbE\cb* Not on a channel");
  }
}

sub notice {
  local($towho, $what)=@_;
  $what=substr($what, 0, 485);
  &dohooks("send_notice", $towho, $what);
  &tell("-> -${towho}- $what");
  &sl("NOTICE $towho :$what");
}

sub describe {
  local($towho, $what)=@_;
  $what=substr($what, 0, 480);
  &dohooks("send_action", $towho, $what);
  if (&eq($towho, $talkchannel)) {
    &tell("* ${nick} $what");
  } else {
    &tell("*-> \cb${towho}\cb: $nick $what");
  }
  &sl("PRIVMSG ".$towho." :\caACTION".($what eq "" ? "" : " ").$what."\ca");
}

sub me {
  if ($talkchannel) {
    &describe($talkchannel, @_);
  } else {
    &tell("*\cbE\cb* Not on a channel");
  }
}

sub yetonearg {
  ($newarg, $args)=split(/ +/, $args, 2);
  $args =~ s/^://;
}

sub getarg {
  ($newarg, $args)=split(/ +/, $args, 2);
}

@weekdays=("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
@months=("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
	 "Oct", "Nov", "Dec");

sub date {
  local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)=localtime($_[0]);
  return sprintf("$weekdays[$wday] $months[$mon] $mday  %.2d:%.2d:%.2d",
		 $hour, $min, $sec);
}

sub ctcp {
# implemented ctcp's: CLIENTINFO, FINGER, USERINFO, VERSION, ACTION, PING, TIME, DCC
  local($towho, $args)=@_;
  local($to, $what, $newarg);
  $args =$1 if ($args =~ /^\001([^\001]*)\001$/);
  if (&eq($towho, $nick)) {
    $to="you";
  } else {
    $to=$towho;
  }
  ($what, $args)=split(/ +/, $args, 2);
  $what =~ tr/a-z/A-Z/;
  &dohooks("ctcp", $towho, $what, $args);
  return if $skip;
  local($a)=($args);
  $a && ($a=' '.$a);
  unless ($what eq 'ACTION' || $what eq 'PING' || $what eq 'DCC') { 
    &tell("*** $who did a CTCP ${what}$a to $to");
  }
  if ($what eq 'TIME') {
    local($t);
    $t=&date(time);
    &sl("NOTICE $who :\caTIME $t\ca");
  } elsif ($what eq 'CLIENTINFO') {
    &sl("NOTICE $who :\caCLIENTINFO ACTION, CLIENTINFO, DCC, ECHO, ERRMSG, FINGER, PING, USERINFO, VERSION\ca");
  } elsif ($what eq 'DCC') {
    &getarg;
    if ($newarg eq 'CHAT' || $newarg eq 'SEND') {
      local($dfile, $dhost, $dport, $dsize)=split(/ +/, $args, 4);
      $dfile=$1 if $dfile =~ m|/([^/]*)$|;
      $dfile =~ s/^\./_/;
      if ($dhost==2130706433 || !$dport>1024 || $dhost !~ /^\d+$/ ||
	  $dport !~ /^\d+$/) {
	&tell("*\cbE\cb* DCC $newarg ($dfile) from $who rejected");
      } else {
	&tell("*\cbD\cb* DCC $newarg ($dfile) from $who ".
	      ($dsize ? "(size: $dsize) " : "")."[$dhost, $dport]");
	if ($newarg eq 'CHAT') {
	  $dcoffered{$who}=$dhost." ".$dport;
	  &dohooks("dcc_request", "CHAT", $dhost, $dport);
	} else {
	  $dgoffered{$dhost." ".$dport." ".$dfile}=$who;
	  &dohooks("dcc_request", "SEND", $dhost, $dport, $dfile, $dsize);
	}
      }
    } else {
      &tell("*** $who did a CTCP ${what}$a to $to");
    }
  } elsif ($what eq 'FINGER') {
    &sl("NOTICE $who :\caFINGER $finger\ca");
  } elsif ($what eq 'USERINFO') {
    &sl("NOTICE $who :\caUSERINFO $userinfo\ca");
  } elsif ($what eq 'VERSION') {
    local($u)=$add_ons;
    $u =~ s/^\+//;
    $u =~ s/\+/ + /g;
    $u=" -- using $u" if $u;
    &sl("NOTICE $who :\caVERSION sirc ${version}, a \cbperl\cb client$u\ca");
  } elsif ($what eq 'PING') {
    &sl("NOTICE $who :\caPING $args\ca");
    &tell("*** $who pinged $to");
  } elsif ($what eq 'ECHO' || $what eq 'ERRMSG') {
    &sl("NOTICE $who :\ca$what $args\ca");
  } elsif ($what eq 'ACTION') {
    &dohooks("action", $towho, $args);
    if (&eq($towho, $nick)) {
      &tell("*> \cb${who}\cb $args");
    } elsif (&eq($towho, $talkchannel)) {
      &tell("* $who $args");
    } else {
      &tell("* ${who}/${towho} $args");
    }
  }
}

sub ctcpreply {
  local($ctcp, $rest)=split(/ +/, $_[1], 2);
  $ctcp =~ tr/a-z/A-Z/;
  &dohooks("ctcp_reply", $_[0], $ctcp, $rest);
  if ($ctcp eq 'PING') {
    $rest=(time - $rest)." seconds";
  }
  &tell("*** CTCP $ctcp reply from $who: $rest");
}

sub dosplat {
  $args =~ s/^\s*\*($|\s)/${talkchannel}${1}/ if $talkchannel;
}

sub expand {
  if ($_[0] eq '$') {
    return '$';
  } elsif ($_[0] =~ /^(\d+)$/) {
    return (split(/ +/, $args))[$1];
  } elsif ($_[0] =~ /^(\d+)-$/) {
    return (split(/ +/, $args, 1+$1))[$1];
  } else {
    return eval "\$$_[0]";
  }
}

sub docommand {
  local($line)=@_;
  local($noalias)=($line =~ s/^\///);
  local($silent)=(1) if $line =~ s/^\^//;
  local($cmd, $args)=split(/ +/, $line, 2);
  $cmd =~ tr/a-z/A-Z/;
  if (!$noalias && defined($aliases{$cmd})) {
    $line=$aliases{$cmd};
    $line.=($args ne '' ? " ".$args : "")
      unless ($line =~ s/\$(\$|\d+-?|\w+)/&expand($1)/eg);
    $line =~ s/^\///;
    $noalias=1 if $line =~ s/^\///;
    $silent=1 if $line =~ s/^\^//;
    ($cmd, $args)=split(/ +/, $line, 2);
    $cmd =~ tr/a-z/A-Z/;
  }
  if (!$noalias && defined($cmds{$cmd})) {
    eval $cmds{$cmd};
    $@ =~ s/\n$//;
    &tell("*\cbE\cb* error in $cmd: $@") if ($@ ne '');
  } elsif ($cmd eq 'ALIAS') {
    &getarg;
    if ($newarg =~ /^-/) {
      local($a)=($');
      if ($a eq '') {
	%aliases=();
	&tell("*** All aliases removed");
      } else {
	$a =~ tr/a-z/A-Z/;
	delete $aliases{$a};
	&tell("*** Alias $a removed");
      }
    } elsif ($newarg ne '') {
      $newarg =~ tr/a-z/A-Z/;
      if ($args ne '') {
	$aliases{$newarg}=$args;
	&tell("*** $newarg aliased to $args");
      } else {
	if (defined($aliases{$newarg})) {
	  &tell("*** $newarg is aliased to: $aliases{$newarg}");
	} else {
	  &tell("*** $newarg: no such alias");
	}
      }
    } else {
      foreach $a (keys(%aliases)) {
	&tell("*** $a is aliased to $aliases{$a}");
      }
    }
  } elsif ($cmd eq 'LOAD') {
    &getarg;
    if ($newarg ne '') {
      $newarg =~ s/^\~\//$ENV{'HOME'}\//;
      $newarg =~ s|^~([^/]+)(/.*)$|(getpwnam($1))[7].$2|e;
      if ($newarg !~ /\//) {
	foreach (@loadpath) {
	  $newarg="$_/$newarg", last if -f "$_/$newarg";
	}
      }
      if ($newarg ne '' && -f $newarg) {
	do $newarg;
	if ($@) {
	  $@ =~ s/\n$//;
	  &tell("*\cbE\cb* Load error on $newarg: $@");
	}
      } else {
	&tell("*\cbE\cb* $newarg: File not found");
      }
    } else {
      &tell("*\cbE\cb* Yeah, but what?");
    }
  } elsif ($cmd eq 'LOG') {
    &getarg;
    if ($newarg =~ /^on$/i) {
      $logging && close LOG;
      if (open(LOG, ">> ".$logfile)) {
	$logging=1;
	print LOG "*\cbL\cb* IRC Log started ".&date(time)."\n";
      } else {
	$logging='';
	&tell("*\cbE\cb* Can't write to logfile $logfile");
      }
    } elsif ($newarg =~ /^off$/i) {
      if ($logging) {
	print LOG "*\cbL\cb* Log ended ".&date(time)."\n";
	close LOG;
      }
      $logging='';
    } else {
      &tell("*\cbL\cb* Logging is ".($logging?"on":"off"));
    }
  } elsif ($cmd eq 'LOGFILE') {
    &getarg;
    if ($newarg ne '') {
      $newarg =~ s/^\~\//$ENV{'HOME'}\//;
      $newarg =~ s|^~([^/]+)(/.*)$|(getpwnam($1))[7].$2|e;
      $newarg =~ s/\/$//;
      $logfile=$newarg;
    } else {
      &tell("*\cbL\cb* logfile is $logfile");
    }
  } elsif ($cmd eq 'NOTIFY' || $cmd eq 'N') {
    if ($args eq '-') {
      &tell("*** Notify list cleared");
      %notify=();
    } elsif ($args eq '') {
      local($l)='';
      foreach (grep($notify{$_}, keys %notify)) {
	&tell("*** Currently present: $l"), $l='' if (length($l)>450);
	$l.=$_." ";
      }
      $l && &tell("*** Currently present: $l");
      $l='';
      foreach (grep(!$notify{$_}, keys %notify)) {
	&tell("*** Currently absent: $l"), $l='' if (length($l)>450);    
	$l.=$_." ";
      }
      $l && &tell("*** Currently absent: $l");
    } else {
      local($w, $n);
      foreach $w (split(/ +/, $args)) {
	if ($w =~ s/^-//) {
	  ($n)=(grep(&eq($_, $w), keys(%notify)), '');
	  $n ne '' && delete $notify{$n};
	  &tell("*** $w removed from notify list");
	} else {
	  $notify{$w}='0';
	  &tell("*** $w added to notify list");
	  $newisons=1;
	}
      }
    }
  } elsif ($cmd eq 'IGNORE' || $cmd eq 'IG') {
    &getarg;
    if ($newarg eq '-') {
      &tell("*** Ignore list cleared");
      @ignore=();
    } elsif ($newarg eq '') {
      local($p);
      &tell("*** You're ignoring:");
      foreach (@ignore) {
	$p=$_;
	$p =~ s/\\//g;
	$p =~ s/\.\*/*/g;
	&tell("*** $p");
      }
    } else {
      local($d, $p)=('');
      $d=1 if $newarg =~ s/^-//;
      if ($newarg =~ /\!.*\@/) {
      } elsif ($newarg !~ /[\@\!]/) {
	$newarg.="!*";
      } elsif ($newarg =~ /\@/) {
	$newarg="*!".$newarg;
      } else {
	$newarg.="\@*";
      }
      $p=$newarg;
      $newarg =~ s/([^\\])\./$1\\./g;
      $newarg =~ s/\*/\.\*/g;
      $newarg =~ s/([^\.\*\\\w])/\\$1/g;
      if ($d) {
        &tell("*** Removing $p from the ignore list");
	@ignore=grep(!&eq($_, $newarg), @ignore);
      } else {
        &tell("*** Ignoring $p ... what a relief!");
	push(@ignore, $newarg);
      }
    }
  } elsif ($cmd eq 'ECHO') {
    &print($args);
  } elsif ($cmd eq 'CLEAR' || $cmd eq 'CL') {
    print $cls if $ansi;
    print "`#ssfe#l\n" if $ssfe;
  } elsif ($cmd eq 'EVAL') {
    eval ($args);
    $@ =~ s/\n$//;
    &tell("*\cbE\cb* eval error: $@") if ($@ ne '');
  } elsif ($cmd eq 'HELP') {
    if ($help) {
      $args =~ tr/a-z/A-Z/;
      $args='MAIN' unless $args;
      if ($args !~ /[A-Z0-9]+/) {
	&tell("*\cbH\cb* Use: /HELP <topic>");
      } else {
	local($found)=('');
	dohelp: foreach(@help) {
	  if (/^\@/) {
	    last dohelp if $found;
	    if (/^\@${args}$/i) {
	      $found=1;
	      &tell("*\cbH\cb* Help on $args") if ($args ne 'MAIN');
	    }
	  } else {
	    &tell("*\cbH\cb* $_") if $found;
	  }
	}
	&tell("*\cbH\cb* Unknown help topic; try /help") unless $found;
      }
    } else {
      &tell("*\cbH\cb* Help not available");
    }
  } elsif ($cmd eq 'VERSION') {
    &tell("*** \cbsirc\cb version $version, written in \cbperl\cb by \cborabidoo\cb");
    $connected==2 && &sl("VERSION $args");
  } elsif ($cmd eq 'CD') {
    &getarg;
    if ($newarg ne '') {
      $newarg.="/" if $newarg !~ /\/$/;
      $newarg =~ s/^\~\//$ENV{'HOME'}\//;
      $newarg =~ s|^~([^/]+)(/.*)$|(getpwnam($1))[7].$2|e;
      $newarg =~ s/\/$//;
      chdir($newarg) || &tell("*\cbE\cb* Can't chdir to $newarg");
    }
    local($cwd); chop($cwd=`pwd`); 
    &tell("*** Current directory is $cwd");
  } elsif ($cmd eq 'SYSTEM') {
      system($args);
  } elsif ($cmd eq 'BYE' || $cmd eq 'QUIT' || $cmd eq 'EXIT' ||
	   $cmd eq 'SIGNOFF') {
    $args || ($args="using \cbsirc\cb version ${version}${add_ons}");
    &sl("QUIT :$args"), sleep 1 if $connected;
    &exit;
  } elsif ($cmd eq 'SERVER') {
    $args=$1 if $args =~ /^\s*(.*)\s*$/;
    $args=$server0 if $args eq '0';
    if ($args eq '') {
      &tell("*** Your current server is $server");
    } else {
      ($tryserver, $port, $pass)=split(/[\s:]+/, $args);
      if ($tryserver =~ /^([^\@]+)\@/) {
	$tryserver=$';
	$nick=$1;
      }
      $port || ($port=$port0);
      &sl("QUIT :changing servers"), close S if $connected;
      $connected=0;
    }
  } elsif ($cmd eq 'MSG' || $cmd eq 'M') {
    &dosplat;
    if ($args) {
      ($newarg, $args)=split(/ /, $args, 2);
      if ($newarg =~ /^\=/ || $connected>1) {
	&msg($newarg, $args);
	$lastmsg=$newarg;
      } else {
      &tell("*** You're not connected to a server");
      }
    } else {
      &tell("*\cbE\cb* You must specify a nick or channel!");
    }
  } elsif ($connected<2) {
    &tell("*** You're not connected to a server");
  } elsif ($cmd eq 'DCC') {
    &getarg;
    if ($newarg =~ /^chat$/i) {
      &getarg;
      local($n)=grep(&eq($newarg, $_), keys(%dcoffered));
      if ($n) {
	local($dcadr, $dcport)=split(/ +/, $dcoffered{$n});
	local($otherend);
	delete $dcoffered{$n};
	$dcadr=pack("N", $dcadr);
	$otherend=pack("S n a4 x8", &AF_INET, $dcport, $dcadr);
	$nextfh++;
	&tell("*\cbE\cb* Out of file descriptors.... duh"), return
	  unless socket($nextfh, &PF_INET, &SOCK_STREAM, 0);
	$SIG{'QUIT'}='sigquit';
	&print("*\cbE\cb* Can't connect: $!"), close($nextfh), 
	  $SIG{'QUIT'}='IGNORE', return unless connect($nextfh, $otherend);
	$SIG{'QUIT'}='IGNORE';
	select($nextfh); $|=1; select(STDOUT);
	$dcnick{$nextfh}=$n;
	&tell("*\cbD\cb* DCC CHAT with $n established");
	$n =~ tr/A-Z/a-z/;
	$dcfh{$n}=$nextfh;
      } elsif (grep (&eq($newarg, $dcwait{$_}), keys(%dcwait))) {
	&tell("*\cbE\cb* DCC CHAT request to $newarg already sent");
      } elsif (grep(&eq($newarg, $dcnick{$_}), keys(%dcnick))) {
	&tell("*\cbE\cb* DCC CHAT with $newarg already established");
      } elsif ($newarg) {
	$nextfh++;
	local($thisend, $blah, $myport);
	$thisend=pack("S n a4 x8", &AF_INET, 0, $myaddr);
	&tell("*\cbE\cb* Out of file descriptors.... duh"), return
	  unless socket($nextfh, &PF_INET, &SOCK_STREAM, 0);
	&tell("*\cbE\cb* Can't bind local socket!"), close $nextfh, return
	  unless bind($nextfh, $thisend);
	&tell("*\cbE\cb* Can't listen to socket!"), close $nextfh, return
	  unless listen($nextfh, 5);
	($blah, $myport)=unpack("S n", getsockname($nextfh));
	$dcwait{$nextfh}=$newarg;
	&sl("PRIVMSG $newarg :\caDCC CHAT chat $mynumber $myport\ca");
	&tell("*\cbD\cb* Sent DCC CHAT request to $newarg");
      } else {
	&tell("*** I need a nick");
      }
    } elsif ($newarg =~ /^close$/i) {
      &getarg;
      if ($newarg =~ /^chat$/i) {
	&getarg;
	local($n)=($newarg);
	$newarg =~ tr/A-Z/a-z/;
	local($fh)=($dcfh{$newarg});
	local($nn)=(grep(&eq($_, $newarg), keys(%dcoffered)));
	if ($nn) {
	  &tell("*\cbD\cb* Forgetting offered DCC CHAT from $nn");
	  delete $dcoffered{$nn};
	} elsif ($fh) {
	  &tell("*\cbD\cb* Closing DCC CHAT connection with $n");
	  close($fh);
	  delete $dcnick{$fh};
	  delete $dcfh{$newarg};
	} elsif (($fh)=grep(&eq($dcwait{$_}, $n), keys (%dcwait)), $fh) {
	  close($fh);
	  delete $dcwait{$fh};
	  &tell("*\cbD\cb* Closing listening DCC CHAT with $n");
	} else {
	  $n && &tell("*\cbE\cb* No DCC CHAT connection with $n");
	}
      } elsif ($newarg =~ /^get$/i) {
	&getarg;
	local($found)=('');
	foreach $i (keys(%dgoffered)) {
	  if (&eq($dgoffered{$i}, $newarg) && (!$args ||
	      &eq($args, (split(/ +/, $i))[2]))) {
	    &tell("*\cbE\cb* Forgetting pending DCC GET from $newarg");
	    delete $dgoffered{$i};
	    $found=1;
	  }
	}
	foreach $sfh (grep(&eq($newarg, $dnick{$_}), keys(%dnick))) {
	  if (!$found && $dgrfh{$sfh}) {
	    local($fh)=($dgrfh{$sfh});
	    next if $args && ($args ne $dfile{$fh});
	    &tell("*\cbE\cb* Closing DCC GET connection with $newarg");
	    $found=1;
	    close $sfh;
	    close $fh;
	    delete $dgrfh{$sfh};
	    delete $dfile{$fh};
	    delete $dstarttime{$fh};
	    delete $dtransferred{$sfh};
	    delete $dnick{$sfh};
	  }
	}
	&tell("*\cbE\cb* No DCC GET connection with $newarg") unless($found);
      } elsif ($newarg =~ /^send$/i) {
        &getarg;
	local($n, $found, $fh)=($newarg, '');
	&getarg;
	$newarg =~ s/(\W)/\\$1/g;
	foreach $sfh (keys(%dswait), keys(%dsrfh)) {
	  next unless &eq($dnick{$sfh}, $n);
	  $fh=$dswait{$sfh} || $dsrfh{$sfh} || next;
	  if ($newarg eq '' || $dfile{$fh} =~ /^${newarg}$/ ||
	      $dfile{$fh} =~ /\/${newarg}$/) {
	    &tell("*\cbD\cb* DCC SEND connection with $n closed");
	    close($sfh);
	    close($fh);
	    delete $dswait{$sfh};
	    delete $dsrfh{$sfh};
	    delete $dfile{$fh};
	    delete $dstarttime{$fh};
	    delete $dtransferred{$sfh};
	    delete $dnick{$sfh};
	    $found=1;
	  }
	}
	&tell("*\cbE\cb* No DCC SEND connection with $n") unless($found);
      } else {
	&tell("*\cbE\cb* Unknown DCC type");
      }
    } elsif ($newarg =~ /^rename$/i) {
      local($found, $n)=('');
      &getarg;
      $n=$newarg;
      &getarg;
      $args=$newarg, $newarg='' if $args eq '';
      &tell("*\cbE\cb* I need a filename :p"), return if $args eq '';
      $args =~ s/^\~\//$ENV{'HOME'}\//;
      $args =~ s|^~([^/]+)(/.*)$|(getpwnam($1))[7].$2|e;
      $args =~ s/\/$//;
      foreach $i (keys(%dgoffered)) {
	if (&eq($dgoffered{$i}, $n) && (!$newarg ||
	    &eq($newarg, (split(/ +/, $i))[2]))) {
	  local($m, $p, $f)=split(/ +/, $i);
	  delete $dgoffered{$i};
	  $dgoffered{$m." ".$p." ".$args}=$n;
	  &tell("*\cbD\cb* Renaming \"$f\" (offered by $n) to \"$args\"");
	  $found=1;
	  last;
	}
      }
      &tell("*\cbE\cb* No such file offered by $n") unless $found;
    } elsif ($newarg =~ /^get$/i) {
      &getarg;
      local($n)=grep((&eq($newarg, $dgoffered{$_}) && (!$args || 
		      &eq($args, (split(/ +/, $_))[2]))),
		     keys(%dgoffered));
      if ($n) {
	local($dgadr, $dgport, $file)=split(/ +/, $n);
	local($fh, $otherend);
	$n=(delete $dgoffered{$n});
	$nextfh++;
	$fh=$nextfh;
	&print("*\cbE\cb* Can't write to file $file"), return
	  unless open($fh, "> ".$file);
	$dgadr=pack("N", $dgadr);
	$otherend=pack("S n a4 x8", &AF_INET, $dgport, $dgadr);
	$nextfh++;
	&tell("*\cbE\cb* Out of file descriptors.... duh"), return
	  unless socket($nextfh, &PF_INET, &SOCK_STREAM, 0);
	$SIG{'QUIT'}='sigquit';
	&print("*\cbE\cb* Can't connect: $!"), close($nextfh), 
	  $SIG{'QUIT'}='IGNORE', return unless connect($nextfh, $otherend);
	$SIG{'QUIT'}='IGNORE';
	select($nextfh); $|=1; select(STDOUT);
	$dgrfh{$nextfh}=$fh;
	$dnick{$nextfh}=$n;
	$dfile{$fh}=$file;
	$dstarttime{$fh}=time;
	$dtransferred{$nextfh}='0';
	$dnick{$nextfh}=$n;
	&tell("*\cbD\cb* DCC GET connection with $n established");
      } else {
	if ($newarg) {
	  &tell("*\cbE\cb* No pending DCC GET from $newarg");
	} else {
	  &tell("*\cbE\cb* Uhm, who from?");
	}
      }
    } elsif ($newarg =~ /^list$/i || $newarg eq '') {
      &tell("*\cbD\cb* List of DCC connections:");
      foreach $n (keys(%dcfh)) {
	&tell("*\cbD\cb* Established DCC CHAT with $n");
      }
      foreach $n (keys(%dcoffered)) {
	&tell("*\cbD\cb* DCC CHAT offered by $n");
      }
      foreach $f (keys(%dcwait)) {
	&tell("*\cbD\cb* DCC CHAT offered to ".$dcwait{$f});
      }
      foreach $i (keys(%dgoffered)) {
	&tell("*\cbD\cb* DCC GET ".$i." offered by ".$dgoffered{$i});
      }
      foreach $s (keys(%dgrfh)) {
        local($f)=($dgrfh{$s});
	&tell("*\cbD\cb* DCC GET \"".$dfile{$f}."\" established with ".$dnick{$s}.", ".$dtransferred{$s}." bytes read in ".(time-$dstarttime{$f})." seconds.");
      }
      foreach $s (keys(%dswait)) {
        local($f)=($dswait{$s});
	&tell("*\cbD\cb* DCC SEND \"".$dfile{$f}."\" offered to ".$dnick{$s});
      }
      foreach $s (keys(%dsrfh)) {
        local($f)=($dsrfh{$s});
	&tell("*\cbD\cb* DCC SEND \"".$dfile{$f}."\" established with ".$dnick{$s}.", ".$dtransferred{$s}." bytes sent in ".(time-$dstarttime{$f})." seconds.");
      }
    } elsif ($newarg =~ /^send$/i) {
      local($n, $f)=split(/ +/, $args);
      local($tf, $sz, $fh, $thisend, $blah, $myport)=($f);
      $nextfh++;
      $fh=$nextfh++;
      $f =~ s/^\~\//$ENV{'HOME'}\//;
      $f =~ s|^~([^/]+)(/.*)$|(getpwnam($1))[7].$2|e;
      $f =~ s/\/$//;
      &tell("*\cbE\cb* Can't open file $f"), return unless open($fh, "< ".$f);
      $thisend=pack("S n a4 x8", &AF_INET, 0, $myaddr);
      &tell("*\cbE\cb* Out of file descriptors.... duh"), close $fh,
	return unless socket($nextfh, &PF_INET, &SOCK_STREAM, 0);
      &tell("*\cbE\cb* Can't bind local socket!"), close $nextfh, close $fh,
        return unless bind($nextfh, $thisend);
      &tell("*\cbE\cb* Can't listen to socket!"), close $nextfh, close $fh,
	return unless listen($nextfh, 5);
      ($blah, $myport)=unpack("S n", getsockname($nextfh));
      $dswait{$nextfh}=$fh;
      $tf=$1 if $dfile =~ m|/([^/]*)$|;
      $sz=(-s $f);
      &sl("PRIVMSG $n :\caDCC SEND $tf $mynumber $myport $sz\ca");
      &tell("*\cbD\cb* Sent DCC SEND request to $n");
      $dfile{$fh}=$f;
      $dswait{$nextfh}=$fh;
      $dnick{$nextfh}=$n;
    } else {
      &tell("*** I can \"only\" do DCC CHAT, GET, SEND, CLOSE, RENAME and LIST, *sheesh*");
    }
  } elsif ($cmd eq 'AWAY') {
    if ($args) {
      &sl("AWAY :$args");
    } else {
      &sl("AWAY");
    }
  } elsif ($cmd eq 'NEXT') {
    if ($#channels>0) {
      $talkchannel=shift(@channels);
      push(@channels, $talkchannel);
      !$ssfe && &tell("*** Talking to $talkchannel now");
      &dostatus;
    }
  } elsif ($cmd eq 'A') {
    if ($lastmsg) {
      &msg($lastmsg, $args);
    } else {
      &tell("*\cbE\cb* How about sending a first /m to someone?");
    }
  } elsif ($cmd eq 'SAY' || $cmd eq '') {
    &say($args);
  } elsif ($cmd eq 'NOTICE' || $cmd eq 'NO') {
    &dosplat;
    if ($args) {
      ($newarg, $args)=split(/ /, $args, 2);
      &notice($newarg, $args);
    } else {
      &tell("*\cbE\cb* You must specify a nick or channel!");
    }
  } elsif ($cmd eq 'DESCRIBE' || $cmd eq 'DE') {
    &dosplat;
    if ($args) {
      ($newarg, $args)=split(/ /, $args, 2);
      &describe($newarg, $args);
      $lastmsg=$newarg;
    } else {
      &tell("*\cbE\cb* You must specify a nick or channel!");
    }
  } elsif ($cmd eq 'KICK' || $cmd eq 'K') {
    &dosplat;
    &getarg;
    local($c)=($talkchannel);
    if ($newarg =~ /^[\#\&]/) {
      $c=$newarg;
      &getarg;
    }
    if ($newarg) {
      $args || ($args=$nick);
      &sl("KICK $c $newarg :$args");
    } else {
      &tell("*\cbE\cb* You must specify a nick!");
    }
  } elsif ($cmd eq 'INVITE' || $cmd eq 'INV' || $cmd eq 'I') {
    local(@ns)=split(/ +/, $args);
    local($l, $c)=(pop(@ns), $talkchannel);
    if ($l =~ /^[\#\&]/) {
      $c=$l;
    } else {
      $l && push(@ns, $l);
    }
    foreach (@ns) {
      &sl("INVITE $_ $c");
    }
  } elsif ($cmd eq 'CTCP') {
    &dosplat;
    if ($args) {
      &getarg;
      if ($newarg) {
        local($towho)=($newarg);
        &getarg;
	$newarg =~ tr/a-z/A-Z/;
        &sl("PRIVMSG ".$towho." :\ca".$newarg." ".$args."\ca");
      }
    } else {
      &tell("*\cbE\cb* You must specify a nick or channel!");
    }
  } elsif ($cmd eq 'PING' || $cmd eq 'P') {
    &dosplat;
    if ($args) {
      &getarg;
      local($t)=time;
      $newarg && &sl("PRIVMSG ".$newarg." :\caPING $t\ca");
    } else {
      &tell("*\cbE\cb* You must specify a nick or channel!");
    }
  } elsif ($cmd eq 'ME') {
    if ($talkchannel) {
      &describe($talkchannel, $args);
    } else {
      &tell("*\cbE\cb* Not on a channel");
    }
  } elsif ($cmd eq 'TOPIC' || $cmd eq 'T') {
    &dosplat;
    local($c)=($talkchannel);
    if ($args =~ /^[\#\&]/) {
      &getarg;
      $c=$newarg;
    }
    if ($args) {
      &sl("TOPIC $c :$args");
    } else {
      &sl("TOPIC $c");
    }
  } elsif ($cmd eq 'LEAVE' || $cmd eq 'PART' || $cmd eq 'HOP') {
    &dosplat;
    $args=$talkchannel if $args eq '';
    &sl("PART $args");
  } elsif ($cmd eq 'LL') {
    if ($talkchannel) {
      &sl("WHO $talkchannel");
    } else {
      &tell("*\cbE\cb* Not on a channel");
    }
  } elsif ($cmd eq 'O' || $cmd eq 'OP') {
    local($c, $n, $l)=($talkchannel, 0, '');
    &getarg, $c=$newarg if ($args =~ /^[\#\&]/);
    local(@ppl)=split(/ +/, $args);
    foreach (@ppl) {
      if ($n<4) {
	$l .= " ".$_;
	$n++;
      } else {
	&sl("MODE $c +oooo $l");
	$l=$_;
	$n=1;
      }
    }
    $l && &sl("MODE $c +oooo $l");
  } elsif ($cmd eq 'D' || $cmd eq 'DEOP') {
    local($c, $n, $l)=($talkchannel, 0, '');
    &getarg, $c=$newarg if ($args =~ /^[\#\&]/);
    local(@ppl)=split(/ +/, $args);
    foreach (@ppl) {
      if ($n<4) {
	$l .= " ".$_;
	$n++;
      } else {
	&sl("MODE $c -oooo $l");
	$l=$_;
	$n=1;
      }
    }
    $l && &sl("MODE $c -oooo $l");
  } elsif ($cmd eq 'W' || $cmd eq 'WHOIS') {
    if ($args eq '') {
      &sl("WHOIS $nick");
    } else {
      &sl("WHOIS $args");
    }
  } elsif ($cmd eq 'WI') {
    &getarg;
    &sl("WHOIS $newarg $newarg");
  } elsif ($cmd eq 'WHO') {
    &dosplat;
    if ($args =~ /^[\s\*]*$/) {
      &tell("*** Uhm, better not");
    } else {
      &sl("WHO $args");
    }
  } elsif ($cmd eq 'JOIN' || $cmd eq 'J') {
    unless($args =~ /^[\#\&]/) {
      $args='#'.$args;
    }
    if (grep(&eq($_, $args), @channels)) {
      &tell("*** Talking to $args now");
      $talkchannel=$args;
      &dostatus;
    } else {
      &sl("JOIN $args");
    }
  } elsif ($cmd eq 'QUOTE') {
    $args && &sl($args);
  } elsif ($cmd eq 'UMODE') {
    &sl("MODE $nick $args");
  } elsif ($cmd eq 'MO') {
    if ($talkchannel) {
      &sl("MODE $talkchannel $args");
    } else {
      &tell("*\cbE\cb* You're not on any channel anyway");
    }
  } elsif ($cmd eq 'QUERY' || $cmd eq 'Q') {
    if ($args) {
      $args =~ s/\s+$//;
      $query=$args;
      &tell("*** Starting conversation with $query");
    } elsif ($query) {
      &tell("*** Ending conversation with $query");
      $query='';
    } else {
      &tell("*** You aren't querying anyone :p");
    }
    &dostatus;
  } elsif ($cmd eq 'LIST') {
    &dosplat;
    $listmin=0;
    $listmax=100000;
    $listpat='';
    if ($args =~ /\*/ || $args =~ /-m[ia][nx]\s/i) {
      while(&getarg, $newarg ne '') {
	if ($newarg =~ /^-min$/i) {
	  &getarg;
	  $listmin=$newarg if $newarg>0;
	} elsif ($newarg =~ /^-max$/i) {
	  &getarg;
	  $listmax=$newarg if $newarg>0;
	} else {
	  $newarg =~ s/([^\\])\./$1\\./g;
	  $newarg =~ s/\*/\.\*/g;
	  $newarg =~ s/([^\.\*\\\w])/\\$1/g;
	  $listpat=$newarg;
	}
      }
      &sl("LIST");
    } else {
      &sl($line);
    }
  } elsif ($cmd eq 'RPING') {
    &getarg;
    &sl("RPING ".$newarg." ".time);
  } elsif ($cmd eq 'KILL') {
    &getarg;
    if ($newarg) {
      $args || ($args=$nick);
      &sl("KILL $newarg :$args");
    } else {
      &tell("*\cbE\cb* You must specify a nick!");
    }
  } elsif ($cmd eq 'MODE' || $cmd eq 'NAMES') {
    &dosplat;
    &sl("$cmd $args");
  } elsif ($cmd eq 'WHOWAS' || $cmd eq 'ADMIN' || $cmd eq 'STATS' ||
           $cmd eq 'INFO' || $cmd eq 'LIST' || $cmd eq 'LUSERS' ||
	   $cmd eq 'OPER' || $cmd eq 'CONNECT' || $cmd eq 'SQUIT' ||
	   $cmd eq 'DIE' || $cmd eq 'LINKS' || $cmd eq 'NOTE' ||
	   $cmd eq 'WALLOPS' || $cmd eq 'NICK' || $cmd eq 'MOTD' ||
	   $cmd eq 'TIME' || $cmd eq 'TRACE' || $cmd eq 'USERS' ||
	   $cmd eq 'SILENCE' || $cmd eq 'MAP' || $cmd eq 'UPING' ||
	   $cmd eq 'REHASH') {
    &sl($line);
  } else {
    &tell("*\cbE\cb* Unknown command: $cmd");
  }
}

sub douserline {
  local($line)=@_;
  if ($line =~ /^\@ssfe\@/) {
    $ssfe=1;
    $raw_mode=1;
    $add_ons.="+ssfe";
    &dostatus;
  } elsif ($line =~ s/^\///) {
    &docommand($line);
  } elsif ($query ne '') {
    &msg($query, $line);
  } else {
    &say($line);
  }
}

sub getuserline {
  local($skip)=('');
  &dohooks("input", $_[0], $_[1]);
  return if $skip;
  print $_[0];
  print "\n" if $raw_mode;
  print "`#ssfe#p".$_[1]."\n" if $ssfe;
  while (($_=<STDIN>) ne '') {
    if (/^\@ssfe\@/) {
      $ssfe || ($add_ons.="+ssfe");
      $ssfe=1;
      $raw_mode=1;
      &dostatus;
    } else {
      &exit if $_ eq '';
      chop;
      return;
    }
  }
  &exit;
}

%cmds=();
sub addcmd {
  local($cmd)=($_[0]);
  $cmd =~ tr/a-z/A-Z/;
  $cmds{$cmd}="&cmd_".$_[0].";";
}

sub addhelp {
  local($cmd, $txt)=@_;
  $cmd =~ tr/A-Z/a-z/;
  push(@help, "\@".$cmd);
  foreach (split(/\n/, $txt)) {
    s/\$v/$version/g;
    s/\$d/$date/g;
    push (@help, $_);
  }
}

@hooks=("action", "ctcp", "ctcp_reply", "dcc_chat", "dcc_request", "input", 
	"invite", "join", "kick", "leave", "mode", "msg", "nick", "notice", 
	"notify_signoff", "notify_signon", "public", "raw_irc", "send_action",
	"send_dcc_chat", "send_text", "send_notice", "signoff", "topic");

sub addhook {
  local($type, $name)=@_;
  $type =~ tr/A-Z/a-z/;
  $name="hook_".$name;
  if ($type =~ /^\d\d\d$/ || grep(($_ eq $type), @hooks)) {
    ($type =~ /^\d\d\d$/) && ($type="num_".$type);
    eval "*ugly_hack_hooks=*${type}_hooks;";
    unless (grep(($_ eq $name), @ugly_hack_hooks)) {
      push(@ugly_hack_hooks, $name);
    }
  } else {
    &tell("*\cbE\cb* $type: no such hook");
  }
}

sub remhook {
  local($type, $name)=@_;
  $type =~ tr/A-Z/a-z/;
  $name="hook_".$name;
  if ($type =~ /^\d\d\d$/ || grep(($_ eq $type), @hooks)) {
    ($type =~ /^\d\d\d$/) && ($type="num_".$type);
    eval "*ugly_hack_hooks=*${type}_hooks;";
    @ugly_hack_hooks=grep(($_ ne $name), @ugly_hack_hooks);
  } else {
    &tell("*\cbE\cb* $type: no such hook");
  }
}

sub userhost {
  push (@waituh, $_[0]);
  push (@douh, $_[1]);
  push (@erruh, $_[2]);
  &sl("USERHOST $_[0]");
}

sub timer {
  local(@t, @a)=();
  local($t)=$_[0]+time;
  while ($#timers>=0 && $timers[0]<=$t) {
    push (@t, shift(@timers));
    push (@a, shift(@timeactions));
  }
  @timers=(@t, $t, @timers);
  @timeactions=(@a, $_[1], @timeactions);
}

sub disappeared {
  local($n)=(grep(&eq($_, $_[0]), keys(%notify)), '');
  if ($n ne '' && $notify{$n}>0) {
    local($silent)=0;
    &dohooks("notify_signoff", $_[0]);
    &tell("*\cb(\cb* Signoff by $_[0] detected");
    $notify{$n}=0;
  }
}

sub appeared {
  local($t, $n)=(time, grep(&eq($_, $_[0]), keys(%notify)), '');
  if ($n ne '') {
    if ($notify{$n}==0) {
      local($silent)=0;
      &dohooks("notify_signon", $_[0]);
      &tell("*\cb)\cb* Signon by $_[0] detected!");
    }
    $notify{$n}=$t;
  }
}

$lastsendison=0;
sub send_isons {
  local($l)=('');
  foreach (keys %notify) {
    &sl("ISON :".$l), $l='' if (length($l)>500);
    $l.=$_." ";
  }
  &sl("ISON :".$l) if $l;
  $lastsendison=time;
  $newisons='';
  $checkisons=1;
}

sub signoffs {
  foreach (keys %notify) {
    if ($notify{$_}>0 && $notify{$_}<$lastsendison) {
      local($silent)=0;
      &dohooks("notify_signoff", $_);
      &tell("*\cb(\cb* Signoff by $_ detected");
      $notify{$_}=0;
    }
  }
  $checkisons='';
}

sub modestripper {
  local($chnl, $what)=@_;
  $chnl =~ tr/A-Z/a-z/;
  local($how, $modes, @args)=('+', split(/ +/, $what));
  foreach $m (split(//, $modes)) {
    if ($m =~ /[\-\+]/) {
      $how=$m;
    } elsif ($m =~ /[vb]/) {
      shift(@args);
    } elsif ($m eq 'k') {
      $how eq '+' ? ($chankey{$chnl}=shift(@args)) : delete $chankey{$chnl};
    } elsif ($m eq 'l') {
      $how eq '+' ? ($limit{$chnl}=shift(@args)) : delete $limit{$chnl};
    } elsif ($m eq 'o') {
      $haveops{$chnl}=($how eq '+') if (&eq(shift(@args), $nick));
    } else {
      $mode{$chnl} =~ s/$m//g;
      $mode{$chnl}.=$m if $how eq '+';
    }
  }
}

sub umodechange {
  local($what)=@_;
  local($how)=('+');
  foreach $m (split(//, $what)) {
    if ($m =~ /[\-\+]/) {
      $how=$m;
    } else {
      $umode =~ s/$m//g;
      $umode.=$m if ($how eq '+' && $m !~ /\s/);
    }
  }
}

sub ignored {
  foreach (@ignore) {
    return 1 if $_[0] =~ /^${_}$/;
  }
  return '';
}

sub loadrc {
  $rcloaded=1, return if $no_rc;
  if (open (SIRCRC, "< ".$ENV{'HOME'}."/.sircrc")) {
    while (<SIRCRC>) {
      chop;
      s/^\///;
      &docommand($_) if $_;
      $silent='';
      $skip='';
    }
    close SIRCRC;
  }
  $rcloaded=1;
}

sub selline {
  $leftover=0;
  $rin=$ein=$rout=$eout="\0" x 32;
  foreach ('S', keys(%dcnick)) {
    $leftover=1, return $_ if ($buffer{$_} =~ /\n/);
  }
  foreach ('S', 'STDIN', keys(%dcnick), keys(%dcwait), keys(%dgrfh), 
	   keys(%dswait), keys(%dsrfh)) {
    vec($rin, fileno($_), 1)=1;
  }
  $ein=$rin;
  if ($#timers<0 || $timers[0]>time+30) {
    select($rout=$rin, undef, $eout=$ein, 30);
  } elsif ($timers[0]<=time) {
    select($rout=$rin, undef, $eout=$ein, 0);
  } else {
    select($rout=$rin, undef, $eout=$ein, $timers[0]-time);
  }
}

sub donumeric {
  local($from)=($who eq $myserver ? '' : " (from ${who})");
  if ($cmd eq '401') {
    &yetonearg;
    &yetonearg;
    &tell("*\cb?\cb* Cannot find $newarg on irc$from");
  } elsif ($cmd eq '402') {
    &yetonearg;
    &yetonearg;
    &tell("*\cb?\cb* $newarg: no such server$from");
  } elsif ($cmd eq '403') {
    &yetonearg;
    &yetonearg;
    &tell("*\cb?\cb* $newarg: no such channel$from");
  } elsif ($cmd eq '406') {
    &yetonearg;
    &yetonearg;
    &tell("*\cb?\cb* $newarg: there was no such nickname$from");
  } elsif ($cmd eq '421') {
    &yetonearg;
    &yetonearg;
    &tell("*\cb?\cb* $newarg: unknown command$from");
  } elsif ($cmd =~ /^4[012]/) {
    $args =~ s/^[^:]*://;
    &tell("*** $args$from");
  } elsif($cmd eq '431') {
    &tell("*** Was expecting a nickname somewhere...$from");
    if ($connected<2) {
      &getuserline("Pick a nick: ", "Nick: ");
      $nick=$_;
      &dostatus;
      &sl("NICK $_");
    }
  } elsif ($cmd eq '432') {
    if ($connected==2) {
      &tell("*\cbN\cb* Invalid nickname, you're still \"$nick\"");
    } else {
      &tell("*\cbN\cb* Invalid nickname!");
      &getuserline("Pick a nick: ", "Nick: ");
      $nick=$_;
      &dostatus;
      &sl("NICK $_");
    }
  } elsif ($cmd eq '433') {
    if ($connected==2) {
      &tell("*\cbN\cb* Nick already taken, you're still \"$nick\"");
    } else {
      &tell("*\cbN\cb* Nick already taken!");
      &getuserline("Pick a nick: ", "Nick: ");
      $nick=$_;
      &dostatus;
      &sl("NICK $_");
    }
  } elsif ($cmd eq '441') {
    local($g, $w, $c)=split(/ +/, $args);
    &tell("*\cbE\cb* $w is not on channel $c$from");
  } elsif ($cmd eq '442') {
    local($w, $c)=split(/ +/, $args);
    &tell("*\cbE\cb* You're not on channel $c$from");
  } elsif ($cmd eq '443') {
    local($w, $o, $c)=split(/ +/, $args);
    &tell("*\cbE\cb* $o is already on channel $c$from");
  } elsif ($cmd eq '465') {
    &tell("*\cbE\cb* You are banned from this server$from");
  } elsif ($cmd eq '461') {
    &yetonearg;
    &yetonearg;
    &tell("*\cbE\cb* The command $newarg needs more arguments than that$from");
  } elsif ($cmd =~ /^47[1345]$/) {
    &yetonearg;
    &yetonearg;
    local($r);
    if ($cmd eq '471') {
      $r="channel is full";
    } elsif ($cmd eq '473') {
      $r="channel is invite-only";
    } elsif ($cmd eq '474') {
      $r="banned from channel";
    } else {
      $r="bad channel key";
    }
    &tell("*\cbE\cb* Can't join $newarg: ${r}$from");
  } elsif ($cmd eq '301') {
    &yetonearg;
    &yetonearg;
    &tell("*** $newarg is away: $args");
  } elsif ($cmd eq '302') {
    &yetonearg;
    &yetonearg;
    local($n, $do, $err)=(shift(@waituh), shift(@douh), shift(@erruh));
    if ($newarg =~ /^([^\s\*=]+)[\*]?=([\-+])/) {
      $who=$1;
      local($adr)=($');
      if ($adr =~ /\@/) {
	$user=$`;
	$host=$';
      } else {
	$user=$host='';
      }
      if (&eq($who, $n)) {
	eval $do;
	$@ =~ s/\n$//;
	&tell("*\cbE\cb* error in userhost: $@") if ($@ ne '');
      } else {
	&tell("*\cbE\cb* userhost returned for unexpected nick $who");
      }
    } else {
      if (defined($err)) {
	eval $err;
	$@ =~ s/\n$//;
	&tell("*\cbE\cb* error in userhost: $@") if ($@ ne '');
      } else {
	&tell("*\cb?\cb* Cannot find $n on irc");
      }
    }
  } elsif ($cmd eq '303') {
    &yetonearg;
    local($n);
    foreach $n (split(/ +/, $args)) {
      &appeared($n);
    }
  } elsif ($cmd eq '305') {
    &tell("*** You are no longer marked as away");
    $away='';
    &dostatus;
  } elsif ($cmd eq '306') {
    &tell("*** You are marked as being away");
    $away=1;
    &dostatus;
  } elsif ($cmd eq '311') {
    local($g, $n, $u, $m, $g, $r)=split(/ +/, $args, 6);
    $r =~ s/^://;
    &tell("*** $n is $u\@$m ($r)");
  } elsif ($cmd eq '312') {
    &yetonearg;
    &yetonearg;
    &yetonearg;
    local($s)=($newarg);
    &tell("*** on IRC via server $s ($args)");
  } elsif ($cmd eq '313') {
    &yetonearg;
    &yetonearg;
    &tell("*** $newarg is an IRC Operator");
  } elsif ($cmd eq '314') {
    local($g, $n, $u, $m, $g, $r)=split(/ +/, $args, 6);
    $r =~ s/^://;
    &tell("*** $n was $u\@$m ($r)");
  } elsif ($cmd eq '317') {
    &yetonearg;
    &yetonearg;
    local($n)=($newarg);
    &yetonearg;
    if ($newarg>=3600) {
      &tell("*** $n has been ".int($newarg/3600)." hours, ".
	  int(($newarg%3600)/60)." minutes and ".
	  ($newarg%60)." seconds idle");
    } elsif ($newarg>=60) {
      &tell("*** $n has been ".int($newarg/60)." minutes and ".
	    ($newarg%60)." seconds idle");
    } else {
      &tell("*** $n has been $newarg seconds idle");
    }
  } elsif ($cmd eq '319') {
    local($g, $g, $c)=split(/ +/, $args, 3);
    $c =~ s/^://;
    &tell("*** on channels: $c");
  } elsif ($cmd eq '322') {
    local($g, $c, $n, $r)=split(/ +/, $args, 4);
    $r =~ s/^://;
    $n>=$listmin && $n <=$listmax && (!$listpat || $c =~ /^${listpat}$/i)
      && &tell(sprintf("*** %-10s %-5s %s", $c, $n, $r));
  } elsif ($cmd eq '323') {
    $listmin=0;
    $listmax=100000;
    $listpat='';
  } elsif ($cmd eq '324') {
    local($g, $c, $m)=split(/ +/, $args, 3);
    $m =~ s/^://;
    $m =~ s/ $//;
    $c =~ tr/A-Z/a-z/;
    if (grep(&eq($_, $c), @channels)) {
      if (defined($mode{$c})) {
	&tell("*\cb+\cb* Mode for channel $c is \"$m\"");
      } else {
	$mode{$c}='';
      }
      &modestripper($c, $m);
      &dostatus;
    } else {
      &tell("*\cb+\cb* Mode for channel $c is \"$m\"");
    }
  } elsif ($cmd eq '329') {
    &yetonearg;
    &yetonearg;
    local($c)=$newarg;
    &yetonearg;
    local($t)=(&date($newarg));
    &tell("*** $c : created $t");
  } elsif ($cmd eq '331') {
    &yetonearg;
    &yetonearg;
    &tell("*\cbT\cb* No topic is set on channel $newarg");
  } elsif ($cmd eq '332') {
    &yetonearg;
    &yetonearg;
    &tell("*\cbT\cb* Topic for $newarg: $args");
  } elsif ($cmd eq '333') {
    local($g, $c, $n, $t)=split(/ +/, $args, 4);
    local($d)=(&date($t));
    &tell("*\cbT\cb* Topic for $c set by $n on $d");
  } elsif ($cmd eq '318' || $cmd eq '315' || $cmd eq '369' || 
	   $cmd eq '321' || $cmd eq '366' || $cmd eq '376' ||
	   $cmd eq '365' || $cmd eq '368' || $cmd eq '374' ||
	   $cmd eq '219' || $cmd eq '007') {
    #nothing!
  } elsif ($cmd eq '341') {
    local($g, $n, $c)=split(/ +/, $args, 3);
    &tell("*\cbI\cb* Inviting $n to channel $c");
  } elsif ($cmd eq '352') {
    local($g, $c, $u, $m, $s, $n, $st, $g, $i)=split(/ +/, $args, 9);
    &tell(sprintf("%-10s %-9s %4s %s\@%s (%s)", $c, $n, $st, $u, $m, $i));
  } elsif ($cmd eq '353') {
    local($g, $m, $c, $r)=split(/ +/, $args, 4);
    local($n)=($nick);
    $n =~ s/(\W)/\\$1/g;
    $r =~ s/^://;
    &tell("*\cb#\cb* Users on $c: $r");
    $c =~ tr/A-Z/a-z/;
    $haveops{$c}=1 if ($r =~ /\@${n}\b/i);
    &dostatus if &eq($c, $talkchannel);
  } elsif ($cmd eq '221') {
    &yetonearg;
    &tell("*\cb+\cb* Your user mode is \"$args\"");
  } elsif ($cmd eq '200') {
    local($b, $l, $v, $n, $s)=split(/ +/, $args);
    $s =~ s/^://;
    &tell("*** $l $who ($v) ==> $n $s");
  } elsif ($cmd eq '205') {
    local($b, $u, $h, $n)=split(/ +/, $args);
    $n =~ s/^://;
    &tell("*** $u [$h] ==> $n");
  } elsif ($cmd =~ /^20/) {
    local($b, $t, $n, $r)=split(/ +/, $args, 4);
    &tell("*** $t [$n] ==> $r");
  } elsif ($cmd eq '375' || $cmd eq '372' || $cmd =~ /^25/) {
    &yetonearg;
    $args =~ s/ :/ /;
    &tell("*** $args");
  } else {
    &yetonearg;
    $args =~ s/ :/ /;
    &tell("*** $args$from");
  }
}

# main prog

print "`#ssfe#i\n" unless (-t STDOUT);
&tell("*** Welcome to \cbsirc\cb version $version");
srand;

if (-f $initfile && -r $initfile) {
  do $initfile;
  if ($@) {
    $@ =~ s/\n$//;
    &tell("*\cbE\cb* Errors in $initfile file: $@");
  }
}

&bindtoserver;
while (1) {
  until ($connected) {
    until ($tryserver) {
      &getuserline("Pick a server: ", "Server: ");
      $_=$1 if m/^\s*(.*)\s*$/;
      $_=$server0 if $_ eq '0';
      ($tryserver, $port, $pass)=split(/[\s:]+/, $_);
      $port || ($port=$port0);
    }
    $server=$tryserver;
    &bindtoserver;
  }
  $silent='';
  $skip='';
  if ($connected==2) {
    $time=time;
    &loadrc unless $rcloaded;
    &send_isons
      if (($time>=$lastsendison+90) || ($newisons && $time>=$lastsendison+10));
    &signoffs if $checkisons && ($time>=$lastsendison+30);
  }
  $fh=&selline;
  &tell("*\cbE\cb* Connection to server lost"), $connected=0, next
    if (vec($eout, fileno(S), 1));
  &exit if vec($eout, fileno(STDIN), 1);
  foreach $rfh (keys (%dcnick)) {
    &dcerror($rfh), next if vec($eout, fileno($rfh), 1);
    if (vec($rout, fileno($rfh), 1) || ($leftover && $fh eq $rfh)) {
      &gl($rfh) || next;
      &dcerror($rfh), next if $_ eq '';
      chop;
      local($who, $what)=($dcnick{$rfh}, $_);
      print "`#ssfe#t/m =".$who." \n" if $ssfe;
      print "`#ssfe#o=\cb".$who."\cb= ".$what."\n" if $ssfe;
      &dohooks("dcc_chat", $who, $what);
      &tell("=\cb".$who."\cb= ".$what);
      $silent='';
    }
  }
  foreach $rfh (keys (%dcwait)) {
    close($rfh), delete $dcwait{$rfh}, next if vec($eout, fileno($rfh), 1);
    if (vec($rout, fileno($rfh), 1)) {
      $nextfh++;
      if (accept($nextfh, $rfh)) {
	select($nextfh); $|=1; select(STDOUT);
	local($n)=$dcwait{$rfh};
	$dcnick{$nextfh}=$n;
	$n =~ tr/A-Z/a-z/;
	$dcfh{$n}=$nextfh;
	&tell("*\cbD\cb* DCC CHAT connection with $n established");
      }
      close($rfh);
      delete $dcwait{$rfh};
    }
  }
  foreach $sfh (keys (%dswait)) {
    local($rfh)=($dswait{$sfh});
    close($sfh), delete $dnick{$sfh}, delete $dswait{$sfh},
      delete $dfile{$rfh}, next if vec($eout, fileno($sfh), 1);
    if (vec($rout, fileno($sfh), 1)) {
      $nextfh++;
      if (accept($nextfh, $sfh)) {
	select($nextfh); $|=1; select(STDOUT);
	$dsrfh{$nextfh}=$rfh;
	$dstarttime{$rfh}=time;
	$dtransferred{$nextfh}=0;
	$dnick{$nextfh}=$dnick{$sfh};
	&tell("*\cbD\cb* DCC SEND connection with ".$dnick{$sfh}." established");
      }
      delete $dnick{$sfh};
      delete $dswait{$sfh};
      close($sfh);
    }
  }
  foreach $sfh (keys (%dgrfh)) {
    local($rfh)=($dgrfh{$sfh});
    &dgsclose($sfh, $rfh), next if vec($eout, fileno($sfh), 1);
    if (vec($rout, fileno($sfh), 1)) {
      local($a, $buf)=(0, '');
      $a=sysread($sfh, $buf, 1024);
      if ($a) {
	$dtransferred{$sfh}+=$a;
	print $rfh $buf;
	print $sfh pack("N", $dtransferred{$sfh});
      } else {
	&dgsclose($sfh, $rfh);
      }
    }
  }
  foreach $sfh (keys (%dsrfh)) {
    local($rfh)=($dsrfh{$sfh});
    &dgsclose($sfh, $rfh), next if vec($eout, fileno($sfh), 1);
    if (vec($rout, fileno($sfh), 1) || !$dtransferred{$sfh}) {
      local($buf, $b, $l, $w)=('', '');
      if ($dtransferred{$sfh}) {
	&dgsclose($sfh, $rfh), next if (sysread($sfh, $b, 4)!=4);
	next if (unpack("N", $b)!=$dtransferred{$sfh});
      }
      $l=read($rfh, $buf, 512);
      $w=syswrite($sfh, $buf, $l);
      $dtransferred{$sfh}+=$w;
      &dgsclose($sfh, $rfh), next if ($w<$l || $l==0);
    }
  }
  while ($#timers>=0 && $timers[0]<=time) {
    shift (@timers);
    eval shift (@timeactions);
    $@ =~ s/\n$//;
    &tell("*\cbE\cb* error in timer: $@") if ($@ ne '');
  }
  if (vec($rout, fileno(STDIN), 1)) {
    $_=<STDIN>;
    &exit if $_ eq '';
    chop;
    &douserline($_) if $_ ne '';
    next;
  }
  if (($leftover && $fh eq 'S') || vec($rout, fileno(S), 1)) {
    &gl('S') || next;
    if ($_ eq '') {
      &tell("*\cbE\cb* Connection to server lost");
      close(S);
      $connected=0;
      next;
    }
    chop;
    $_=$server." ".$_ unless /^:/;
    ($who, $cmd, $args)=split(/ /, $_, 3);
    $cmd =~ tr/a-z/A-Z/;
    $who =~ s/^://;
    $args =~ s/^://;
    if ($who =~ /^([^!@ ]+)!([^@ ]+)@([^ ]+)$/) {
      $who=$1;
      $user=$2;
      $host=$3;
    } else {
      $user='';
      $host='';
    }
    &dohooks("raw_irc", $cmd, $args);
    next if $skip;
    next if (($cmd eq 'PRIVMSG' || $cmd eq 'NOTICE') && 
		&ignored($who."!".$user."\@".$host));
    $connected=2, $myserver=$who if ($cmd eq '001');
    if ($cmd =~ /^\d\d\d$/) {
      &dohooks("num_".$cmd, $args);
      next if $skip;
      &donumeric;
    } elsif ($cmd eq 'PING') {
      &sl("PONG $args");
    } elsif  ($cmd eq 'PRIVMSG') {
      &yetonearg;
      if ($args =~ /^\001[^\001]*\001$/) {
	&ctcp($newarg, $args);
      } elsif (&eq ($newarg, $talkchannel)) {
	&dohooks("public", $newarg, $args);
	&tell("<${who}> $args");
      } elsif ($newarg =~ /^[\#\&]/) {
	&dohooks("public", $newarg, $args);
	&tell("<${who}/${newarg}> $args");
      } elsif (&eq ($newarg, $nick)) {
	print "`#ssfe#t/m ".$who." \n" if $ssfe;
	print "`#ssfe#o[\cb".$who."\cb] ".$args."\n" if $ssfe;
	&dohooks("msg", $args);
	&tell("[\cb${who}\cb] $args");
      } else {
	&tell("[\cb${who}/${newarg}\cb] $args");
      }
    } elsif ($cmd eq 'NOTICE') {
      &yetonearg;
      if ($args =~ /^\001([^\001]*)\001$/) {
	&ctcpreply($newarg, $1);
      } elsif ($newarg =~ /^[\#\&]/) {
	&dohooks("notice", $newarg, $args);
	&tell("-${who}/${newarg}- $args");
      } elsif($who =~ /\./) {
        $args="*** ".$args unless ($args =~ /^\*/);
	&tell($args);
      } else {
	&dohooks("notice", $newarg, $args);
	&tell("-\cb${who}\cb- $args");
      }
    } elsif ($cmd eq 'KICK') {
      &yetonearg;
      local($channel)=($newarg);
      &yetonearg;
      $args=$who unless($args);
      if (&eq($nick, $newarg)) {
	&tell("*\cb<\cb* You have been kicked off channel $channel by $who ($args)");
	@channels=grep(!&eq($_, $channel), @channels);
	if (@channels) {
	  $talkchannel=$channels[$#channels];
	} else {
	  $talkchannel='';
	}
	$channel =~ tr/A-Z/a-z/;
	&dohooks("kick", $newarg, $channel, $args);
	delete $mode{$channel};
	delete $limit{$channel};
	delete $haveops{$channel};
	delete $chankey{$channel};
	$talkchannel && !$ssfe && &tell("*** Talking to $talkchannel now");
	&dostatus;
      } else {
	&dohooks("kick", $newarg, $channel, $args);
	&tell("*\cb<\cb* $newarg has been kicked off channel $channel by $who ($args)");
      }
    } elsif ($cmd eq 'PART') {
      &yetonearg;
      if (&eq($who, $nick)) {
	&tell("*\cb<\cb* You have left channel $newarg");
	@channels=grep(!&eq($_, $newarg), @channels);
	if (@channels) {
	  $talkchannel=$channels[$#channels];
	} else {
	  $talkchannel='';
	}
	$newarg =~ tr/A-Z/a-z/;
	delete $mode{$newarg};
	delete $limit{$newarg};
	delete $haveops{$newarg};
	delete $chankey{$newarg};
	&dohooks("leave", $newarg);
	$talkchannel && !$ssfe && &tell("*** Talking to $talkchannel now");
	&dostatus;
      } else {
	&dohooks("leave", $newarg);
	&tell("*\cb<\cb* $who has left channel $newarg");
      }
    } elsif ($cmd eq 'JOIN') {
      &yetonearg;
      if (&eq($nick, $who)) {
	push(@channels, $newarg);
	$talkchannel=$newarg;
	&dohooks("join", $newarg);
	&dostatus;
	&tell("*\cb>\cb* You have joined channel $newarg");
	&sl("MODE $newarg");
      } else {
	&dohooks("join", $newarg);
	&tell("*\cb>\cb* $who ($user\@$host) has joined channel $newarg");
      }
      &appeared($who);
    } elsif ($cmd eq 'NICK') {
      &yetonearg;
      if (&eq($nick, $who)) {
	$nick=$newarg;
	$who=$newarg;
	&dohooks("nick", $newarg);
	&dostatus;
	&tell("*\cbN\cb* You are now known as $newarg");
      } else {
	&dohooks("nick", $newarg);
	&tell("*\cbN\cb* $who is now known as $newarg");
      }
    } elsif ($cmd eq 'MODE') {
      &yetonearg;
      $args =~ s/ $//;
      if ($newarg =~ /^[\#\&]/) {
	&modestripper($newarg, $args);
	&dohooks("mode", $newarg, $args);
	&dostatus;
	&tell("*\cb+\cb* Mode change \"$args\" on channel $newarg by $who");
      } else {
	local($towho)=($newarg);
	&yetonearg;
	&umodechange($newarg), &dostatus if &eq($towho, $nick);
	&dohooks("mode", $towho, $newarg);
	&tell("*\cb+\cb* Mode change \"$newarg\" for user $towho by $who");
      }
    } elsif ($cmd eq 'KILL') {
      &yetonearg;
      local($n)=($newarg);
      &yetonearg;
      $newarg || ($newarg=$who);
      &tell("*\cb<\cb* $n got killed by $who ($newarg)");
    } elsif ($cmd eq 'INVITE') {
      &yetonearg;
      &yetonearg;
      &dohooks("invite", $newarg);
      &tell("*\cbI\cb* $who invites you to channel $newarg");
    } elsif ($cmd eq 'TOPIC') {
      &yetonearg;
      &dohooks("topic", $newarg, $args);
      &tell("*\cbT\cb* $who has changed the topic on channel $newarg to \"$args\"");
    } elsif ($cmd eq 'SILENCE') {
      &tell("*** Silence $args");
    } elsif ($cmd eq 'PONG') {
    } elsif ($cmd eq 'QUIT') {
      &dohooks("signoff", $args);
      &tell("*\cb<\cb* Signoff: $who ($args)");
      &disappeared($who);
    } elsif ($cmd eq 'WALLOPS') {
      &tell("!".$who."! ".$args);
    } elsif ($cmd eq 'RPONG') {
      local($n, $t, $ms, $ts)=split(/ +/, $args);
      $ts =~ s/^://;
      &tell("*** RPONG: $who - $t: $ms ms, ".time-$ts." sec");
    } else {
      &tell("*** The server says: $args");
    }
  }
}
