
# infobot :: Kevin Lenzo & Patrick Cole   (c) 1997

use Socket;

sub srvConnect {
    my ($server, $port) = @_;
    my ($iaddr, $paddr, $proto);
    select(STDOUT);
    &status("Connecting to port $port of server $server ...");

    $iaddr = inet_aton($server);
    $paddr = sockaddr_in($port, $iaddr);
    $proto = getprotobyname('tcp');
    socket(SOCK, PF_INET, SOCK_STREAM, $proto) or die "socket failed: $!";
    connect(SOCK, $paddr) or die "connect failed: $!";

    &status(" connected.");
}

sub procservmode {
    my ($server, $e, $f) = @_;
    my @parts = split (/ /, $f);
    $cnt=0;
    my $mode="";
    my $chan="";
    foreach (@parts) {
	if ($cnt == 0) {
	    $chan = $_;
	} else {
	    $mode .= $_;
	    $mode .= " ";
	}
	++$cnt;
    }
    chop $mode;
    $mode=~s/://;

    if ($server eq $chan) {
	&status("USERMODE $mode set by $server");
    } else {
	&status("MODE $mode set by $server/$chan");
    }
}

sub procmode {
    my ($nick, $user, $host, $e, $f) = @_;
    my @parts = split (/ /, $f);
    $cnt=0;
    my $mode="";
    my $chan="";
    foreach (@parts) {
	if ($cnt == 0) {
	    $chan = $_;
	} else {
	    $mode .= $_;
	    $mode .= " ";
	}
	++$cnt;
    }
    $mode =~ s/\s$//;
    if ($chan =~ /^[\#\&]/) {
	my ($modes, $targets) = ($mode =~ /^(\S+)\s+(.*)/);
	my @m = ($modes =~ /([+-]*\w)/g);
	my @t = split /\s+/, $targets;
	if (@m != @t) {
	    &status("number of modes does not match number of targets: @m / @t");
	} else {
	    my $parity = 0;
	    foreach (0..$#m) {
		if ($m[$_] =~ s/^([-+])//) {
		    $sign = $1;
		    if ($sign eq '-') {
			$parity = -1;
		    } else {
			$parity = 1;
		    }
		}
		if ($parity == 0) {
		    &status("zero parity mode change... ignored");
		} else {
		    if ($parity > 0) {
			$channels{$chan}{$m}{$t} = '+';
		    } else {
			delete $channels{$chan}{$mode}{$t};
		    }
		}
	    }
	}
    }
    &status("mode/$chan [$mode] by $nick");
}

sub entryEvt {
    my ($nick, $user, $host, $type, $chan) = @_;
    if ($type=~/PART/) {
	&status("$nick ($user\@$host) has left $chan");
    } elsif ($type=~/JOIN/) {
	if ($netsplit) {
	    foreach (keys(%snick)) {
		if ($nick eq $snick{$_}) {
		    @be = split (/ /);
		    &status("Net joined: $be[0] $be[1]");
		    $netsplit--;
		}
	    }
	}
	&status("$nick ($user\@$host) has joined $chan");

    } elsif ($type=~/QUIT/) {
	$chan=~s/\r//;
	if ($chan=~/^([\d\w\_\-\/]+\.[\.\d\w\_\-\/]+)\s([\d\w\_\-\/]+\.[\.\d\w\_\-\/]+)$/) {
	    $i=0;
	    while (0 and ($i < $netsplit || !$netsplit)) {
#	    while ($i < $netsplit || !$netsplit) {
		$i++;
		if (($prevsplit1{$i} ne $2) && ($prevsplit2{$i} ne $1)) {
		    &status("\Netsplit: $2 split from $1");
		    $netsplit++;
		    $prevsplit1{$netsplit} = $2;
		    $prevsplit2{$netsplit} = $1;
		    $snick{"$2 $1"}=$nick;
		    $schan{"$2 $1"}=$chan;
		}
	    }
	} else {
	    &status("$nick has signed off IRC ($chan)");
	}
    } elsif ($type=~/NICK/) {
	&status("$nick materializes into $chan");
    }
}

sub procevent {
    my ($nick, $user, $host, $type, $chan, $msg) = @_;

    # support global $nuh, $who
    $nuh = "$nick!$user\@$host";

    if ($type=~/PRIVMSG/) {
	if ($chan =~ /^$ischan/) {
	    ## It's a public message on the channel##
	    $chan =~ tr/A-Z/a-z/;

	    if ($msg =~ /\001(.*)\001/ && $msg !~ /ACTION/) {
		#### Client To Client Protocol ####
		parsectcp($nick, $user, $host, $1, $chan);
	    } elsif ($msg !~ /ACTION\s(.+)/) {
		#### Public Channel Message ####
		&IrcMsgHook('public', $chan, $nick, $msg);
	    } else {
		#### Public Action ####
		&IrcActionHook($nick, $chan, $1);
	    }
	} else {
	    ## Is Private ##
	    if ($msg=~/\001(.*)\001/) {
		#### Client To Client Protocol ####
		parsectcp($nick, $user, $host, $1, $chan);
	    } else {
		#### Is a Private Message ##
		&IrcMsgHook('private', $chan, $nick, $msg);
	    }
	}
    } elsif ($type=~/NOTICE/) {
	if ($chan =~ /^$ischan/) {
	    $chan =~ tr/A-Z/a-z/;
	    if ($msg !~ /ACTION (.*)/) {
		&status("-$nick/$chan- $msg");
	    } else {
		&status("* $nick/$chan $1");
	    }
	} else {
	    if ($msg=~/\001([A-Z]*)\s(.*)\001/) {
		ctcpReplyParse($nick, $user, $host, $1, $2);
	    } else {
		&status("-$nick($user\@$host)- $msg");
	    }
	}
    }
}

sub servmsg {
    my $msg=$_[0];
    my ($ucount, $uc) = (0, 0);
    if ($msg=~/^001/) {
	joinChan(split/\s+/, $param{'join_channels'});
	$nicktries=0;
    } elsif ($msg=~/^NOTICE ($ident) :(.*)/) {
	serverNotice($1,$2);
    } elsif ($msg=~/^332 $ident ($ischan) :(.*)/) {
	&status("TOPIC for $1: $2");
    } elsif ($msg=~/^333 $ident $ischan (.*) (.*)$/) {
	&status("set by $1 at $2");
    } elsif ($msg=~/^433/) {
	++$nicktries;
	if (length($param{wantNick}) > 9) {
	    $ident = chop $param{wantNick};
	    $ident .= $nicktries;
	} else {
	    $ident = $param{wantNick}.$nicktries;
	}
	$param{nick} = $ident;
	&status("*** Nickname $param{wantNick} in use, trying $ident");
	rawout("NICK $ident");

    } elsif ($msg=~/[0-9]+ $ident . ($ischan) :(.*)/) {
	my ($chan, $users) = ($1, $2);
	&status("NAMES $chan: $users");
	my $u;
	foreach $u (split /\s+/, $users) {
	    if (s/\@//) {
		$channels{$chan}{o}{$u}++;
	    }
	    if (s/\+//) {
		$channels{$chan}{v}{$u}++;
	    }
	}
    } elsif ($msg=~/[0-9]{3} $ident(\s$ischan)*?\s:(.*)/) {
	&status("$2");
    }
}

sub serverNotice {
    ($type, $msg) = @_;
    if ($type=~/AUTH/) {
	&status("!$param{server}! $msg");
    } else {
	$msg =~ s/\*\*\* Notice -- //;
	&status("-!$param{server}!- $msg");
    }
}

sub OperWall {
    my ($nick, $msg) = @_;
    $msg=~s/\*\*\* Notice -- //;
    &status("[wallop($nick)] $msg");
}

sub prockick {
    my ($kicker, $chan, $knick, $why) = @_;

    &status("KICK $knick $chan by $kicker ($why)");
    if ($knick eq $ident) {
	&status("SELF attempting to rejoin lost channel $chan");
	joinChan($chan);
    }
}

sub prockill {
    my ($killer, $knick, $kserv, $killnick, $why) = @_;
    if ($knick eq $ident) {
	&status("KILLED by $killnick ($why)");	
    } else {
	&status("KILL $knick by $killnick ($why)");
    }
}

sub fhbits {
    local (@fhlist) = split(' ',$_[0]);
    local ($bits);
    for (@fhlist) {
	vec($bits,fileno($_),1) = 1;
    }
    $bits;
}

sub irc {
    local ($rin, $rout);
    local ($buf, $line);

    $nicktries=0;
    $connected=1;
    $wild_debug=0;
    while ($connected) {
	srvConnect($param{server}, $param{port});

	rawout("NICK $param{wantNick}");
	rawout("USER $param{ircuser} $param{ident} $param{server} :$param{realname}");
	if ($param{operator}) {
	    rawout("OPER $param{operName} $param{operPass}\n");
	}
	$param{nick} = $param{wantNick};
	$ident = $param{wantNick};

	$/ = "\015" if $^O eq "MacOS";

	$rin = fhbits('SOCK');
	while (1) {
	    ($nfound,$timeleft) = select($rout=$rin, undef, undef, 0);
	    if ($rout & SOCK) {
		if (sysread(SOCK,$buf,1) <= 0) {
		    last;
		}
		if ($buf=~/\n/) {
		    $line.=$buf;
		    sparse($line);
		    undef $line;
		} else {
		    $line.=$buf;
		}
	    }
	}
    }
}

sub sparse {
    $_ = $_[0];
    s/\r//;

    if (/^PING :(\S+)/) {	# Ping's are important
	rawout("PONG :$1");
	&status("SELF replied to server PING") if $param{VERBOSITY} > 2;
    } elsif (/^:\S+ ([\d]{3} .*)/) {
	servmsg($1);
    } elsif (/^:([\d\w\_\-\/]+\.[\.\d\w\_\-\/]+) NOTICE ($ident) :(.*)/) {
	&status("\-\[$1\]- $3");
    } elsif (/^NOTICE (.*) :(.*)/) {
	serverNotice($1, $2);
    } elsif (/^:(.*)!(.*)@(.*?)\s(PRIVMSG|NOTICE)\s([\#\&]?.*?)\s:(.*)/) {
	procevent($1,$2,$3,$4,$5,$6);
    } elsif (/^:(.*)!(.*)@(.*?) (PART|JOIN|NICK|QUIT) :?(.*)/) {
	entryEvt($1,$2,$3,$4,$5);
    } elsif (/^:(.*) WALLOPS :(.*)/) {
	OperWall($1,$2);
    } elsif (/^:(.*)!(.*)@(.*) (MODE) (.*)/) {
	procmode($1,$2,$3,$4,$5);
    } elsif (/^:(.*) (MODE) (.*)/) {
	procservmode($1,$2,$3);
    } elsif (/^:(.*)!.*@.* KICK ((\#|&).+) (.*) :(.*)/) {
	prockick($1,$2,$4,$5);
    } elsif (/^ERROR :(.*)/) {
	&status("ERROR $1");
    } elsif (/^:(.*)!.*@.* TOPIC (\#.+) :(.*)/) {
	&status("$1[$2] set the topic: $3");
    } elsif (/^:(.*)!.*@.* KILL (.*) :(.*)!(.*) \((.*)\)/) {
	prockill($1,$2,$3,$4,$5);
    } else {
	&status("UNKNOWN $_");
    }
}

	     1;
