shithub: preserve

Download patch

ref: 86fd8d82baf0cc4ddb4f833b0f0844cfd684ba93
author: glenda <glenda@pi>
date: Mon Feb 16 07:59:12 EST 2026

libirc & chat

--- /dev/null
+++ b/README
@@ -1,0 +1,1 @@
+i want to thank JUSTIN CASE
--- /dev/null
+++ b/chat/chat.man
@@ -1,0 +1,68 @@
+.TH CHAT 1
+.SH NAME
+chat \- internet relay chat client
+.SH SYNOPSIS
+.B chat
+[
+.B -t
+] [
+.B -a
+.I address
+]
+.SH DESCRIPTION
+.I Chat
+is an
+.B IRC
+client. By default, it connects to oftc.net
+at port 6697 or the server specified in the
+.B $irc
+environment variable (see
+.IR dial (2))
+and reads raw
+.B IRC
+commands from standard input. Chat recognizes the following flags:
+.TP
+.B -t
+Connect using TLS.
+.TP
+.B -a
+Set the address to
+.I address.
+.PP
+.I Chat
+also recognizes the following control
+messages:
+.TP
+.B /q \fItarget\fR
+This opens a new window that relays
+messages to the
+.I target.
+The
+.I target
+can be a channel or a nick.
+The created window
+also accepts control messages.
+.TP
+.BI /x
+Closes a window and parts. If sent to
+the main window, the connection is closed
+and the program exits.
+.TP
+.B /n \fIdoofus\fR
+Change nicknames to
+.I doofus.
+.SH EXAMPLES
+Join #cat-v and then open a private
+message window with glenda:
+.br
+.ne 3
+.IP
+.EX
+chat
+/q #cat-v
+/q glenda
+.EE
+.SH SOURCE
+https://github.com/mischief/plan9
+.SH SEE ALSO
+.IR ircrc (1)
--- /dev/null
+++ b/chat/dat.h
@@ -1,0 +1,15 @@
+typedef struct Wind Wind;
+struct Wind
+{
+	QLock;
+	u64int		id;
+	char		*target;
+
+	Channel		*event;		/* char* */
+
+	int			kfd;		/* rio cons */
+	int			kpid;		/* keyboard child */
+
+	Wind		*prev;
+	Wind		*next;
+};
--- /dev/null
+++ b/chat/fns.h
@@ -1,0 +1,13 @@
+/* util.c */
+u64int	jenkinshash(char*, int);
+void	riolabel(char*);
+void	rioclose(void);
+int	riowindow(char*);
+
+/* wind.c */
+Wind	*windmk(char*);
+void	windfree(Wind*);
+void	windlink(Wind*, Wind*);
+void	windunlink(Wind*, Wind*);
+Wind*	windfind(Wind*, char*);
+void	windappend(Wind*, char*, ...);
--- /dev/null
+++ b/chat/main.c
@@ -1,0 +1,315 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include <mp.h>
+#include <libsec.h>
+
+#include "irc.h"
+
+#include "dat.h"
+#include "fns.h"
+
+static Wind *mainwind;			/* server window */
+static Irc *conn;			/* our connection */
+static char *nick;
+
+static void windevents(Wind*);
+
+static void
+windproc(void *v)
+{
+	char *name;
+	Wind *w;
+
+	name = v;
+
+	threadsetname("wind %s", name);
+
+	/* copy ns since we fiddle with the namespace for graphics */
+	rfork(RFNAMEG);
+
+	if(riowindow("-scroll -dy 200") < 0)
+		sysfatal("newwindow: %r");
+
+	riolabel(name);
+
+	w = windmk(name);
+	windlink(mainwind, w);
+
+	free(name);
+
+	windevents(w);
+
+	windunlink(mainwind, w);
+	if(chanclosing(w->event) == -1)
+		chanclose(w->event);
+	windfree(w);
+
+	rioclose();
+	threadexits(nil);
+}
+
+enum
+{
+	CMexit,
+	CMquery,
+	CMnick,
+};
+
+Cmdtab inputtab[] =
+{
+	CMexit,		"x",		1,
+	CMexit,		"exit",		1,
+	CMquery,	"q",		2,
+	CMquery,	"query",	2,
+	CMnick,		"n",		2,
+	CMnick,		"nick",		2,
+};
+
+static void
+windevents(Wind *w)
+{
+	char *s, *name;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	while((s = recvp(w->event)) != nil){
+		if(s[0] == '/' && strlen(s) > 1){
+			cb = parsecmd(s+1, strlen(s+1));
+			ct = lookupcmd(cb, inputtab, nelem(inputtab));
+			if(ct == nil){
+				windappend(w, "* invalid command %q *", cb->f[0]);
+				continue;
+			}
+
+			switch(ct->index){
+			case CMexit:
+				free(cb);
+			part:
+				if(w->target != nil && w->target[0] == '#')
+					ircpart(conn, w->target);
+				goto done;
+				break;
+
+			case CMquery:
+				if(cb->f[1][0] == '#')
+					ircjoin(conn, cb->f[1]);
+
+				name = strdup(cb->f[1]);
+				proccreate(windproc, name, 8192);
+				break;
+
+			case CMnick:
+				ircnick(conn, cb->f[1]);
+				break;
+			}
+			free(cb);
+		} else {
+			if(w->target != nil){
+				ircprivmsg(conn, w->target, s);
+			} else {
+				ircraw(conn, s);
+			}
+		}
+		free(s);
+	}
+
+	/* make sure to part on rio del */
+	if(w->target != nil && w->target[0] == '#')
+		goto part;
+
+done:
+	return;
+}
+
+static void
+on433(Irc *irc, IrcMsg*, void*)
+{
+	char buf[128];
+
+	snprint(buf, sizeof buf, "%s%d", nick, 10+nrand(1000));
+	windappend(mainwind, "* nickname %q in use, switching to %q *", nick, buf);
+
+	ircnick(irc, buf);
+}
+
+// NOTICE/PRIVMSG
+static void
+msg(Irc*, IrcMsg *m, void*)
+{
+	int r;
+	char buf[512], prefix[256];
+	char *s;
+	Wind *w;
+
+	/* user privmsg */
+	strcpy(prefix, m->prefix);
+	if((s = strchr(prefix, '!')) != nil)
+		*s = 0;
+
+	snprint(buf, sizeof buf, "%s→	", prefix);
+	for(r = 1; r < m->nargs; r++){
+		strcat(buf, " ");
+		strcat(buf, m->args[r]);
+	}
+
+	if(m->args[0][0] == '#'){
+		w = windfind(mainwind, m->args[0]);
+	} else {
+		w = windfind(mainwind, prefix);
+	}
+
+	if(w != nil){
+		windappend(w, buf);
+	} else {
+		windappend(mainwind, buf);
+	}
+}
+
+static int
+inset(char *needle, char **haystack){
+	char **p;
+	for(p = haystack; *p != nil; (*p)++)
+		if(cistrcmp(needle, *p) == 0)
+			return 0;
+
+	return -1;
+}
+
+static void
+catchall(Irc*, IrcMsg *m, void*)
+{
+	int i;
+	char buf[512];
+
+	if(m->cmd[0] == '_')
+		return;
+
+	char *unwanted[]={
+		IRC_PING,
+		IRC_NOTICE,
+		IRC_PRIVMSG,
+		nil,
+	};
+
+	if(inset(m->cmd, unwanted) == 0)
+		return;
+
+	snprint(buf, sizeof buf, "%s→	", m->prefix);
+	for(i = 1; i < m->nargs; i++){
+		strcat(buf, " ");
+		strcat(buf, m->args[i]);
+	}
+
+	windappend(mainwind, buf);
+}
+
+static void
+ondisconnect(Irc *, IrcMsg*, void*)
+{
+	windappend(mainwind, "* disconnected *");
+}
+
+static void
+onconnect(Irc *irc, IrcMsg*, void*)
+{
+	Wind *w;
+
+	windappend(mainwind, "* connected *");
+	ircuser(irc, "none", "0", "none");
+	ircnick(irc, nick);;
+
+	qlock(mainwind);
+	for(w = mainwind; w != nil; w = w->next){
+		if(w->target != nil && w->target[0] == '#')
+			ircjoin(irc, w->target);
+	}
+	qunlock(mainwind);
+}
+
+static void
+ircproc(void *v)
+{
+	Irc *irc;
+
+	irc = v;
+
+	procsetname("irc %s", ircconf(irc)->address);
+
+	ircrun(irc);
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-t] [-a address]\n", argv0);
+	threadexitsall("usage");
+}
+
+static int
+tlsdial(Irc *, IrcConf *conf)
+{
+	TLSconn c;
+	int fd;
+
+	fd = dial(conf->address, nil, nil, nil);
+	if(fd < 0)
+		return -1;
+	return tlsClient(fd, &c);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	IrcConf conf = { 0 };
+	char *address = getenv("irc");
+
+	ARGBEGIN{
+	case 't':
+		conf.dial = tlsdial;
+		break;
+	case 'a':
+		address = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(argc != 0)
+		usage();
+
+	quotefmtinstall();
+	srand(truerand());
+
+	rfork(RFNOTEG);
+
+	if(address == nil)
+		address = "tcp!irc.oftc.net!6697";
+
+	conf.address = address;
+	nick = getuser();
+
+	riolabel(address);
+
+	mainwind = windmk(nil);
+
+	conn = ircinit(&conf);
+	irchook(conn, IRC_CONNECT, onconnect);
+	irchook(conn, IRC_DISCONNECT, ondisconnect);
+	irchook(conn, IRC_ERR_NICKNAMEINUSE, on433);
+	irchook(conn, IRC_PRIVMSG, msg);
+	irchook(conn, IRC_NOTICE, msg);
+	irchook(conn, nil, catchall);
+	
+	proccreate(ircproc, conn, 16*1024);
+
+	procsetname("main window");
+
+	windevents(mainwind);
+
+	postnote(PNGROUP, getpid(), "die yankee pig dog");
+	threadexitsall(nil);
+}
--- /dev/null
+++ b/chat/mkfile
@@ -1,0 +1,19 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+TARG=chat
+OFILES=\
+	main.$O\
+	util.$O\
+	wind.$O\
+
+LIB=/$objtype/lib/libirc.a
+
+MAN=/sys/man/1
+
+</sys/src/cmd/mkone
+
+install:V:	man
+
+uninstall:V:
+	rm -f $BIN/$TARG $MAN/$TARG
--- /dev/null
+++ b/chat/util.c
@@ -1,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+
+u64int jenkinshash(char *key, int len)
+{
+	u64int hash, i;
+	for(hash = i = 0; i < len; ++i){
+		hash += key[i];
+		hash += (hash << 10);
+		hash ^= (hash >> 6);
+	}
+	hash += (hash << 3);
+	hash ^= (hash >> 11);
+	hash += (hash << 15);
+	return hash;
+}
+
+static void
+fwrites(char *f, char *s)
+{
+	int fd;
+	fd = open(f, OWRITE);
+	if(fd > 0){
+		write(fd, s, strlen(s));
+		close(fd);
+	}
+}
+
+void
+riolabel(char *label)
+{
+	fwrites("/dev/label", label);
+}
+
+void
+rioclose(void)
+{
+	fwrites("/dev/wctl", "delete");
+}
+
+/* copied from /sys/src/libdraw/newwindow.c to avoid linking in libdraw. */
+int
+riowindow(char *str)
+{
+	int fd;
+	char *wsys;
+	char buf[256];
+
+	wsys = getenv("wsys");
+	if(wsys == nil)
+		return -1;
+	fd = open(wsys, ORDWR);
+	if(fd < 0){
+		free(wsys);
+		return -1;
+	}
+	rfork(RFNAMEG);
+	unmount(wsys, "/dev");	/* drop reference to old window */
+	free(wsys);
+	if(str)
+		snprint(buf, sizeof buf, "new %s", str);
+	else
+		strcpy(buf, "new");
+	if(mount(fd, -1, "/mnt/wsys", MREPL, buf) < 0)
+		return mount(fd, -1, "/dev", MBEFORE, buf);
+	return bind("/mnt/wsys", "/dev", MBEFORE);
+}
--- /dev/null
+++ b/chat/wind.c
@@ -1,0 +1,131 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+static void
+kbdproc(void *v)
+{
+	char line[512];
+	int r;
+	Wind *w;
+
+	w = v;
+
+	threadsetname("kbdproc");
+
+	while((r = read(w->kfd, line, sizeof line - 1)) > 0){
+		if(r > 0 && line[r] == '\n')
+			r--;
+
+		line[r] = 0;
+
+		chanprint(w->event, "%s", line);
+	}
+
+	chanclose(w->event);
+}
+
+Wind*
+windmk(char *target)
+{
+	int r;
+	Wind *w;
+
+	w = mallocz(sizeof(*w), 1);
+	if(target != nil){
+		for(r = 0; r < strlen(target); r++)
+			target[r] = tolower(target[r]);
+
+		w->id = jenkinshash(target, strlen(target));
+	}
+
+	if(target != nil)
+		w->target = strdup(target);
+
+	w->event = chancreate(sizeof(char*), 0);
+	w->kpid = -1;
+
+	if((w->kfd = open("/dev/cons", ORDWR)) < 0){
+		sysfatal("open: %r");
+	}
+
+	w->kpid = proccreate(kbdproc, w, 8192);
+	return w;
+}
+
+void
+windfree(Wind *w)
+{
+	qlock(w);
+
+	if(w->kpid > 0)
+		postnote(PNPROC, w->kpid, "die yankee pig dog");
+
+	if(w->event != nil)
+		chanfree(w->event);
+
+	free(w->target);
+	free(w);
+}
+
+void
+windlink(Wind *l, Wind *w)
+{
+	qlock(l);
+	w->prev = l;
+	w->next = l->next;
+	l->next = w;
+	qunlock(l);
+}
+
+void
+windunlink(Wind *l, Wind *w)
+{
+	qlock(l);
+	if(w->next != nil)
+		w->next->prev = w->prev;
+	if(w->prev != nil)
+		w->prev->next = w->next;
+	qunlock(l);
+}
+
+Wind*
+windfind(Wind *l, char *target)
+{
+	int r;
+	u64int id;
+	Wind *w;
+
+	for(r = 0; r < strlen(target); r++)
+		target[r] = tolower(target[r]);
+
+	id = jenkinshash(target, strlen(target));
+
+	qlock(l);
+	for(w = l; w != nil; w = w->next){
+		if(w->id == id)
+			break;
+	}
+	qunlock(l);
+
+	return w;
+}
+
+void
+windappend(Wind *w, char *msg, ...)
+{
+	va_list arg;
+
+	qlock(w);
+
+	va_start(arg, msg);
+	vfprint(w->kfd, msg, arg);
+	va_end(arg);
+
+	write(w->kfd, "\n", 1);
+
+	qunlock(w);
+}
--- /dev/null
+++ b/libirc/irc.c
@@ -1,0 +1,342 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+#include "irc.h"
+
+typedef struct Hook Hook;
+struct Hook
+{
+	char what[64];
+	IrcCb *cb;
+	Hook *next;
+};
+
+struct Irc
+{
+	QLock;
+
+	IrcConf *conf;
+
+	int fd;
+	Biobuf *buf;
+	Hook *head;
+
+	int run;
+};
+
+IrcMsg*
+parsemsg(char *b)
+{
+	char *tok[5], *args[20];
+	int i, n, ntok, nargs;
+	IrcMsg *msg;
+
+	if(b == nil || b[0] == 0)
+		return nil;
+
+	msg = mallocz(sizeof(*msg), 1);
+	if(msg == nil)
+		return nil;
+
+	if(b[0] == ':'){
+		ntok = gettokens(b, tok, 2, " ");
+		if(ntok != 2)
+			goto fail;
+		strncpy(msg->prefix, tok[0]+1, sizeof(msg->prefix)-1);
+		b = tok[1];
+	}
+
+	ntok = gettokens(b, tok, 2, ":");
+
+	nargs = gettokens(tok[0], args, nelem(args), " ");
+
+	strncpy(msg->cmd, args[0], sizeof(msg->cmd)-1);
+	n = strlen(msg->cmd);
+	for(i = 0; i < n; i++){
+		msg->cmd[i] = tolower(msg->cmd[i]);
+	}
+
+	msg->nargs = nargs - 1 + (ntok > 1 ? 1 : 0);
+
+	msg->args = mallocz(msg->nargs * sizeof(char*), 1);
+	if(msg->args == nil)
+		goto fail;
+
+	for(i = 0; i < msg->nargs - (ntok > 1 ? 1 : 0); i++){
+		msg->args[i] =  strdup(args[i+1]);
+	}
+
+	if(ntok > 1){
+		msg->args[i] = strdup(tok[1]);
+	}
+
+/*
+	fprint(2, "prefix: %s\n", msg->prefix);
+	fprint(2, "command: %s\n", msg->cmd);
+	for(i = 0; i < msg->nargs; i++){
+		fprint(2, " %s\n", msg->args[i]);
+	}
+*/
+
+	return msg;
+fail:
+	free(msg);
+	return nil;
+}
+
+void
+freemsg(IrcMsg *m)
+{
+	int i;
+
+	assert(m != nil);
+
+	if(m->nargs > 0){
+		for(i = 0; i < m->nargs; i++)
+			free(m->args[i]);
+		free(m->args);
+	}
+
+	free(m);
+}
+
+void
+pinghandler(Irc *irc, IrcMsg *msg, void*)
+{
+	if(msg->nargs > 0)
+		ircrawf(irc, "PONG :%s", msg->args[0]);
+}
+
+Irc*
+ircinit(IrcConf *conf)
+{
+	Irc *c;
+
+	c = mallocz(sizeof(*c), 1);
+	if(c == nil)
+		return nil;
+
+	c->conf = conf;
+	c->fd = -1;
+	c->run = 1;
+
+	if(irchook(c, IRC_PING, pinghandler) < 0)
+		goto bad1;
+
+	return c;
+
+bad1:
+	free(c);
+	return nil;
+}
+
+void
+ircfree(Irc *irc)
+{
+	Hook *h, *next;
+
+	for(h = irc->head, next = h->next; next != nil; h = next, next = next->next){
+		free(h);
+	}
+
+	if(irc->buf)
+		Bterm(irc->buf);
+}
+
+static void
+callhook(Irc *irc, char *what, IrcMsg *msg){
+	Hook *h;
+	IrcMsg fake;
+
+	if(msg == nil){
+		msg = &fake;
+		memset(msg, 0, sizeof(*msg));
+		strcpy(msg->cmd, what);
+	}
+
+	for(h = irc->head; h != nil; h = h->next)
+		if(h->what[0] == 0 || cistrcmp(what, h->what) == 0)
+			h->cb(irc, msg, irc->conf->aux);
+}
+
+int
+ircrun(Irc *irc)
+{
+	int l;
+	char *p;
+	IrcMsg *msg;
+	Biobuf *buf;
+
+	buf = nil;
+
+	for(;;){
+		qlock(irc);
+		if(!irc->run)
+			break;
+		qunlock(irc);
+
+		switch(irc->fd){
+		case -1:
+			callhook(irc, IRC_DIALING, nil);
+
+			if(irc->conf->dial != nil)
+				irc->fd = irc->conf->dial(irc, irc->conf);
+			else
+				irc->fd = dial(irc->conf->address, nil, nil, nil);
+
+			if(irc->fd >= 0){
+				buf = Bfdopen(irc->fd, OREAD);
+				if(buf == nil){
+					callhook(irc, IRC_OOM, nil);
+					break;
+				}
+
+				callhook(irc, IRC_CONNECT, nil);
+			}
+
+			sleep(5000);
+
+			break;
+		default:
+			p = Brdstr(buf, '\n', 1);
+			if(p == nil){
+				// eof/maybe oom?
+				Bterm(buf);
+				buf = nil;
+				irc->fd = -1;
+				callhook(irc, IRC_DISCONNECT, nil);
+				break;
+			}
+
+			l = Blinelen(buf);
+			if(l > 0 && p[l-1] == '\r')
+				p[l-1] = 0;
+
+			if(irc->conf->debug)
+				fprint(2, "<< %s\n", p);
+
+			msg = parsemsg(p);
+			free(p);
+			if(msg == nil){
+				callhook(irc, IRC_OOM, nil);
+				break;
+			}
+
+			if(strcmp(msg->cmd, IRC_PING) == 0){
+				ircrawf(irc, "PONG: %s", msg->args[0]);
+			}
+
+			callhook(irc, msg->cmd, msg);
+
+			freemsg(msg);
+
+			break;
+		}
+	}
+
+	if(buf != nil)
+		Bterm(buf);
+
+	return 0;
+}
+
+int
+irchook(Irc *irc, char *what, IrcCb *cb)
+{
+	Hook *h;
+
+	h = mallocz(sizeof(*h), 1);
+	if(h == nil)
+		return -1;
+
+	if(what == nil)
+		what = "";
+
+	snprint(h->what, sizeof(h->what), "%s", what);
+	h->cb = cb;
+	h->next = irc->head;
+
+	irc->head = h;
+
+	return 0;
+}
+
+IrcConf*
+ircconf(Irc *irc)
+{
+	return irc->conf;
+}
+
+void
+ircterminate(Irc *irc)
+{
+	qlock(irc);
+	irc->run = 0;
+	close(irc->fd);
+	qunlock(irc);
+}
+
+int
+ircraw(Irc *irc, char *msg)
+{
+	int rv;
+
+	if(irc->conf->debug)
+		fprint(2, ">> %s\n", msg);
+
+	rv = write(irc->fd, msg, strlen(msg));
+	if(rv > 0)
+		rv = write(irc->fd, "\r\n", 2);
+	return rv;
+}
+
+int
+ircrawf(Irc *irc, char *fmt, ...)
+{
+	char tmp[512];
+	va_list arg;
+
+	va_start(arg, fmt);
+	
+	tmp[0] = 0;
+	vsnprint(tmp, sizeof(tmp), fmt, arg);
+
+	return ircraw(irc, tmp);
+}
+
+int
+ircquit(Irc *irc, char *why)
+{
+	return ircrawf(irc, "QUIT :%s", why);
+}
+
+int
+ircuser(Irc *irc, char *user, char *mode, char *realname)
+{
+	return ircrawf(irc, "USER %s %s * :%s", user, mode, realname);
+}
+
+int
+ircnick(Irc *irc, char *nickname)
+{
+	return ircrawf(irc, "NICK %s", nickname);
+}
+
+int
+ircjoin(Irc *irc, char *channel)
+{
+	return ircrawf(irc, "JOIN %s", channel);
+}
+
+int
+ircpart(Irc *irc, char *channel)
+{
+	return ircrawf(irc, "PART %s", channel);
+}
+
+int
+ircprivmsg(Irc *irc, char *tgt, char *msg)
+{
+	return ircrawf(irc, "PRIVMSG %s :%s", tgt, msg);
+}
--- /dev/null
+++ b/libirc/irc.h
@@ -1,0 +1,50 @@
+typedef struct IrcConf IrcConf;
+typedef struct Irc Irc;
+#pragma incomplete Irc
+typedef struct IrcMsg IrcMsg;
+typedef struct IrcHooks IrcHooks;
+
+typedef void IrcCb(Irc*, IrcMsg*, void *aux);
+
+struct IrcConf
+{
+	char 	*address;
+	int		debug;
+	void	*aux;
+	int		(*dial)(Irc *irc, IrcConf *conf);
+};
+
+struct IrcMsg
+{
+	char	prefix[256];
+	char	cmd[64];
+	char	**args;
+	int		nargs;
+};
+
+#define IRC_DISCONNECT 	"_DISCONNECTED"
+#define IRC_CONNECT		"_CONNECTED"
+#define IRC_DIALING		"_DIALING"
+#define IRC_OOM			"_OOM"
+
+#define IRC_PING		"PING"
+#define IRC_NOTICE		"NOTICE"
+#define IRC_PRIVMSG 	"PRIVMSG"
+
+#define IRC_RPL_WELCOME			"001"
+#define IRC_ERR_NICKNAMEINUSE	"433"	
+
+Irc* ircinit(IrcConf *conf);
+void ircfree(Irc*);
+int ircrun(Irc*);
+int irchook(Irc *irc, char *what, IrcCb *cb);
+void ircterminate(Irc *irc);
+IrcConf *ircconf(Irc*);
+int ircraw(Irc *irc, char *msg);
+int ircrawf(Irc *irc, char *fmt, ...);
+int ircquit(Irc *irc, char *why);
+int ircuser(Irc *irc, char *user, char *mode, char *realname);
+int ircnick(Irc *irc, char *nickname);
+int ircjoin(Irc *irc, char *channel);
+int ircpart(Irc *irc, char *channel);
+int ircprivmsg(Irc *irc, char *tgt, char *msg);
binary files /dev/null b/libirc/libirc.7.a differ
--- /dev/null
+++ b/libirc/mkfile
@@ -1,0 +1,20 @@
+</$objtype/mkfile
+
+P=irc
+
+LIB=lib$P.$O.a
+OFILES=$P.$O
+HFILES=/sys/include/$P.h
+
+</sys/src/cmd/mklib
+
+install:V:	$LIB
+	cp $LIB /$objtype/lib/lib$P.a
+	cp $P.h /sys/include/$P.h
+	#cp $P.man2 /sys/man/2/$P
+
+uninstall:V:
+	rm -f /$objtype/lib/lib$P.a /sys/include/$P.h /sys/man/2/$P
+
+$O.test: test.$O $LIB
+	$LD -o $target $prereq
--