#include #include #include #include #include "irc.h" #include "screen.h" #include "edbox.h" #include "numerics.h" #include #include #include "nio.h" #define MAX_MSG 512 char *msg_src, *msg_cmd, *msg_dest, *msg_text; char *msg_args[MAX_MSG_ARGS]; int msg_argcount; static char msgbuf[MAX_MSG] = { 0 }; static char *msg; /* with source removed */ static int msgbuf_len = 0, msg_len = 0; static char regged = 0, hilite = 0; /* static void join_channel(void) { txbuf_set_str2("JOIN ", channel); txbuf_send(); } */ static void print_reason(void) { if(msg_text) { scr_print_active(": "); scr_print_active(msg_text); } scr_eol_active(); } static void do_pong(void) { scr_print_server("PING/PONG\n"); /* make hiding this a preference, or just ditch it */ txbuf_set_str2("PONG ", msg_args[0]); txbuf_send(); } static void hilite_bold(void) { if(hilite) scr_print_active("\x02"); } static void do_chan_nick(void) { hilite_bold(); scr_print_active("<"); hilite_bold(); scr_print_active(msg_src); if(scr_active == SCR_SERVER) { /* if we don't have a window for it */ scr_print_active("/"); scr_print_active(msg_dest); } hilite_bold(); scr_print_active("> "); hilite_bold(); } static void do_priv_nick(void) { scr_print_active("*"); scr_print_active(msg_src); scr_print_active("* "); } static void do_privmsg(void) { /* TODO: this shouldn't be case-sensitive */ if(strstr(msg_text, usernick)) hilite = 1; else hilite = 0; if(*msg_dest == '#') do_chan_nick(); else do_priv_nick(); scr_print_active(msg_text); scr_eol_active(); } static void do_join(void) { if(streq_i(usernick, msg_src)) { /* if our nick is the source, create the screen if needed. if a screen can't be created, no harm done. */ if(scr_active == SCR_SERVER) scr_create(msg_dest, 1); } else { scr_print_active(msg_src); scr_print_active(" has joined "); scr_print_active(msg_dest); scr_eol_active(); } } static void do_nick(void) { if(streq_i(usernick, msg_src)) { scr_print_active("You are "); strncpy(usernick, msg_src, 32); } else { scr_print_active(msg_src); scr_print_active(" is "); } scr_print_active(" now known as "); scr_print_active(msg_dest); scr_eol_active(); } static void do_quit(void) { scr_print_active(msg_src); scr_print_active(" has quit IRC"); print_reason(); } static void do_part(void) { scr_print_active(msg_src); scr_print_active(" has left "); scr_print_active(msg_dest); if(msg_text) { scr_print_active(": "); scr_print_active(msg_text); } scr_eol_active(); } static void do_topic(void) { scr_print_active(msg_src); scr_print_active(" has set the topic of "); scr_print_active(msg_dest); scr_print_active(" to: "); scr_print_active(msg_text); scr_eol_active(); /* TODO: set topic in the screen! */ } static void do_kick(void) { scr_print_active(msg_src); scr_print_active(" has kicked "); scr_print_active(msg_args[2]); scr_print_active(" from "); scr_print_active(msg_dest); print_reason(); } static void do_mode(void) { int i; scr_print_active(msg_src); scr_print_active(" sets mode: "); for(i = 0; i < msg_argcount; i++) { scr_print_active(" "); scr_print_active(msg_args[i]); } scr_eol_active(); } /* numerics call this with arg==1, since arg 0 is always our nick. other commands call with arg==0 to print everything. */ static void do_catchall(int arg) { if(msg_src) { scr_print_active(msg_src); scr_print_active(" "); } scr_print_active(msg_cmd); for(; arg < msg_argcount; arg++) { scr_print_active(" "); scr_print_active(msg_args[arg]); } if(msg_text) { scr_print_active(" "); scr_print_active(msg_text); } scr_eol_active(); } /* permutes last character (doesn't add one), so for "Bob" you get: Bo_, Bo1 through Bo9, BoA through BoZ Gives a total of 36 replacement nicks to try. Eventually we run out and start repeating, but by then the IRC server will have disconnected us. */ static void permute_nick(void) { char *last; last = usernick + strlen(usernick) - 1; if((*last >= '1' && *last < '9') || (*last >= 'A' && *last < 'Z')) { (*last)++; } else { switch(*last) { case '_': *last = '1'; break; case '9': *last = 'A'; break; default: *last = '_'; break; } } } static void do_numeric(void) { char s; unsigned int num = atoi(msg_cmd); switch(num) { /* use the server's idea of what our nick is, in case it got truncated. */ case RPL_WELCOME: strcpy(usernick, msg_args[0]); regged = 1; do_catchall(1); break; /* case RPL_NAMREPLY: "Users in ##channel", msg_text case RPL_TOPIC: case RPL_NOTOPIC: set the topic for the screen, if there is one case RPL_TOPICWHOTIME: "Topic was set by ", don't try to parse the UNIX timestamp. case RPL_WHOISCERTFP: case RPL_WHOISREGNICK: case RPL_WHOISUSER: case RPL_WHOISSERVER: case RPL_WHOISOPERATOR: case RPL_WHOISIDLE: case RPL_WHOISCHANNELS: case RPL_WHOISSPECIAL: case RPL_WHOISACCOUNT: case RPL_WHOISACTUALLY: case RPL_WHOISHOST: case RPL_WHOISMODES: case RPL_WHOISSECURE: case RPL_WHOWASUSER: These always go to the server window. case RPL_ENDOFWHOIS: case RPL_ENDOFWHOWAS: Don't print. */ case RPL_ENDOFNAMES: /* don't print, just noise */ break; case ERR_NICKNAMEINUSE: do_catchall(0); if(!regged) { permute_nick(); irc_register(); } break; /* don't print these, just noise */ case RPL_MOTDSTART: case RPL_ENDOFMOTD: case ERR_NOMOTD: break; case RPL_TOPIC: s = scr_getbyname(msg_args[1]); scr_print(s, "Topic for "); scr_print(s, msg_args[1]); scr_print(s, ": "); scr_print(s, msg_text); scr_putc(s, '\n'); break; case RPL_TOPICWHOTIME: s = scr_getbyname(msg_args[1]); scr_print(s, "Topic set by: "); scr_print(s, msg_args[1]); scr_putc(s, '\n'); break; default: do_catchall(1); break; } } static void invalid_msg(char type) { scr_print(SCR_SERVER, "??? unknown, type "); scr_putc(SCR_SERVER, type); scr_putc(SCR_SERVER, '\n'); } void select_screen(void) { char s; if(!msg_src || !msg_dest) { s = SCR_SERVER; } else { s = scr_getbyname(msg_dest); if(!s && msg_dest[0] != '#') s = SCR_PRIV; } scr_activate(s); } static void dispatch_msg(void) { /* at this point, we know the message source and destination, so: */ select_screen(); if(streq_i(msg_cmd, "PRIVMSG")) { do_privmsg(); } else if(streq_i(msg_cmd, "JOIN")) { do_join(); } else if(streq_i(msg_cmd, "NICK")) { do_nick(); } else if(streq_i(msg_cmd, "QUIT")) { do_quit(); } else if(streq_i(msg_cmd, "PART")) { do_part(); } else if(streq_i(msg_cmd, "TOPIC")) { do_topic(); } else if(streq_i(msg_cmd, "KICK")) { do_kick(); } else if(streq_i(msg_cmd, "MODE")) { do_mode(); } else if(isdigit(msg_cmd[0])) { do_numeric(); } else { do_catchall(0); } } /* msgbuf contains a complete message from the server, whose length is msgbuf_len. The EOL that was at the end of the message is *not* present here (been replaced with a null already). */ static void parse_msg(void) { char *p; msg_cmd = msg_text = msg_src = msg_dest = 0; msg = msgbuf; /* ignore empty message */ if(!*msg) return; /* if there's a final multiword arg... */ /* FIXME: channel names can have colons, which breaks this... */ p = strstr(msg + 1, " :"); /* +1 to skip leading colon in msg source */ if(p) { msg_text = p + 2; *p = 0; } /* first token is either the source (with a :) or a command (without) */ p = strtok(msg, " "); if(!p) { invalid_msg('1'); return; } if(*p == ':') { msg_src = p; /* generally :irc.example.com or :nick!user@host */ msg_cmd = strtok(0, " "); } else { msg_src = 0; /* no source supplied */ msg_cmd = p; } if(!msg_cmd) { invalid_msg('2'); return; } /* special case for ping, treat as 1 arg, even if it has space and no : */ if(streq_i(msg_cmd, "PING")) { msg_argcount = 1; msg_args[0] = msg_cmd + 6; do_pong(); return; } else { for(msg_argcount = 0; msg_argcount < MAX_MSG_ARGS; msg_argcount++) { p = strtok(0, " "); if(p) { msg_args[msg_argcount] = p; } else { break; } } } if(msg_argcount) msg_dest = msg_args[0]; if(msg_src) { if((p = strstr(msg_src, "!"))) { msg_src++; *p = '\0'; } else if(strstr(msg_src, ".")) { msg_src = 0; } } dispatch_msg(); } /* we read via block I/O, so split into lines and process each one separately. */ static void irc_split_Lines(void) { int i; char *p = rx_buf; for(i = 0; i < rxbuflen; i++) { msgbuf[msgbuf_len] = *p; if(*p == CH_EOL) { // msgbuf[msgbuf_len + 1] = '\0'; /* do not include the EOL */ msgbuf[msgbuf_len] = '\0'; parse_msg(); msgbuf_len = 0; } else { msgbuf_len++; } p++; } } /* TODO: there needs to be a scr_printnum() */ void print_errnum(void) { extern unsigned char err; char tmp[10]; scr_print_current("Error #"); itoa(err, tmp, 10); scr_print_current(tmp); scr_print_current(", press any key...\n"); } int irc_read(void) { if(!trip) return 1; err = nstatus(url); if(err == 136) { scr_print_current("Disconnected, press any key...\n"); cgetc(); return 0; } else if(err != 1) { print_errnum(); cgetc(); return 0; } // Get # of bytes waiting, no more than size of rx_buf rxbuflen = OS.dvstat[1] * 256 + OS.dvstat[0]; if(rxbuflen > MAX_MSG) rxbuflen = MAX_MSG; if(rxbuflen > 0) { err = nread(url, rx_buf, rxbuflen); if(err != 1) { scr_print_current("READ ERROR: "); print_errnum(); return 0; } trip = 0; PIA.pactl |= 1; // Flag interrupt as serviced, ready for next one. irc_split_Lines(); } return 1; } /* modern.ircdocs.horse say to do this IMMEDIATELY upon TCP connection, without waiting for anything from the server. */ void irc_register(void) { if(regged) return; /* 2nd arg: local (UNIX) username, just use the nick */ /* 3rd arg: "real" name (make it a pref?) */ txbuf_set_str3("USER ", usernick, " 0 * :FujiNetChat User"); txbuf_send(); txbuf_set_str2("NICK ", usernick); txbuf_send(); } static void start_keystroke(void) { char i, s; i = cgetc(); if(i >= '1' && i <= '7') { s = i - '1'; if(scr_status[s] != SCR_UNUSED) scr_display(s); } } static void keystroke(void) { if(OS.ch == 0xff) return; if(GTIA_READ.consol == 6) { /* start pressed */ start_keystroke(); } else { edbox_keystroke(); } } /* only exits on error (e.g. connection closed, which might be via /QUIT). */ void irc_loop(void) { while(1) { if(!irc_read()) return; keystroke(); } }