--- /dev/null
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+
+char help[] =
+"cmd explanation/example\n"
+"--------------------------------------------\n"
+"/m privmsg #chan/nick message\n"
+"/M mode #chan +nt\n"
+"/j join #chan\n"
+"/p part #chan\n"
+"/q send parameters raw to the server\n"
+"/l list #chan\n"
+"/n nick newnick\n"
+"/N notice #chan/nick message\n"
+"/t set victim\n"
+"/T topic #chan newtopic\n"
+"/W whois nick\n"
+"/w who nick (a shorter whois)\n";
+
+#define NPAR 14
+
+enum state { Time, Cmd, Prefix, Middle, Trail, Ok };
+
+typedef struct handler Handler;
+
+struct handler {
+ char *cmd;
+ int (*fun)(int fd, char *time, char *pre, char *cmd, char *par[]);
+};
+
+QLock lck;
+int server_in;
+int server_out;
+int scr;
+char *victim;
+char *nick;
+int inacme; /* running in acme? */
+int linewidth; /* terminal width in # of characters */
+
+int replay; /* just print the log ma'am */
+
+void setwintitle(char *chan);
+
+int rtcs(int fd, char *cset);
+int wtcs(int fd, char *cset);
+int follow(int fd);
+void getwidth(void); /* establish the width of the terminal, from mc.c */
+
+int pmsg(int fd, char *time, char *pre, char *cmd, char *par[]);
+int ntc(int fd, char *time, char *pre, char *cmd, char *par[]);
+int generic(int fd, char *time, char *pre, char *cmd, char *par[]);
+int misc(int fd, char *time, char *pre, char *cmd, char *par[]);
+int numeric(int fd, char *time, char *pre, char *cmd, char *par[]);
+
+Handler handlers[] = {
+ {"PRIVMSG", pmsg},
+ {"NOTICE", ntc},
+ {"JOIN", misc},
+ {"PART", misc},
+ {"MODE", misc},
+ {"QUIT", misc},
+ {"TOPIC", misc},
+ {"332", numeric},
+ {"333", numeric},
+ {"352", numeric},
+ {"315", numeric},
+ {"311", numeric},
+ {"319", numeric},
+ {"312", numeric},
+ {"320", numeric},
+ {"317", numeric},
+ {"318", numeric},
+ {nil, nil}
+};
+
+int srvparse(char *line, char **time, char **pre, char **cmd, char *par[], int npar);
+int usrparse(char *ln, char *cmd, char *par[], int npar);
+
+void
+usage(void)
+{
+ char usage[] = "usage: irc [-c charset] [-t victim] [-b lines] [-r file] [/srv/irc [/tmp/irc]]\n";
+ write(1, usage, sizeof(usage)-1);
+ exits("usage");
+}
+
+void
+setwintitle(char *chan)
+{
+ int fd;
+
+ if ((fd = open("/dev/label", OWRITE)) >= 0) {
+ fprint(fd, "%s", chan);
+ close(fd);
+ }
+ if ((fd = open("/dev/acme/ctl", OWRITE)) >= 0) {
+ fprint(fd, "name -IRC/%s\n", chan);
+ close(fd);
+ inacme = 1;
+ }
+}
+
+/* try to find out whether we're running in acme's win -e */
+int
+testacme(void)
+{
+ return access("/dev/acme", OREAD) >= 0 ? 1 : 0;
+}
+
+void
+usrin(void)
+{
+ char *line, *p;
+ char *par[2];
+ char cmd;
+ int n, i;
+
+ Biobuf kbd;
+ Binit(&kbd, 0, OREAD);
+ while ((line = Brdstr(&kbd, '\n', 0)) != nil) {
+ n = utflen(line);
+ if(!inacme) {
+ p = malloc(n);
+ for (i = 0; i < n; ++i)
+ p[i] = '\b';
+ write(scr, p, i);
+ free(p);
+ }
+ qlock(&lck);
+ if (!usrparse(line, &cmd, par, 2)) {
+ switch(cmd) {
+ case 'q': /* quote, just send the params ... */
+ if(par[0]) {
+ fprint(server_out, "%s %s\r\n", par[0], par[1] ? par[1] : "");
+ } else {
+ fprint(scr, "/q %s %s: not enough arguments\n", par[0], par[1]);
+ }
+ break;
+ case 'M':
+ if(par[0] && par[1]) {
+ fprint(server_out, "MODE %s %s\r\n", par[0], par[1]);
+ } else {
+ fprint(scr, "/M %s %s: not enough arguments\n", par[0], par[1]);
+ }
+ break;
+ case 'm':
+ if(par[0] && par[1]) {
+ fprint(server_out, "PRIVMSG %s :%s\r\n", par[0], par[1]);
+ } else {
+ fprint(scr, "/m %s %s: not enough arguments\n", par[0], par[1]);
+ }
+ break;
+ case 't':
+ if(par[0] != nil) {
+ free(victim);
+ victim = strdup(par[0]);
+ setwintitle(par[0]);
+ }
+ fprint(scr, "*** default victim set to '%s'\n", par[0]);
+ break;
+ case 'T':
+ if(par[0] == nil)
+ fprint(server_out, "TOPIC %s\r\n", victim);
+ else if(par[1] == nil)
+ fprint(server_out, "TOPIC %s\r\n", par[0]);
+ else
+ fprint(server_out, "TOPIC %s :%s\r\n", par[0], par[1]);
+ break;
+ case 'j':
+ fprint(server_out, "JOIN %s\r\n", par[0] == nil ? victim : par[0]);
+ break;
+ case 'p':
+ fprint(server_out, "PART %s\r\n", par[0] == nil ? victim : par[0]);
+ break;
+ case 'n':
+ if(par[0] != nil) {
+ fprint(server_out, "NICK %s\r\n", par[0]);
+ free(nick);
+ nick = strdup(par[0]);
+ } else {
+ fprint(scr, "%s", help);
+ }
+ break;
+ case 'N':
+ if(par[1] != nil)
+ fprint(server_out, "NOTICE %s :%s\r\n", par[0] == nil ? victim : par[0], par[1]);
+ break;
+ case 'W':
+ fprint(server_out, "WHOIS %s %s\r\n", par[0] == nil ? victim : par[0], par[0]);
+ case 'w':
+ fprint(server_out, "WHO %s\r\n", par[0] == nil ? victim : par[0]);
+ break;
+ case 'l':
+ fprint(server_out, "LIST %s\r\n", par[0] == nil ? victim : par[0]);
+ break;
+ case 'L':
+ fprint(server_out, "NAMES %s\r\n", par[0] == nil ? victim : par[0]);
+ break;
+ case 'f':
+ break;
+ case 'h':
+ case 'H':
+ fprint(scr, "%s", help);
+ break;
+ }
+ } else {
+ fprint(scr, "%s", help);
+ }
+ qunlock(&lck);
+ free(line);
+ }
+ exits(0);
+}
+
+void
+timestamp(char *logtime, char *scrtime, int maxlen)
+{
+ static char *wday[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+ };
+ static char *mon[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ };
+ static int day = 0;
+ Tm *t;
+
+ t = localtime(atol(logtime));
+
+ if (t->mday != day) {
+ day = t->mday;
+ fprint(scr, "-- %s, %02d %s %d --\n",
+ wday[t->wday], t->mday, mon[t->mon],
+ t->year + 1900);
+ }
+
+ snprint(scrtime, maxlen, "%02d:%02d:%02d",
+ t->hour, t->min, t->sec);
+}
+
+void
+srvin(void)
+{
+ char *line;
+ char *time, *pre, *cmd, *par[NPAR];
+ char scrtime[32];
+ Biobuf srv;
+ Binit(&srv, server_in, OREAD);
+
+ while ((line = Brdstr(&srv, '\n', 0)) != nil) {
+ if (!srvparse(line, &time, &pre, &cmd, par, NPAR)) {
+ Handler *hp = handlers;
+ qlock(&lck);
+ timestamp(time, scrtime, sizeof(scrtime));
+ while (hp->cmd != nil) {
+ if (!strcmp(hp->cmd, cmd)) {
+ hp->fun(server_out, scrtime, pre, cmd, par);
+ break;
+ }
+ ++hp;
+ }
+ if (hp->cmd == nil)
+ generic(server_out, scrtime, pre, cmd, par);
+ qunlock(&lck);
+ }
+ free(line);
+ }
+}
+
+void
+replayfile(void)
+{
+ char *line;
+ char *time, *pre, *cmd, *par[NPAR];
+ char scrtime[32];
+ Biobuf srv;
+ Binit(&srv, server_in, OREAD);
+
+ while ((line = Brdstr(&srv, '\n', 0)) != nil) {
+ if (!srvparse(line, &time, &pre, &cmd, par, NPAR)) {
+ Handler *hp = handlers;
+ qlock(&lck);
+ timestamp(time, scrtime, sizeof(scrtime));
+ while (hp->cmd != nil) {
+ if (!strcmp(hp->cmd, cmd)) {
+ hp->fun(server_out, scrtime, pre, cmd, par);
+ break;
+ }
+ ++hp;
+ }
+ if (hp->cmd == nil)
+ generic(server_out, scrtime, pre, cmd, par);
+ qunlock(&lck);
+ }
+ free(line);
+ }
+}
+
+/*
+ * display the last N lines from the conversation
+ * if we have a default target only the conversation with
+ * that target will be shown
+ */
+void
+seekback(int fd, int lines)
+{
+ Biobuf srv;
+ int found = 0, off;
+ char c, *line;
+
+ if(lines < 0)
+ return;
+
+ Binit(&srv, fd, OREAD);
+
+ Bseek(&srv, -2, 2);
+ while(((off = Boffset(&srv)) > 0) && found < lines) {
+ c = Bgetc(&srv);
+ Bungetc(&srv);
+ if(c == '\n') {
+ Bseek(&srv, 1, 1);
+ line = Brdstr(&srv, '\n', '\0');
+ if(victim) {
+ if(cistrstr(line, victim))
+ found++;
+ } else {
+ found++;
+ }
+ free(line);
+ }
+ Bseek(&srv, off-1, 0);
+ }
+
+ Bterm(&srv);
+}
+
+void
+main(int argc, char *argv[])
+{
+ char *charset = nil;
+ char buf[32], buf2[32], *out = nil, *in = nil;
+ char *arg;
+ int sb = 10; /* how many lines are we displaying initially */
+ int uipid;
+
+ ARGBEGIN {
+ case 't':
+ victim = strdup(EARGF(usage()));
+ break;
+ case 'b':
+ arg = ARGF();
+ if(arg != nil && arg[0] != '-')
+ sb = atoi(arg);
+ else
+ sb = 0; /* show all text */
+ break;
+ case 'c':
+ charset = EARGF(usage());
+ break;
+ case 'r':
+ replay = 1;
+ sb = 0;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ switch(argc) {
+ case 0:
+ break;
+ case 1:
+ if(replay)
+ in = argv[0];
+ else
+ out = argv[0];
+ break;
+ case 2:
+ out = argv[0];
+ in = argv[1];
+ break;
+ default:
+ usage();
+ }
+
+ if(out == nil) {
+ out = getuser();
+ if(strlen(out) > 4)
+ out[4] = 0;
+ snprint(buf, sizeof buf, "/srv/%sirc", out);
+ out = buf;
+ }
+ if(in == nil) {
+ in = getuser();
+ if(strlen(in) > 4)
+ in[4] = 0;
+ snprint(buf2, sizeof buf2, "/tmp/%sirc", in);
+ in = buf2;
+ }
+
+ if(!replay && (server_out = open(out, OWRITE)) < 0)
+ sysfatal("open write: %s %r", out);
+ if ((server_in = open(in, OREAD)) < 0)
+ sysfatal("open read: %s %r", in);
+
+ inacme = testacme();
+ getwidth();
+
+ if(sb)
+ seekback(server_in, sb);
+
+ while(read(server_in, buf, 1) > 0)
+ if(*buf == '\n')
+ break;
+
+ if(victim && cistrncmp(victim, "MSGS", 4)){
+ setwintitle(victim);
+ fprint(server_out, "JOIN %s\r\n", victim);
+ }
+ scr = 1;
+
+ server_in = follow(server_in);
+
+ if (charset != nil && strcmp(charset, "utf")) {
+ server_out = wtcs(server_out, charset);
+ server_in = rtcs(server_in, charset);
+ }
+
+ if(replay) {
+ replayfile();
+ } else {
+ if ((uipid = rfork(RFPROC|RFFDG|RFMEM)) == 0)
+ srvin();
+
+ usrin();
+
+ postnote(PNPROC, uipid, "kill");
+ while (waitpid() != uipid);
+ }
+
+ exits(0);
+}
+
+int
+wtcs(int fd, char *cset)
+{
+ int totcs[2];
+ int pid;
+
+ pipe(totcs);
+
+ pid = fork();
+
+ if (pid == 0) {
+ dup(totcs[0], 0);
+ dup(fd, 1);
+ close(totcs[1]);
+ execl("/bin/tcs", "tcs", "-f", "utf", "-t", cset, nil);
+ exits("execfailure");
+ }
+ close(totcs[0]);
+
+ return totcs[1];
+}
+
+int
+rtcs(int fd, char *cset)
+{
+ int fromtcs[2];
+ int pid;
+
+ pipe(fromtcs);
+
+ pid = fork();
+
+ if (pid == 0){
+ dup(fromtcs[1], 1);
+ dup(fd, 0);
+ close(fromtcs[0]);
+ execl("/bin/tcs", "tcs", "-f", cset, "-t", "utf", nil);
+ exits("execfailure");
+ }
+ close(fromtcs[1]);
+
+ return fromtcs[0];
+}
+
+int
+follow(int fd)
+{
+ int p[2], pid;
+ long n;
+ char buf[1024];
+ Dir *dp;
+
+ pipe(p);
+
+ pid = fork();
+ if (pid == 0){
+ dup(p[1], 1);
+ dup(fd, 0);
+ close(p[0]);
+ for(;;){
+ while((n = read(0, buf, sizeof(buf))) > 0)
+ write(1, buf, n);
+ sleep(1000);
+ dp = dirfstat(0);
+ free(dp);
+ }
+ }
+ close(p[1]);
+
+ return p[0];
+}
+
+char *
+prenick(char *p)
+{
+ char *n = p;
+ if (p != nil) {
+ while (*p != '\0' && *p != '!') ++p;
+ if (*p != '!')
+ n = nil;
+ *p = '\0';
+ }
+ return n;
+}
+
+int
+pmsg(int, char *time, char *pre, char *, char *par[])
+{
+ int n = 0;
+ char buf[8192];
+ char *c, *tc;
+
+/*
+ * if sent to victim, or comes from victim to non-channel, print.
+ * otherwise bail out.
+ */
+ pre = prenick(pre);
+ if(victim) {
+ if((cistrncmp(victim, "MSGS", 4) == 0) && *par[0] != '#') {
+ /* catch-all for messages, fall through */
+
+ } else if(cistrcmp(par[0], victim))
+ if(!pre || cistrcmp(pre, victim) || *par[0] == '#')
+ return 0;
+ }
+
+ if(!pre)
+ sprint(buf, "%s (%s) ⇐ %s\n", time, par[0], par[1]);
+ else if(*par[0] != '#')
+ sprint(buf, "%s (%s) ⇒ %s\n", time, pre, par[1]);
+ else
+ sprint(buf, "%s %s → %s\n", time, pre, par[1]);
+
+ c = buf;
+again:
+ if(strlen(c) >= linewidth) {
+ for(tc = c + linewidth; tc > c; tc--) {
+ switch(*tc) {
+ case ' ':
+ *tc = '\0';
+ n += fprint(scr, "%s\n", c);
+ c = tc+1;
+ goto again;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ n += fprint(scr, "%s", c);
+ return n;
+}
+
+int
+ntc(int, char *time, char *pre, char *, char *par[])
+{
+ int n;
+
+/*
+ * if sent to victim, or comes from victim to non-channel, print.
+ * otherwise bail out.
+ */
+ pre = prenick(pre);
+ if(victim && cistrcmp(par[0], victim))
+ if(!pre || cistrcmp(pre, victim) || *par[0] == '#')
+ return 0;
+
+ if(!pre)
+ n = fprint(scr, "%s [%s] ⇐\t%s\n", time, par[0], par[1]);
+ else if(*par[0] != '#')
+ n = fprint(scr, "%s [%s] ⇒\t%s\n", time, pre, par[1]);
+ else
+ n = fprint(scr, "%s [%s] %s →\t%s\n", time, par[0], pre, par[1]);
+ return n;
+}
+
+int
+generic(int, char *time, char *pre, char *cmd, char *par[])
+{
+ int i = 0, r;
+ char *nick = prenick(pre);
+
+/*
+ * don't print crud on screens with victim set
+ */
+ if(victim)
+ return 0;
+
+ if (nick != nil)
+ r = fprint(scr, "%s %s (%s)\t", time, cmd, nick);
+ else
+ r = fprint(scr, "%s %s (%s)\t", time, cmd, par[i++]);
+
+ for (; par[i] != nil; ++i)
+ r += fprint(scr, " %s", par[i]);
+
+ r += fprint(scr, "\n");
+
+ return r;
+}
+
+int
+misc(int, char *time, char *pre, char *cmd, char *par[])
+{
+ int i = 0, r;
+ char *nick = prenick(pre);
+
+ if(cistrcmp(cmd,"QUIT"))
+ if(victim && par[0] && cistrcmp(par[0], victim))
+ return 0;
+
+ if (nick != nil)
+ r = fprint(scr, "%s %s (%s)\t", time, cmd, nick);
+ else
+ r = fprint(scr, "%s %s %s\t", time, cmd, par[i++]);
+
+ for (; par[i] != nil; ++i)
+ r += fprint(scr, " %s", par[i]);
+
+ r += fprint(scr, "\n");
+
+ return r;
+}
+
+int
+numeric(int, char *time, char *pre, char *cmd, char *par[])
+{
+ int i = 0, r;
+ char *nick = prenick(pre);
+
+ if(victim && par[1] && cistrcmp(par[1], victim))
+ return 0;
+
+ if (nick != nil)
+ r = fprint(scr, "%s %s (%s)\t", time, cmd, nick);
+ else
+ r = fprint(scr, "%s %s (%s)\t", time, cmd, par[i++]);
+
+ for (; par[i] != nil; ++i)
+ r += fprint(scr, " %s", par[i]);
+
+ r += fprint(scr, "\n");
+
+ return r;
+}
+
+int
+usrparse(char *ln, char *cmd, char *par[], int npar)
+{
+ enum state st = Cmd;
+ int i;
+
+ for(i = 0; i < npar; i++)
+ par[i] = nil;
+
+ if (ln[0] == '/' && npar >= 2) {
+ *cmd = ln[1];
+ for (i = 1; ln[i] != '\0'; ++i) {
+ switch(st) {
+ case Cmd:
+ if (ln[i] == ' ') {
+ ln[i] = '\0';
+ par[0] = ln+i+1;
+ st = Middle;
+ } else if(ln[i] == '\n') {
+ /* enable commands with no arguments */
+ ln[i] = '\0';
+ par[0] = nil;
+ par[1] = nil;
+ st = Ok;
+ }
+ break;
+ case Middle:
+ if (ln[i] == '\r' || ln[i] == '\n') {
+ ln[i] = '\0';
+ st = Ok;
+ }
+ if (ln[i] == ' ') {
+ ln[i] = '\0';
+ par[1] = ln+i+1;
+ st = Trail;
+ }
+ break;
+ case Trail:
+ if (ln[i] == '\r' || ln[i] == '\n') {
+ ln[i] = '\0';
+ st = Ok;
+ }
+ break;
+ case Ok:
+ if (ln[i] == '\r' || ln[i] == '\n')
+ ln[i] = '\0';
+ break;
+ }
+ }
+ } else { /* send line to victim by default */
+ st = Ok;
+ *cmd = 'm';
+ for (i = 0; ln[i] != '\0'; ++i)
+ if (ln[i] == '\r' || ln[i] == '\n')
+ ln[i] = '\0';
+ par[0] = victim;
+ par[1] = ln;
+ }
+ return st == Ok ? 0 : 1;
+}
+
+int
+srvparse(char *line, char **time, char **pre, char **cmd, char *par[], int npar)
+{
+ int i;
+ char *p;
+ enum state st = Time;
+
+ *time = *pre = *cmd = nil;
+
+ for (*time = p = line, i = 0; *p != '\0'; ++p) {
+ switch (st) {
+ case Time:
+ if (*p == ' ') {
+ *p = '\0';
+ *cmd = p + 1;
+ st = Cmd;
+ }
+ break;
+ case Cmd:
+ if (*p == ':') {
+ *p = '\0';
+ *pre = p + 1;
+ st = Prefix;
+ } else if (*p == ' ') {
+ *p = '\0';
+ par[i] = p + 1;
+ st = Middle;
+ }
+ break;
+ case Prefix:
+ if (*p == ' ') {
+ *p = '\0';
+ *cmd = p + 1;
+ st = Cmd;
+ }
+ break;
+ case Middle:
+ if (*p == '\r' || *p == '\n') {
+ *p = '\0';
+ st = Ok;
+ } else if (*p == ':') {
+ *p = '\0';
+ par[i] = p + 1;
+ st = Trail;
+ } else if (*p == ' ') {
+ *p = '\0';
+ i = (i + 1) % npar;
+ par[i] = p + 1;
+ st = Middle;
+ }
+ break;
+ case Trail:
+ if (*p == '\r' || *p == '\n') {
+ *p = '\0';
+ st = Ok;
+ }
+ break;
+ case Ok:
+ *p = '\0';
+ break;
+ }
+ }
+ par[(i + 1) % npar] = nil;
+ return st == Ok ? 0 : 1;
+}
+
+void
+getwidth(void)
+{
+ Font *font;
+ int n, fd, mintab;
+ char buf[128], *f[10], *p;
+
+ if(inacme){
+ if((fd = open("/dev/acme/ctl", OREAD)) < 0)
+ return;
+ n = read(fd, buf, sizeof buf-1);
+ close(fd);
+ if(n <= 0)
+ return;
+ buf[n] = 0;
+ n = tokenize(buf, f, nelem(f));
+ if(n < 7)
+ return;
+ if((font = openfont(nil, f[6])) == nil)
+ return;
+ mintab = stringwidth(font, "0");
+ linewidth = atoi(f[5]);
+ linewidth = linewidth/mintab;
+ return;
+ }
+
+ if((p = getenv("font")) == nil)
+ return;
+ if((font = openfont(nil, p)) == nil)
+ return;
+ if((fd = open("/dev/window", OREAD)) < 0)
+ return;
+
+ n = read(fd, buf, 5*12);
+ close(fd);
+
+ if(n < 5*12)
+ return;
+
+ buf[n] = 0;
+
+ /* window stucture:
+ 4 bit left edge
+ 1 bit gap
+ 12 bit scrollbar
+ 4 bit gap
+ text
+ 4 bit right edge
+ */
+ linewidth = atoi(buf+3*12) - atoi(buf+1*12) - (4+1+12+4+4);
+ mintab = stringwidth(font, "0");
+ linewidth = linewidth/mintab;
+}