diff options
Diffstat (limited to 'handle.c')
-rw-r--r-- | handle.c | 740 |
1 files changed, 463 insertions, 277 deletions
diff --git a/handle.c b/handle.c index fcc0c5d..5a2cf7c 100644 --- a/handle.c +++ b/handle.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 C. McEnroe <june@causal.agency> +/* Copyright (C) 2020 June McEnroe <june@causal.agency> * * 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 @@ -33,10 +33,11 @@ #include <stdlib.h> #include <string.h> #include <sysexits.h> +#include <wchar.h> #include "chat.h" -struct Replies replies; +uint replies[ReplyCap]; static const char *CapNames[] = { #define X(name, id) [id##Bit] = name, @@ -62,21 +63,21 @@ static enum Cap capParse(const char *list) { return caps; } -static const char *capList(enum Cap caps) { - static char buf[1024]; - buf[0] = '\0'; - struct Cat cat = { buf, sizeof(buf), 0 }; +static void capList(char *buf, size_t cap, enum Cap caps) { + *buf = '\0'; + char *ptr = buf, *end = &buf[cap]; for (size_t i = 0; i < ARRAY_LEN(CapNames); ++i) { if (caps & (1 << i)) { - catf(&cat, "%s%s", (buf[0] ? " " : ""), CapNames[i]); + ptr = seprintf( + ptr, end, "%s%s", (ptr > buf ? " " : ""), CapNames[i] + ); } } - return buf; } static void require(struct Message *msg, bool origin, uint len) { if (origin) { - if (!msg->nick) errx(EX_PROTOCOL, "%s missing origin", msg->cmd); + if (!msg->nick) msg->nick = "*.*"; if (!msg->user) msg->user = msg->nick; if (!msg->host) msg->host = msg->user; } @@ -90,13 +91,25 @@ static const time_t *tagTime(const struct Message *msg) { static time_t time; struct tm tm; if (!msg->tags[TagTime]) return NULL; - if (!strptime(msg->tags[TagTime], "%FT%T", &tm)) return NULL; + if (!strptime(msg->tags[TagTime], "%Y-%m-%dT%T", &tm)) return NULL; time = timegm(&tm); return &time; } typedef void Handler(struct Message *msg); +static void handleStandardReply(struct Message *msg) { + require(msg, false, 3); + for (uint i = 2; i < ParamCap - 1; ++i) { + if (msg->params[i + 1]) continue; + uiFormat( + Network, Warm, tagTime(msg), + "%s", msg->params[i] + ); + break; + } +} + static void handleErrorGeneric(struct Message *msg) { require(msg, false, 2); if (msg->params[2]) { @@ -114,10 +127,33 @@ static void handleErrorGeneric(struct Message *msg) { } } +static void handleReplyGeneric(struct Message *msg) { + uint first = 1; + uint id = Network; + if (msg->params[1] && strchr(network.chanTypes, msg->params[1][0])) { + id = idFor(msg->params[1]); + first++; + } + char buf[1024]; + char *ptr = buf, *end = &buf[sizeof(buf)]; + ptr = seprintf(ptr, end, "\3%d(%s)\3\t", Gray, msg->cmd); + for (uint i = first; i < ParamCap && msg->params[i]; ++i) { + ptr = seprintf( + ptr, end, "%s%s", (i > first ? " " : ""), msg->params[i] + ); + } + uiWrite(id, Ice, tagTime(msg), buf); +} + static void handleErrorNicknameInUse(struct Message *msg) { require(msg, false, 2); if (!strcmp(self.nick, "*")) { - ircFormat("NICK :%s_\r\n", msg->params[1]); + static uint i = 1; + if (i < ARRAY_LEN(self.nicks) && self.nicks[i]) { + ircFormat("NICK %s\r\n", self.nicks[i++]); + } else { + ircFormat("NICK %s_\r\n", msg->params[1]); + } } else { handleErrorGeneric(msg); } @@ -142,14 +178,18 @@ static void handleCap(struct Message *msg) { caps &= ~CapConsumer; } if (caps) { - ircFormat("CAP REQ :%s\r\n", capList(caps)); + char buf[512]; + capList(buf, sizeof(buf), caps); + ircFormat("CAP REQ :%s\r\n", buf); } else { if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); } } else if (!strcmp(msg->params[1], "ACK")) { self.caps |= caps; if (caps & CapSASL) { - ircFormat("AUTHENTICATE %s\r\n", (self.plain ? "PLAIN" : "EXTERNAL")); + ircFormat( + "AUTHENTICATE %s\r\n", (self.plainUser ? "PLAIN" : "EXTERNAL") + ); } if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); } else if (!strcmp(msg->params[1], "NAK")) { @@ -188,33 +228,34 @@ static void base64(char *dst, const byte *src, size_t len) { static void handleAuthenticate(struct Message *msg) { (void)msg; - if (!self.plain) { + if (!self.plainUser) { ircFormat("AUTHENTICATE +\r\n"); return; } - byte buf[299]; - size_t len = 1 + strlen(self.plain); - if (sizeof(buf) < len) errx(EX_CONFIG, "SASL PLAIN is too long"); - buf[0] = 0; - for (size_t i = 0; self.plain[i]; ++i) { - buf[1 + i] = (self.plain[i] == ':' ? 0 : self.plain[i]); - } + byte buf[299] = {0}; + size_t userLen = strlen(self.plainUser); + size_t passLen = strlen(self.plainPass); + size_t len = 1 + userLen + 1 + passLen; + if (sizeof(buf) < len) errx(EX_USAGE, "SASL PLAIN is too long"); + memcpy(&buf[1], self.plainUser, userLen); + memcpy(&buf[1 + userLen + 1], self.plainPass, passLen); char b64[BASE64_SIZE(sizeof(buf))]; base64(b64, buf, len); ircFormat("AUTHENTICATE "); - ircSend(b64, BASE64_SIZE(len)); + ircSend(b64, BASE64_SIZE(len) - 1); ircFormat("\r\n"); explicit_bzero(b64, sizeof(b64)); explicit_bzero(buf, sizeof(buf)); - explicit_bzero(self.plain, strlen(self.plain)); + explicit_bzero(self.plainPass, strlen(self.plainPass)); } static void handleReplyLoggedIn(struct Message *msg) { (void)msg; ircFormat("CAP END\r\n"); + handleReplyGeneric(msg); } static void handleErrorSASLFail(struct Message *msg) { @@ -225,30 +266,38 @@ static void handleErrorSASLFail(struct Message *msg) { static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); - completeTouch(Network, self.nick, Default); + completePull(Network, self.nick, Default); + if (self.mode) ircFormat("MODE %s %s\r\n", self.nick, self.mode); if (self.join) { uint count = 1; for (const char *ch = self.join; *ch && *ch != ' '; ++ch) { if (*ch == ',') count++; } ircFormat("JOIN %s\r\n", self.join); - replies.join += count; - replies.topic += count; - replies.names += count; + if (count == 1) replies[ReplyJoin]++; + replies[ReplyTopicAuto] += count; + replies[ReplyNamesAuto] += count; } + commandCompletion(); + handleReplyGeneric(msg); } static void handleReplyISupport(struct Message *msg) { + handleReplyGeneric(msg); for (uint i = 1; i < ParamCap; ++i) { if (!msg->params[i]) break; char *key = strsep(&msg->params[i], "="); if (!strcmp(key, "NETWORK")) { if (!msg->params[i]) continue; set(&network.name, msg->params[i]); - uiFormat( - Network, Cold, tagTime(msg), - "You arrive in %s", msg->params[i] - ); + static bool arrived; + if (!arrived) { + uiFormat( + Network, Cold, tagTime(msg), + "You arrive in %s", msg->params[i] + ); + arrived = true; + } } else if (!strcmp(key, "USERLEN")) { if (!msg->params[i]) continue; network.userLen = strtoul(msg->params[i], NULL, 10); @@ -258,6 +307,9 @@ static void handleReplyISupport(struct Message *msg) { } else if (!strcmp(key, "CHANTYPES")) { if (!msg->params[i]) continue; set(&network.chanTypes, msg->params[i]); + } else if (!strcmp(key, "STATUSMSG")) { + if (!msg->params[i]) continue; + set(&network.statusmsg, msg->params[i]); } else if (!strcmp(key, "PREFIX")) { strsep(&msg->params[i], "("); char *modes = strsep(&msg->params[i], ")"); @@ -290,7 +342,7 @@ static void handleReplyISupport(struct Message *msg) { static void handleReplyMOTD(struct Message *msg) { require(msg, false, 2); char *line = msg->params[1]; - urlScan(Network, msg->nick, line); + urlScan(Network, NULL, line); if (!strncmp(line, "- ", 2)) { uiFormat(Network, Cold, tagTime(msg), "\3%d-\3\t%s", Gray, &line[2]); } else { @@ -302,6 +354,12 @@ static void handleErrorNoMOTD(struct Message *msg) { (void)msg; } +static void handleReplyHelp(struct Message *msg) { + require(msg, false, 3); + urlScan(Network, NULL, msg->params[2]); + uiWrite(Network, Warm, tagTime(msg), msg->params[2]); +} + static void handleJoin(struct Message *msg) { require(msg, true, 1); uint id = idFor(msg->params[0]); @@ -314,23 +372,23 @@ static void handleJoin(struct Message *msg) { set(&self.host, msg->host); } idColors[id] = hash(msg->params[0]); - completeTouch(None, msg->params[0], idColors[id]); - if (replies.join) { - uiShowID(id); - replies.join--; + completePull(None, msg->params[0], idColors[id]); + if (replies[ReplyJoin]) { + windowShow(windowFor(id)); + replies[ReplyJoin]--; } } - completeTouch(id, msg->nick, hash(msg->user)); + completePull(id, msg->nick, hash(msg->user)); if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) { msg->params[2] = NULL; } uiFormat( - id, ignoreCheck(Cold, id, msg), tagTime(msg), + id, filterCheck(Cold, id, msg), tagTime(msg), "\3%02d%s\3\t%s%s%sarrives in \3%02d%s\3", hash(msg->user), msg->nick, (msg->params[2] ? "(" : ""), (msg->params[2] ?: ""), - (msg->params[2] ? ") " : ""), + (msg->params[2] ? "\17) " : ""), hash(msg->params[0]), msg->params[0] ); logFormat(id, tagTime(msg), "%s arrives in %s", msg->nick, msg->params[0]); @@ -352,10 +410,10 @@ static void handlePart(struct Message *msg) { require(msg, true, 1); uint id = idFor(msg->params[0]); if (!strcmp(msg->nick, self.nick)) { - completeClear(id); + completeRemove(id, NULL); } completeRemove(id, msg->nick); - enum Heat heat = ignoreCheck(Cold, id, msg); + enum Heat heat = filterCheck(Cold, id, msg); if (heat > Ice) urlScan(id, msg->nick, msg->params[1]); uiFormat( id, heat, tagTime(msg), @@ -374,7 +432,7 @@ static void handleKick(struct Message *msg) { require(msg, true, 2); uint id = idFor(msg->params[0]); bool kicked = !strcmp(msg->params[1], self.nick); - completeTouch(id, msg->nick, hash(msg->user)); + completePull(id, msg->nick, hash(msg->user)); urlScan(id, msg->nick, msg->params[2]); uiFormat( id, (kicked ? Hot : Cold), tagTime(msg), @@ -391,21 +449,22 @@ static void handleKick(struct Message *msg) { (msg->params[2] ? ": " : ""), (msg->params[2] ?: "") ); completeRemove(id, msg->params[1]); - if (kicked) completeClear(id); + if (kicked) completeRemove(id, NULL); } static void handleNick(struct Message *msg) { require(msg, true, 1); if (!strcmp(msg->nick, self.nick)) { set(&self.nick, msg->params[0]); - uiRead(); // Update prompt. + inputUpdate(); } - for (uint id; (id = completeID(msg->nick));) { + struct Cursor curs = {0}; + for (uint id; (id = completeEachID(&curs, msg->nick));) { if (!strcmp(idNames[id], msg->nick)) { set(&idNames[id], msg->params[0]); } uiFormat( - id, ignoreCheck(Cold, id, msg), tagTime(msg), + id, filterCheck(Cold, id, msg), tagTime(msg), "\3%02d%s\3\tis now known as \3%02d%s\3", hash(msg->user), msg->nick, hash(msg->user), msg->params[0] ); @@ -415,13 +474,27 @@ static void handleNick(struct Message *msg) { msg->nick, msg->params[0] ); } - completeReplace(None, msg->nick, msg->params[0]); + completeReplace(msg->nick, msg->params[0]); +} + +static void handleSetname(struct Message *msg) { + require(msg, true, 1); + struct Cursor curs = {0}; + for (uint id; (id = completeEachID(&curs, msg->nick));) { + uiFormat( + id, filterCheck(Cold, id, msg), tagTime(msg), + "\3%02d%s\3\tis now known as \3%02d%s\3 (%s\17)", + hash(msg->user), msg->nick, hash(msg->user), msg->nick, + msg->params[0] + ); + } } static void handleQuit(struct Message *msg) { require(msg, true, 0); - for (uint id; (id = completeID(msg->nick));) { - enum Heat heat = ignoreCheck(Cold, id, msg); + struct Cursor curs = {0}; + for (uint id; (id = completeEachID(&curs, msg->nick));) { + enum Heat heat = filterCheck(Cold, id, msg); if (heat > Ice) urlScan(id, msg->nick, msg->params[0]); uiFormat( id, heat, tagTime(msg), @@ -442,8 +515,9 @@ static void handleQuit(struct Message *msg) { static void handleInvite(struct Message *msg) { require(msg, true, 2); if (!strcmp(msg->params[0], self.nick)) { + set(&self.invited, msg->params[1]); uiFormat( - Network, ignoreCheck(Hot, Network, msg), tagTime(msg), + Network, filterCheck(Hot, Network, msg), tagTime(msg), "\3%02d%s\3\tinvites you to \3%02d%s\3", hash(msg->user), msg->nick, hash(msg->params[1]), msg->params[1] ); @@ -465,7 +539,6 @@ static void handleInvite(struct Message *msg) { static void handleReplyInviting(struct Message *msg) { require(msg, false, 3); - if (self.caps & CapInviteNotify) return; struct Message invite = { .nick = self.nick, .user = self.user, @@ -477,10 +550,10 @@ static void handleReplyInviting(struct Message *msg) { } static void handleErrorUserOnChannel(struct Message *msg) { - require(msg, false, 4); + require(msg, false, 3); uint id = idFor(msg->params[2]); uiFormat( - id, Cold, tagTime(msg), + id, Warm, tagTime(msg), "\3%02d%s\3 is already in \3%02d%s\3", completeColor(id, msg->params[1]), msg->params[1], hash(msg->params[2]), msg->params[2] @@ -490,21 +563,28 @@ static void handleErrorUserOnChannel(struct Message *msg) { static void handleReplyNames(struct Message *msg) { require(msg, false, 4); uint id = idFor(msg->params[2]); - char buf[1024] = ""; - struct Cat cat = { buf, sizeof(buf), 0 }; + char buf[1024]; + char *ptr = buf, *end = &buf[sizeof(buf)]; while (msg->params[3]) { char *name = strsep(&msg->params[3], " "); char *prefixes = strsep(&name, "!"); char *nick = &prefixes[strspn(prefixes, network.prefixes)]; char *user = strsep(&name, "@"); enum Color color = (user ? hash(user) : Default); - completeAdd(id, nick, color); - if (!replies.names) continue; - catf(&cat, "%s\3%02d%s\3", (buf[0] ? ", " : ""), color, prefixes); + uint bits = 0; + for (char *p = prefixes; p < nick; ++p) { + bits |= prefixBit(*p); + } + completePush(id, nick, color); + *completeBits(id, nick) = bits; + if (!replies[ReplyNames] && !replies[ReplyNamesAuto]) continue; + ptr = seprintf( + ptr, end, "%s\3%02d%s\3", (ptr > buf ? ", " : ""), color, prefixes + ); } - if (!replies.names) return; + if (ptr == buf) return; uiFormat( - id, Cold, tagTime(msg), + id, (replies[ReplyNamesAuto] ? Cold : Warm), tagTime(msg), "In \3%02d%s\3 are %s", hash(msg->params[2]), msg->params[2], buf ); @@ -512,15 +592,17 @@ static void handleReplyNames(struct Message *msg) { static void handleReplyEndOfNames(struct Message *msg) { (void)msg; - if (replies.names) replies.names--; + if (replies[ReplyNamesAuto]) { + replies[ReplyNamesAuto]--; + } else if (replies[ReplyNames]) { + replies[ReplyNames]--; + } } static void handleReplyNoTopic(struct Message *msg) { require(msg, false, 2); - if (!replies.topic) return; - replies.topic--; uiFormat( - idFor(msg->params[1]), Cold, tagTime(msg), + idFor(msg->params[1]), Warm, tagTime(msg), "There is no sign in \3%02d%s\3", hash(msg->params[1]), msg->params[1] ); @@ -528,14 +610,15 @@ static void handleReplyNoTopic(struct Message *msg) { static void topicComplete(uint id, const char *topic) { char buf[512]; - const char *prev = complete(id, "/topic "); + struct Cursor curs = {0}; + const char *prev = completePrefix(&curs, id, "/topic "); if (prev) { snprintf(buf, sizeof(buf), "%s", prev); completeRemove(id, buf); } if (topic) { snprintf(buf, sizeof(buf), "/topic %s", topic); - completeAdd(id, buf, Default); + completePush(id, buf, Default); } } @@ -543,11 +626,10 @@ static void handleReplyTopic(struct Message *msg) { require(msg, false, 3); uint id = idFor(msg->params[1]); topicComplete(id, msg->params[2]); - if (!replies.topic) return; - replies.topic--; + if (!replies[ReplyTopic] && !replies[ReplyTopicAuto]) return; urlScan(id, NULL, msg->params[2]); uiFormat( - id, Cold, tagTime(msg), + id, (replies[ReplyTopicAuto] ? Cold : Warm), tagTime(msg), "The sign in \3%02d%s\3 reads: %s", hash(msg->params[1]), msg->params[1], msg->params[2] ); @@ -555,25 +637,45 @@ static void handleReplyTopic(struct Message *msg) { id, tagTime(msg), "The sign in %s reads: %s", msg->params[1], msg->params[2] ); + if (replies[ReplyTopicAuto]) { + replies[ReplyTopicAuto]--; + } else { + replies[ReplyTopic]--; + } +} + +static void swap(wchar_t *a, wchar_t *b) { + wchar_t x = *a; + *a = *b; + *b = x; +} + +static char *highlightMiddle( + char *ptr, char *end, enum Color color, + wchar_t *str, size_t pre, size_t suf +) { + wchar_t nul = L'\0'; + swap(&str[pre], &nul); + ptr = seprintf(ptr, end, "%ls", str); + swap(&str[pre], &nul); + swap(&str[suf], &nul); + if (hashBound) { + ptr = seprintf( + ptr, end, "\3%02d,%02d%ls\3%02d,%02d", + Default, color, &str[pre], Default, Default + ); + } else { + ptr = seprintf(ptr, end, "\26%ls\26", &str[pre]); + } + swap(&str[suf], &nul); + ptr = seprintf(ptr, end, "%ls", &str[suf]); + return ptr; } static void handleTopic(struct Message *msg) { require(msg, true, 2); uint id = idFor(msg->params[0]); - if (msg->params[1][0]) { - topicComplete(id, msg->params[1]); - urlScan(id, msg->nick, msg->params[1]); - uiFormat( - id, Warm, tagTime(msg), - "\3%02d%s\3\tplaces a new sign in \3%02d%s\3: %s", - hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], - msg->params[1] - ); - logFormat( - id, tagTime(msg), "%s places a new sign in %s: %s", - msg->nick, msg->params[0], msg->params[1] - ); - } else { + if (!msg->params[1][0]) { topicComplete(id, NULL); uiFormat( id, Warm, tagTime(msg), @@ -584,7 +686,62 @@ static void handleTopic(struct Message *msg) { id, tagTime(msg), "%s removes the sign in %s", msg->nick, msg->params[0] ); + return; } + + struct Cursor curs = {0}; + const char *prev = completePrefix(&curs, id, "/topic "); + if (prev) { + prev += 7; + } else { + goto plain; + } + + wchar_t old[512]; + wchar_t new[512]; + if (swprintf(old, ARRAY_LEN(old), L"%s", prev) < 0) goto plain; + if (swprintf(new, ARRAY_LEN(new), L"%s", msg->params[1]) < 0) goto plain; + + size_t pre; + for (pre = 0; old[pre] && new[pre] && old[pre] == new[pre]; ++pre); + size_t osuf = wcslen(old); + size_t nsuf = wcslen(new); + while (osuf > pre && nsuf > pre && old[osuf-1] == new[nsuf-1]) { + osuf--; + nsuf--; + } + + char buf[1024]; + char *ptr = buf, *end = &buf[sizeof(buf)]; + ptr = seprintf( + ptr, end, "\3%02d%s\3\ttakes down the sign in \3%02d%s\3: ", + hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] + ); + ptr = highlightMiddle(ptr, end, Brown, old, pre, osuf); + if (osuf != pre) uiWrite(id, Cold, tagTime(msg), buf); + ptr = buf; + ptr = seprintf( + ptr, end, "\3%02d%s\3\tplaces a new sign in \3%02d%s\3: ", + hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] + ); + ptr = highlightMiddle(ptr, end, Green, new, pre, nsuf); + uiWrite(id, Warm, tagTime(msg), buf); + goto log; + +plain: + uiFormat( + id, Warm, tagTime(msg), + "\3%02d%s\3\tplaces a new sign in \3%02d%s\3: %s", + hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], + msg->params[1] + ); +log: + logFormat( + id, tagTime(msg), "%s places a new sign in %s: %s", + msg->nick, msg->params[0], msg->params[1] + ); + topicComplete(id, msg->params[1]); + urlScan(id, msg->nick, msg->params[1]); } static const char *UserModes[256] = { @@ -597,20 +754,19 @@ static const char *UserModes[256] = { static void handleReplyUserModeIs(struct Message *msg) { require(msg, false, 2); - if (!replies.mode) return; - replies.mode--; - - char buf[1024] = ""; - struct Cat cat = { buf, sizeof(buf), 0 }; + char buf[1024]; + char *ptr = buf, *end = &buf[sizeof(buf)]; for (char *ch = msg->params[1]; *ch; ++ch) { if (*ch == '+') continue; const char *name = UserModes[(byte)*ch]; - catf(&cat, ", +%c%s%s", *ch, (name ? " " : ""), (name ?: "")); + ptr = seprintf( + ptr, end, ", +%c%s%s", *ch, (name ? " " : ""), (name ?: "") + ); } uiFormat( Network, Warm, tagTime(msg), "\3%02d%s\3\tis %s", - self.color, self.nick, (buf[0] ? &buf[2] : "modeless") + self.color, self.nick, (ptr > buf ? &buf[2] : "modeless") ); } @@ -631,12 +787,9 @@ static const char *ChanModes[256] = { static void handleReplyChannelModeIs(struct Message *msg) { require(msg, false, 3); - if (!replies.mode) return; - replies.mode--; - uint param = 3; - char buf[1024] = ""; - struct Cat cat = { buf, sizeof(buf), 0 }; + char buf[1024]; + char *ptr = buf, *end = &buf[sizeof(buf)]; for (char *ch = msg->params[2]; *ch; ++ch) { if (*ch == '+') continue; const char *name = ChanModes[(byte)*ch]; @@ -645,23 +798,23 @@ static void handleReplyChannelModeIs(struct Message *msg) { strchr(network.setParamModes, *ch) ) { assert(param < ParamCap); - catf( - &cat, ", +%c%s%s %s", + ptr = seprintf( + ptr, end, ", +%c%s%s %s", *ch, (name ? " " : ""), (name ?: ""), msg->params[param++] ); } else { - catf( - &cat, ", +%c%s%s", + ptr = seprintf( + ptr, end, ", +%c%s%s", *ch, (name ? " " : ""), (name ?: "") ); } } uiFormat( - idFor(msg->params[1]), Cold, tagTime(msg), + idFor(msg->params[1]), Warm, tagTime(msg), "\3%02d%s\3\tis %s", hash(msg->params[1]), msg->params[1], - (buf[0] ? &buf[2] : "modeless") + (ptr > buf ? &buf[2] : "modeless") ); } @@ -710,6 +863,12 @@ static void handleMode(struct Message *msg) { char prefix = network.prefixes[ strchr(network.prefixModes, *ch) - network.prefixModes ]; + completePush(id, nick, Default); + if (set) { + *completeBits(id, nick) |= prefixBit(prefix); + } else { + *completeBits(id, nick) &= ~prefixBit(prefix); + } uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\t%s \3%02d%c%s\3 %s%s in \3%02d%s\3", @@ -819,7 +978,7 @@ static void handleMode(struct Message *msg) { static void handleErrorChanopPrivsNeeded(struct Message *msg) { require(msg, false, 3); uiFormat( - idFor(msg->params[1]), Cold, tagTime(msg), + idFor(msg->params[1]), Warm, tagTime(msg), "%s", msg->params[2] ); } @@ -827,7 +986,7 @@ static void handleErrorChanopPrivsNeeded(struct Message *msg) { static void handleErrorUserNotInChannel(struct Message *msg) { require(msg, false, 4); uiFormat( - idFor(msg->params[2]), Cold, tagTime(msg), + idFor(msg->params[2]), Warm, tagTime(msg), "%s\tis not in \3%02d%s\3", msg->params[1], hash(msg->params[2]), msg->params[2] ); @@ -836,21 +995,20 @@ static void handleErrorUserNotInChannel(struct Message *msg) { static void handleErrorBanListFull(struct Message *msg) { require(msg, false, 4); uiFormat( - idFor(msg->params[1]), Cold, tagTime(msg), + idFor(msg->params[1]), Warm, tagTime(msg), "%s", (msg->params[4] ?: msg->params[3]) ); } static void handleReplyBanList(struct Message *msg) { require(msg, false, 3); - if (!replies.ban) return; uint id = idFor(msg->params[1]); if (msg->params[3] && msg->params[4]) { char since[sizeof("0000-00-00 00:00:00")]; time_t time = strtol(msg->params[4], NULL, 10); strftime(since, sizeof(since), "%F %T", localtime(&time)); uiFormat( - id, Cold, tagTime(msg), + id, Warm, tagTime(msg), "Banned from \3%02d%s\3 since %s by \3%02d%s\3: %s", hash(msg->params[1]), msg->params[1], since, completeColor(id, msg->params[3]), msg->params[3], @@ -858,26 +1016,22 @@ static void handleReplyBanList(struct Message *msg) { ); } else { uiFormat( - id, Cold, tagTime(msg), + id, Warm, tagTime(msg), "Banned from \3%02d%s\3: %s", hash(msg->params[1]), msg->params[1], msg->params[2] ); } } -static void handleReplyEndOfBanList(struct Message *msg) { - (void)msg; - if (replies.ban) replies.ban--; -} - static void onList(const char *list, struct Message *msg) { + require(msg, false, 3); uint id = idFor(msg->params[1]); if (msg->params[3] && msg->params[4]) { char since[sizeof("0000-00-00 00:00:00")]; time_t time = strtol(msg->params[4], NULL, 10); strftime(since, sizeof(since), "%F %T", localtime(&time)); uiFormat( - id, Cold, tagTime(msg), + id, Warm, tagTime(msg), "On the \3%02d%s\3 %s list since %s by \3%02d%s\3: %s", hash(msg->params[1]), msg->params[1], list, since, completeColor(id, msg->params[3]), msg->params[3], @@ -885,7 +1039,7 @@ static void onList(const char *list, struct Message *msg) { ); } else { uiFormat( - id, Cold, tagTime(msg), + id, Warm, tagTime(msg), "On the \3%02d%s\3 %s list: %s", hash(msg->params[1]), msg->params[1], list, msg->params[2] ); @@ -893,71 +1047,48 @@ static void onList(const char *list, struct Message *msg) { } static void handleReplyExceptList(struct Message *msg) { - require(msg, false, 3); - if (!replies.excepts) return; onList("except", msg); } -static void handleReplyEndOfExceptList(struct Message *msg) { - (void)msg; - if (replies.excepts) replies.excepts--; -} - static void handleReplyInviteList(struct Message *msg) { - require(msg, false, 3); - if (!replies.invex) return; onList("invite", msg); } -static void handleReplyEndOfInviteList(struct Message *msg) { - (void)msg; - if (replies.invex) replies.invex--; -} - static void handleReplyList(struct Message *msg) { - require(msg, false, 4); - if (!replies.list) return; + require(msg, false, 3); uiFormat( Network, Warm, tagTime(msg), "In \3%02d%s\3 are %ld under the banner: %s", hash(msg->params[1]), msg->params[1], strtol(msg->params[2], NULL, 10), - msg->params[3] + (msg->params[3] ?: "") ); } -static void handleReplyListEnd(struct Message *msg) { - (void)msg; - if (!replies.list) return; - replies.list--; -} - static void handleReplyWhoisUser(struct Message *msg) { require(msg, false, 6); - if (!replies.whois) return; - completeTouch(Network, msg->params[1], hash(msg->params[2])); + completePull(Network, msg->params[1], hash(msg->params[2])); uiFormat( Network, Warm, tagTime(msg), - "\3%02d%s\3\tis %s!%s@%s (%s)", + "\3%02d%s\3\tis %s!%s@%s (%s\17)", hash(msg->params[2]), msg->params[1], msg->params[1], msg->params[2], msg->params[3], msg->params[5] ); } static void handleReplyWhoisServer(struct Message *msg) { + if (!replies[ReplyWhois] && !replies[ReplyWhowas]) return; require(msg, false, 4); - if (!replies.whois) return; uiFormat( Network, Warm, tagTime(msg), - "\3%02d%s\3\tis connected to %s (%s)", + "\3%02d%s\3\t%s connected to %s (%s)", completeColor(Network, msg->params[1]), msg->params[1], - msg->params[2], msg->params[3] + (replies[ReplyWhowas] ? "was" : "is"), msg->params[2], msg->params[3] ); } static void handleReplyWhoisIdle(struct Message *msg) { require(msg, false, 3); - if (!replies.whois) return; unsigned long idle = strtoul(msg->params[2], NULL, 10); const char *unit = "second"; if (idle / 60) { @@ -983,13 +1114,16 @@ static void handleReplyWhoisIdle(struct Message *msg) { static void handleReplyWhoisChannels(struct Message *msg) { require(msg, false, 3); - if (!replies.whois) return; - char buf[1024] = ""; - struct Cat cat = { buf, sizeof(buf), 0 }; + char buf[1024]; + char *ptr = buf, *end = &buf[sizeof(buf)]; while (msg->params[2]) { char *channel = strsep(&msg->params[2], " "); + if (!channel[0]) break; char *name = &channel[strspn(channel, network.prefixes)]; - catf(&cat, "%s\3%02d%s\3", (buf[0] ? ", " : ""), hash(name), channel); + ptr = seprintf( + ptr, end, "%s\3%02d%s\3", + (ptr > buf ? ", " : ""), hash(name), channel + ); } uiFormat( Network, Warm, tagTime(msg), @@ -1000,7 +1134,6 @@ static void handleReplyWhoisChannels(struct Message *msg) { static void handleReplyWhoisGeneric(struct Message *msg) { require(msg, false, 3); - if (!replies.whois) return; if (msg->params[3]) { msg->params[0] = msg->params[2]; msg->params[2] = msg->params[3]; @@ -1016,24 +1149,35 @@ static void handleReplyWhoisGeneric(struct Message *msg) { static void handleReplyEndOfWhois(struct Message *msg) { require(msg, false, 2); - if (!replies.whois) return; if (strcmp(msg->params[1], self.nick)) { completeRemove(Network, msg->params[1]); } - replies.whois--; +} + +static void handleReplyWhowasUser(struct Message *msg) { + require(msg, false, 6); + completePull(Network, msg->params[1], hash(msg->params[2])); + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\twas %s!%s@%s (%s)", + hash(msg->params[2]), msg->params[1], + msg->params[1], msg->params[2], msg->params[3], msg->params[5] + ); +} + +static void handleReplyEndOfWhowas(struct Message *msg) { + require(msg, false, 2); + if (strcmp(msg->params[1], self.nick)) { + completeRemove(Network, msg->params[1]); + } } static void handleReplyAway(struct Message *msg) { require(msg, false, 3); // Might be part of a WHOIS response. - uint id; - if (completeColor(Network, msg->params[1]) != Default) { - id = Network; - } else { - id = idFor(msg->params[1]); - } + uint id = (replies[ReplyWhois] ? Network : idFor(msg->params[1])); uiFormat( - id, Warm, tagTime(msg), + id, (id == Network ? Warm : Cold), tagTime(msg), "\3%02d%s\3\tis away: %s", completeColor(id, msg->params[1]), msg->params[1], msg->params[2] ); @@ -1045,24 +1189,30 @@ static void handleReplyAway(struct Message *msg) { static void handleReplyNowAway(struct Message *msg) { require(msg, false, 2); - if (!replies.away) return; uiFormat(Network, Warm, tagTime(msg), "%s", msg->params[1]); - replies.away--; } static bool isAction(struct Message *msg) { - if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; - msg->params[1] += 8; + if (strncmp(msg->params[1], "\1ACTION", 7)) return false; + if (msg->params[1][7] == ' ') { + msg->params[1] += 8; + } else if (msg->params[1][7] == '\1') { + msg->params[1] += 7; + } else { + return false; + } size_t len = strlen(msg->params[1]); - if (msg->params[1][len - 1] == '\1') msg->params[1][len - 1] = '\0'; + if (msg->params[1][len - 1] == '\1') { + msg->params[1][len - 1] = '\0'; + } return true; } -static bool isMention(const struct Message *msg) { - size_t len = strlen(self.nick); - const char *match = msg->params[1]; - while (NULL != (match = strcasestr(match, self.nick))) { - char a = (match > msg->params[1] ? match[-1] : ' '); +static bool matchWord(const char *str, const char *word) { + size_t len = strlen(word); + const char *match = str; + while (NULL != (match = strstr(match, word))) { + char a = (match > str ? match[-1] : ' '); char b = (match[len] ?: ' '); if ((isspace(a) || ispunct(a)) && (isspace(b) || ispunct(b))) { return true; @@ -1072,47 +1222,54 @@ static bool isMention(const struct Message *msg) { return false; } -static const char *colorMentions(uint id, struct Message *msg) { - char *split = strstr(msg->params[1], ": "); +static bool isMention(const struct Message *msg) { + if (matchWord(msg->params[1], self.nick)) return true; + for (uint i = 0; i < ARRAY_LEN(self.nicks) && self.nicks[i]; ++i) { + if (matchWord(msg->params[1], self.nicks[i])) return true; + } + return false; +} + +static char *colorMentions(char *ptr, char *end, uint id, const char *msg) { + // Consider words before a colon, or only the first two. + const char *split = strstr(msg, ": "); if (!split) { - split = strchr(msg->params[1], ' '); + split = strchr(msg, ' '); if (split) split = strchr(&split[1], ' '); } - if (!split) split = &msg->params[1][strlen(msg->params[1])]; - for (char *ch = msg->params[1]; ch < split; ++ch) { - if (iscntrl(*ch)) return ""; + if (!split) split = &msg[strlen(msg)]; + // Bail if there is existing formatting. + for (const char *ch = msg; ch < split; ++ch) { + if (iscntrl(*ch)) goto rest; } - char delimit = *split; - char *mention = msg->params[1]; - msg->params[1] = (delimit ? &split[1] : split); - *split = '\0'; - - static char buf[1024]; - buf[0] = '\0'; - struct Cat cat = { buf, sizeof(buf), 0 }; - while (*mention) { - size_t skip = strspn(mention, ",<> "); - catf(&cat, "%.*s", (int)skip, mention); - mention += skip; - - size_t len = strcspn(mention, ",<> "); - char punct = mention[len]; - mention[len] = '\0'; - enum Color color = completeColor(id, mention); + + while (msg < split) { + size_t skip = strspn(msg, ",:<> "); + ptr = seprintf(ptr, end, "%.*s", (int)skip, msg); + msg += skip; + + size_t len = strcspn(msg, ",:<> "); + char *p = seprintf(ptr, end, "%.*s", (int)len, msg); + enum Color color = completeColor(id, ptr); if (color != Default) { - catf(&cat, "\3%02d%s\3", color, mention); + ptr = seprintf(ptr, end, "\3%02d%.*s\3", color, (int)len, msg); } else { - catf(&cat, "%s", mention); + ptr = p; } - mention[len] = punct; - mention += len; + msg += len; } - catf(&cat, "%c", delimit); - return buf; + +rest: + return seprintf(ptr, end, "%s", msg); } static void handlePrivmsg(struct Message *msg) { require(msg, true, 2); + char statusmsg = '\0'; + if (network.statusmsg && strchr(network.statusmsg, msg->params[0][0])) { + statusmsg = msg->params[0][0]; + msg->params[0]++; + } bool query = !strchr(network.chanTypes, msg->params[0][0]); bool server = strchr(msg->nick, '.'); bool mine = !strcmp(msg->nick, self.nick); @@ -1127,39 +1284,50 @@ static void handlePrivmsg(struct Message *msg) { } bool notice = (msg->cmd[0] == 'N'); - bool action = isAction(msg); - bool mention = !mine && isMention(msg); - if (!notice && !mine) completeTouch(id, msg->nick, hash(msg->user)); - enum Heat heat = ignoreCheck((mention || query ? Hot : Warm), id, msg); + bool action = !notice && isAction(msg); + bool highlight = !mine && isMention(msg); + enum Heat heat = (!notice && (highlight || query) ? Hot : Warm); + heat = filterCheck(heat, id, msg); + if (heat > Warm && !mine && !query) highlight = true; + if (!notice && !mine && heat > Ice) { + completePull(id, msg->nick, hash(msg->user)); + } if (heat > Ice) urlScan(id, msg->nick, msg->params[1]); + + char buf[1024]; + char *ptr = buf, *end = &buf[sizeof(buf)]; + if (statusmsg) { + ptr = seprintf( + ptr, end, "\3%d[%c]\3 ", hash(msg->params[0]), statusmsg + ); + } if (notice) { if (id != Network) { logFormat(id, tagTime(msg), "-%s- %s", msg->nick, msg->params[1]); } - uiFormat( - id, ignoreCheck(Warm, id, msg), tagTime(msg), - "\3%d-%s-\3%d\t%s", - hash(msg->user), msg->nick, LightGray, msg->params[1] + ptr = seprintf( + ptr, end, "\3%d-%s-\3%d\t", + hash(msg->user), msg->nick, LightGray ); } else if (action) { logFormat(id, tagTime(msg), "* %s %s", msg->nick, msg->params[1]); - const char *mentions = colorMentions(id, msg); - uiFormat( - id, heat, tagTime(msg), - "%s\35\3%d* %s\17\35\t%s%s", - (mention ? "\26" : ""), hash(msg->user), msg->nick, - mentions, msg->params[1] + ptr = seprintf( + ptr, end, "%s\35\3%d* %s\17\35\t", + (highlight ? "\26" : ""), hash(msg->user), msg->nick ); } else { logFormat(id, tagTime(msg), "<%s> %s", msg->nick, msg->params[1]); - const char *mentions = colorMentions(id, msg); - uiFormat( - id, heat, tagTime(msg), - "%s\3%d<%s>\17\t%s%s", - (mention ? "\26" : ""), hash(msg->user), msg->nick, - mentions, msg->params[1] + ptr = seprintf( + ptr, end, "%s\3%d<%s>\17\t", + (highlight ? "\26" : ""), hash(msg->user), msg->nick ); } + if (notice) { + ptr = seprintf(ptr, end, "%s", msg->params[1]); + } else { + ptr = colorMentions(ptr, end, id, msg->params[1]); + } + uiWrite(id, heat, tagTime(msg), buf); } static void handlePing(struct Message *msg) { @@ -1174,67 +1342,81 @@ static void handleError(struct Message *msg) { static const struct Handler { const char *cmd; + int reply; Handler *fn; } Handlers[] = { - { "001", handleReplyWelcome }, - { "005", handleReplyISupport }, - { "221", handleReplyUserModeIs }, - { "276", handleReplyWhoisGeneric }, - { "301", handleReplyAway }, - { "305", handleReplyNowAway }, - { "306", handleReplyNowAway }, - { "307", handleReplyWhoisGeneric }, - { "311", handleReplyWhoisUser }, - { "312", handleReplyWhoisServer }, - { "313", handleReplyWhoisGeneric }, - { "317", handleReplyWhoisIdle }, - { "318", handleReplyEndOfWhois }, - { "319", handleReplyWhoisChannels }, - { "322", handleReplyList }, - { "323", handleReplyListEnd }, - { "324", handleReplyChannelModeIs }, - { "330", handleReplyWhoisGeneric }, - { "331", handleReplyNoTopic }, - { "332", handleReplyTopic }, - { "341", handleReplyInviting }, - { "346", handleReplyInviteList }, - { "347", handleReplyEndOfInviteList }, - { "348", handleReplyExceptList }, - { "349", handleReplyEndOfExceptList }, - { "353", handleReplyNames }, - { "366", handleReplyEndOfNames }, - { "367", handleReplyBanList }, - { "368", handleReplyEndOfBanList }, - { "372", handleReplyMOTD }, - { "378", handleReplyWhoisGeneric }, - { "379", handleReplyWhoisGeneric }, - { "422", handleErrorNoMOTD }, - { "432", handleErrorErroneousNickname }, - { "433", handleErrorNicknameInUse }, - { "441", handleErrorUserNotInChannel }, - { "443", handleErrorUserOnChannel }, - { "478", handleErrorBanListFull }, - { "482", handleErrorChanopPrivsNeeded }, - { "671", handleReplyWhoisGeneric }, - { "900", handleReplyLoggedIn }, - { "904", handleErrorSASLFail }, - { "905", handleErrorSASLFail }, - { "906", handleErrorSASLFail }, - { "AUTHENTICATE", handleAuthenticate }, - { "CAP", handleCap }, - { "CHGHOST", handleChghost }, - { "ERROR", handleError }, - { "INVITE", handleInvite }, - { "JOIN", handleJoin }, - { "KICK", handleKick }, - { "MODE", handleMode }, - { "NICK", handleNick }, - { "NOTICE", handlePrivmsg }, - { "PART", handlePart }, - { "PING", handlePing }, - { "PRIVMSG", handlePrivmsg }, - { "QUIT", handleQuit }, - { "TOPIC", handleTopic }, + { "001", 0, handleReplyWelcome }, + { "005", 0, handleReplyISupport }, + { "221", -ReplyMode, handleReplyUserModeIs }, + { "276", +ReplyWhois, handleReplyWhoisGeneric }, + { "301", 0, handleReplyAway }, + { "305", -ReplyAway, handleReplyNowAway }, + { "306", -ReplyAway, handleReplyNowAway }, + { "307", +ReplyWhois, handleReplyWhoisGeneric }, + { "311", +ReplyWhois, handleReplyWhoisUser }, + { "312", 0, handleReplyWhoisServer }, + { "313", +ReplyWhois, handleReplyWhoisGeneric }, + { "314", +ReplyWhowas, handleReplyWhowasUser }, + { "317", +ReplyWhois, handleReplyWhoisIdle }, + { "318", -ReplyWhois, handleReplyEndOfWhois }, + { "319", +ReplyWhois, handleReplyWhoisChannels }, + { "320", +ReplyWhois, handleReplyWhoisGeneric }, + { "322", +ReplyList, handleReplyList }, + { "323", -ReplyList, NULL }, + { "324", -ReplyMode, handleReplyChannelModeIs }, + { "330", +ReplyWhois, handleReplyWhoisGeneric }, + { "331", -ReplyTopic, handleReplyNoTopic }, + { "332", 0, handleReplyTopic }, + { "335", +ReplyWhois, handleReplyWhoisGeneric }, + { "338", +ReplyWhois, handleReplyWhoisGeneric }, + { "341", 0, handleReplyInviting }, + { "346", +ReplyInvex, handleReplyInviteList }, + { "347", -ReplyInvex, NULL }, + { "348", +ReplyExcepts, handleReplyExceptList }, + { "349", -ReplyExcepts, NULL }, + { "353", 0, handleReplyNames }, + { "366", 0, handleReplyEndOfNames }, + { "367", +ReplyBan, handleReplyBanList }, + { "368", -ReplyBan, NULL }, + { "369", -ReplyWhowas, handleReplyEndOfWhowas }, + { "372", 0, handleReplyMOTD }, + { "378", +ReplyWhois, handleReplyWhoisGeneric }, + { "379", +ReplyWhois, handleReplyWhoisGeneric }, + { "422", 0, handleErrorNoMOTD }, + { "432", 0, handleErrorErroneousNickname }, + { "433", 0, handleErrorNicknameInUse }, + { "437", 0, handleErrorNicknameInUse }, + { "441", 0, handleErrorUserNotInChannel }, + { "443", 0, handleErrorUserOnChannel }, + { "478", 0, handleErrorBanListFull }, + { "482", 0, handleErrorChanopPrivsNeeded }, + { "671", +ReplyWhois, handleReplyWhoisGeneric }, + { "704", +ReplyHelp, handleReplyHelp }, + { "705", +ReplyHelp, handleReplyHelp }, + { "706", -ReplyHelp, NULL }, + { "900", 0, handleReplyLoggedIn }, + { "904", 0, handleErrorSASLFail }, + { "905", 0, handleErrorSASLFail }, + { "906", 0, handleErrorSASLFail }, + { "AUTHENTICATE", 0, handleAuthenticate }, + { "CAP", 0, handleCap }, + { "CHGHOST", 0, handleChghost }, + { "ERROR", 0, handleError }, + { "FAIL", 0, handleStandardReply }, + { "INVITE", 0, handleInvite }, + { "JOIN", 0, handleJoin }, + { "KICK", 0, handleKick }, + { "MODE", 0, handleMode }, + { "NICK", 0, handleNick }, + { "NOTE", 0, handleStandardReply }, + { "NOTICE", 0, handlePrivmsg }, + { "PART", 0, handlePart }, + { "PING", 0, handlePing }, + { "PRIVMSG", 0, handlePrivmsg }, + { "QUIT", 0, handleQuit }, + { "SETNAME", 0, handleSetname }, + { "TOPIC", 0, handleTopic }, + { "WARN", 0, handleStandardReply }, }; static int compar(const void *cmd, const void *_handler) { @@ -1251,8 +1433,12 @@ void handle(struct Message *msg) { msg->cmd, Handlers, ARRAY_LEN(Handlers), sizeof(*handler), compar ); if (handler) { - handler->fn(msg); + if (handler->reply && !replies[abs(handler->reply)]) return; + if (handler->fn) handler->fn(msg); + if (handler->reply < 0) replies[abs(handler->reply)]--; } else if (strcmp(msg->cmd, "400") >= 0 && strcmp(msg->cmd, "599") <= 0) { handleErrorGeneric(msg); + } else if (isdigit(msg->cmd[0])) { + handleReplyGeneric(msg); } } |