From 843160236381d0c76bef1eac89e556920d700a9d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 01:18:01 -0500 Subject: Blindly implement login flow --- handle.c | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 handle.c (limited to 'handle.c') diff --git a/handle.c b/handle.c new file mode 100644 index 0000000..4084525 --- /dev/null +++ b/handle.c @@ -0,0 +1,163 @@ +/* Copyright (C) 2020 C. McEnroe + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +static const char *CapNames[] = { +#define X(name, id) [id] = name, + ENUM_CAP +#undef X +}; + +static enum Cap capParse(const char *list) { + enum Cap caps = 0; + while (*list) { + enum Cap cap = 0; + size_t len = strcspn(list, " "); + for (size_t i = 0; i < ARRAY_LEN(CapNames); ++i) { + if (len != strlen(CapNames[i])) continue; + if (strncmp(list, CapNames[i], len)) continue; + cap = 1 << i; + break; + } + caps |= cap; + list += len; + if (*list) list++; + } + return caps; +} + +static const char *capList(enum Cap caps) { + static char buf[1024]; + buf[0] = '\0'; + for (size_t i = 0; i < ARRAY_LEN(CapNames); ++i) { + if (caps & (1 << i)) { + if (buf[0]) strlcat(buf, " ", sizeof(buf)); + strlcat(buf, CapNames[i], sizeof(buf)); + } + } + return buf; +} + +static void set(char **field, const char *value) { + free(*field); + *field = strdup(value); + if (!*field) err(EX_OSERR, "strdup"); +} + +typedef void Handler(struct Message *msg); + +static void require(const struct Message *msg, bool origin, size_t len) { + if (origin && !msg->nick) { + errx(EX_PROTOCOL, "%s missing origin", msg->cmd); + } + for (size_t i = 0; i < len; ++i) { + if (msg->params[i]) continue; + errx(EX_PROTOCOL, "%s missing parameter %zu", msg->cmd, 1 + i); + } +} + +static void handleCap(struct Message *msg) { + require(msg, false, 3); + enum Cap caps = capParse(msg->params[2]); + if (!strcmp(msg->params[1], "LS")) { + caps &= ~CapSASL; + ircFormat("CAP REQ :%s\r\n", capList(caps)); + } else if (!strcmp(msg->params[1], "ACK")) { + self.caps |= caps; + if (caps & CapSASL) { + ircFormat("AUTHENTICATE %s\r\n", (self.plain ? "PLAIN" : "EXTERNAL")); + } + if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); + } else if (!strcmp(msg->params[1], "NAK")) { + errx(EX_CONFIG, "server does not support %s", msg->params[2]); + } +} + +static void handleAuthenticate(struct Message *msg) { + (void)msg; + if (!self.plain) { + 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]); + } + + char b64[BASE64_SIZE(sizeof(buf))]; + base64(b64, buf, len); + ircFormat("AUTHENTICATE "); + ircSend(b64, BASE64_SIZE(len)); + ircFormat("\r\n"); + + explicit_bzero(b64, sizeof(b64)); + explicit_bzero(buf, sizeof(buf)); + explicit_bzero(self.plain, strlen(self.plain)); +} + +static void handleReplyLoggedIn(struct Message *msg) { + (void)msg; + ircFormat("CAP END\r\n"); +} + +static void handleErrorSASLFail(struct Message *msg) { + require(msg, false, 2); + errx(EX_CONFIG, "%s", msg->params[1]); +} + +static void handleReplyWelcome(struct Message *msg) { + require(msg, false, 1); + set(&self.nick, msg->params[0]); + if (self.join) ircFormat("JOIN :%s\r\n", self.join); +} + +static const struct Handler { + const char *cmd; + Handler *fn; +} Handlers[] = { + { "001", handleReplyWelcome }, + { "900", handleReplyLoggedIn }, + { "904", handleErrorSASLFail }, + { "905", handleErrorSASLFail }, + { "906", handleErrorSASLFail }, + { "AUTHENTICATE", handleAuthenticate }, + { "CAP", handleCap }, +}; + +static int compar(const void *cmd, const void *_handler) { + const struct Handler *handler = _handler; + return strcmp(cmd, handler->cmd); +} + +void handle(struct Message msg) { + if (!msg.cmd) return; + const struct Handler *handler = bsearch( + msg.cmd, Handlers, ARRAY_LEN(Handlers), sizeof(*handler), compar + ); + if (handler) handler->fn(&msg); +} -- cgit 1.4.1 From 856d40d1212ec835b092a8f275124d09a65ba59d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 02:19:55 -0500 Subject: Fix CapNames array indices --- handle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 4084525..96bd2a2 100644 --- a/handle.c +++ b/handle.c @@ -24,7 +24,7 @@ #include "chat.h" static const char *CapNames[] = { -#define X(name, id) [id] = name, +#define X(name, id) [id##Bit] = name, ENUM_CAP #undef X }; -- cgit 1.4.1 From e5363bcae0f726455fb4198cd21d46721ad5e39a Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 1 Feb 2020 19:37:48 -0500 Subject: Implement the beginnings of UI It takes so much code to do anything in curses... --- Makefile | 3 +- chat.c | 8 +++ chat.h | 22 +++++++- handle.c | 21 ++++++++ irc.c | 6 +-- ui.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 ui.c (limited to 'handle.c') diff --git a/Makefile b/Makefile index 999b491..4af59ee 100644 --- a/Makefile +++ b/Makefile @@ -3,12 +3,13 @@ CFLAGS += -I${LIBRESSL_PREFIX}/include LDFLAGS += -L${LIBRESSL_PREFIX}/lib CFLAGS += -std=c11 -Wall -Wextra -Wpedantic -LDLIBS = -lcrypto -ltls +LDLIBS = -lcurses -lcrypto -ltls OBJS += chat.o OBJS += handle.o OBJS += irc.o OBJS += term.o +OBJS += ui.o catgirl: ${OBJS} ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ diff --git a/chat.c b/chat.c index 462faa0..47227d5 100644 --- a/chat.c +++ b/chat.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -33,6 +34,8 @@ size_t idNext = Network + 1; struct Self self; int main(int argc, char *argv[]) { + setlocale(LC_CTYPE, ""); + bool insecure = false; const char *host = NULL; const char *port = "6697"; @@ -71,6 +74,10 @@ int main(int argc, char *argv[]) { if (!real) real = nick; ircConfig(insecure, cert, priv); + + uiInit(); + uiFormat(Network, Cold, NULL, "Traveling..."); + uiDraw(); int irc = ircConnect(host, port); if (pass) ircFormat("PASS :%s\r\n", pass); @@ -80,6 +87,7 @@ int main(int argc, char *argv[]) { ircFormat("USER %s 0 * :%s\r\n", user, real); for (;;) { + uiDraw(); ircRecv(); } } diff --git a/chat.h b/chat.h index 93014ef..be3952c 100644 --- a/chat.h +++ b/chat.h @@ -82,6 +82,18 @@ struct Message { char *params[ParamCap]; }; +#define B "\2" +#define C "\3" +#define R "\17" +#define V "\26" +#define I "\35" +#define U "\37" +enum Color { + White, Black, Blue, Green, Red, Brown, Magenta, Orange, + Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, + Default = 99, +}; + void ircConfig(bool insecure, const char *cert, const char *priv); int ircConnect(const char *host, const char *port); void ircRecv(void); @@ -91,6 +103,14 @@ void ircFormat(const char *format, ...) void handle(struct Message msg); +enum Heat { Cold, Warm, Hot }; +void uiInit(void); +void uiDraw(void); +void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str); +void uiFormat( + size_t id, enum Heat heat, const struct tm *time, const char *format, ... +) __attribute__((format(printf, 4, 5))); + enum TermMode { TermFocus, TermPaste, @@ -109,11 +129,9 @@ void termMode(enum TermMode mode, bool set); enum TermEvent termEvent(char ch); #define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4) - static const char Base64[64] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" }; - static inline void base64(char *dst, const byte *src, size_t len) { size_t i = 0; while (len > 2) { diff --git a/handle.c b/handle.c index 96bd2a2..b2b2b8d 100644 --- a/handle.c +++ b/handle.c @@ -136,11 +136,32 @@ static void handleReplyWelcome(struct Message *msg) { if (self.join) ircFormat("JOIN :%s\r\n", self.join); } +static void handleReplyISupport(struct Message *msg) { + // TODO: Extract CHANTYPES and PREFIX for future use. + for (size_t i = 1; i < ParamCap; ++i) { + if (!msg->params[i]) break; + char *key = strsep(&msg->params[i], "="); + if (!msg->params[i]) continue; + if (!strcmp(key, "NETWORK")) { + uiFormat(Network, Cold, NULL, "You arrive in %s", msg->params[i]); + } + } +} + +static void handleReplyMOTD(struct Message *msg) { + require(msg, false, 2); + char *line = msg->params[1]; + if (!strncmp(line, "- ", 2)) line += 2; + uiFormat(Network, Cold, NULL, "%s", line); +} + static const struct Handler { const char *cmd; Handler *fn; } Handlers[] = { { "001", handleReplyWelcome }, + { "005", handleReplyISupport }, + { "372", handleReplyMOTD }, { "900", handleReplyLoggedIn }, { "904", handleErrorSASLFail }, { "905", handleErrorSASLFail }, diff --git a/irc.c b/irc.c index cf8aab7..f07fcc8 100644 --- a/irc.c +++ b/irc.c @@ -104,10 +104,10 @@ int ircConnect(const char *host, const char *port) { static void debug(char dir, const char *line) { if (!self.debug) return; size_t len = strcspn(line, "\r\n"); - /*uiFormat( - Debug, Cold, NULL, "\3%02d%c%c\3 %.*s", + uiFormat( + Debug, Cold, NULL, C"%d%c%c"C" %.*s", Gray, dir, dir, (int)len, line - );*/ + ); if (!isatty(STDERR_FILENO)) { fprintf(stderr, "%c%c %.*s\n", dir, dir, (int)len, line); } diff --git a/ui.c b/ui.c new file mode 100644 index 0000000..0295c8d --- /dev/null +++ b/ui.c @@ -0,0 +1,174 @@ +/* Copyright (C) 2020 C. McEnroe + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +#ifndef A_ITALIC +#define A_ITALIC A_UNDERLINE +#endif + +#define BOTTOM (LINES - 1) +#define RIGHT (COLS - 1) +#define WINDOW_LINES (LINES - 2) + +static short colorPairs; + +static void colorInit(void) { + start_color(); + use_default_colors(); + for (short pair = 0; pair < 16; ++pair) { + init_pair(1 + pair, pair % COLORS, -1); + } + colorPairs = 17; +} + +static attr_t colorAttr(short fg) { + return (fg >= COLORS ? A_BOLD : A_NORMAL); +} + +static short colorPair(short fg, short bg) { + if (bg == -1) return 1 + fg; + for (short pair = 17; pair < colorPairs; ++pair) { + short f, b; + pair_content(pair, &f, &b); + if (f == fg && b == bg) return pair; + } + init_pair(colorPairs, fg, bg); + return colorPairs++; +} + +enum { + InputCols = 512, + PadLines = 512, +}; + +static WINDOW *status; +static WINDOW *input; + +struct Window { + size_t id; + WINDOW *pad; + enum Heat heat; + int unread; + int scroll; + bool mark; + struct Window *prev; + struct Window *next; +}; + +static struct { + struct Window *active; + struct Window *other; + struct Window *head; + struct Window *tail; +} windows; + +static void windowAdd(struct Window *window) { + if (windows.tail) windows.tail->next = window; + window->prev = windows.tail; + window->next = NULL; + windows.tail = window; + if (!windows.head) windows.head = window; +} + +static void windowRemove(struct Window *window) { + if (window->prev) window->prev->next = window->next; + if (window->next) window->next->prev = window->prev; + if (windows.head == window) windows.head = window->next; + if (windows.tail == window) windows.tail = window->prev; +} + +static struct Window *windowFor(size_t id) { + struct Window *window; + for (window = windows.head; window; window = window->next) { + if (window->id == id) return window; + } + window = malloc(sizeof(*window)); + if (!window) err(EX_OSERR, "malloc"); + window->id = id; + window->pad = newpad(PadLines, COLS); + wsetscrreg(window->pad, 0, PadLines - 1); + scrollok(window->pad, true); + wmove(window->pad, PadLines - 1, 0); + window->heat = Cold; + window->unread = 0; + window->scroll = PadLines; + window->mark = true; + windowAdd(window); + return window; +} + +void uiInit(void) { + initscr(); + cbreak(); + noecho(); + termInit(); + termNoFlow(); + def_prog_mode(); + colorInit(); + status = newwin(1, COLS, 0, 0); + input = newpad(1, InputCols); + keypad(input, true); + nodelay(input, true); + windows.active = windowFor(Network); +} + +void uiDraw(void) { + wnoutrefresh(status); + pnoutrefresh( + windows.active->pad, + windows.active->scroll - WINDOW_LINES, 0, + 1, 0, + BOTTOM - 1, RIGHT + ); + // TODO: Input scrolling. + pnoutrefresh( + input, + 0, 0, + BOTTOM, 0, + BOTTOM, RIGHT + ); + doupdate(); +} + +void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { + (void)time; + struct Window *window = windowFor(id); + waddch(window->pad, '\n'); + waddstr(window->pad, str); +} + +void uiFormat( + size_t id, enum Heat heat, const struct tm *time, const char *format, ... +) { + char buf[1024]; + va_list ap; + va_start(ap, format); + int len = vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + assert((size_t)len < sizeof(buf)); + uiWrite(id, heat, time, buf); +} -- cgit 1.4.1 From 8ef0af34ef1c62c52cfdbe3f440d6017b0feda6f Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 01:58:03 -0500 Subject: Parse time tag --- handle.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index b2b2b8d..9157419 100644 --- a/handle.c +++ b/handle.c @@ -65,8 +65,6 @@ static void set(char **field, const char *value) { if (!*field) err(EX_OSERR, "strdup"); } -typedef void Handler(struct Message *msg); - static void require(const struct Message *msg, bool origin, size_t len) { if (origin && !msg->nick) { errx(EX_PROTOCOL, "%s missing origin", msg->cmd); @@ -77,6 +75,16 @@ static void require(const struct Message *msg, bool origin, size_t len) { } } +static const struct tm *tagTime(const struct Message *msg) { + if (!msg->tags[TagTime]) return NULL; + static struct tm time; + char *rest = strptime(msg->tags[TagTime], "%FT%T", &time); + time.tm_gmtoff = 0; + return (rest ? &time : NULL); +} + +typedef void Handler(struct Message *msg); + static void handleCap(struct Message *msg) { require(msg, false, 3); enum Cap caps = capParse(msg->params[2]); @@ -143,7 +151,10 @@ static void handleReplyISupport(struct Message *msg) { char *key = strsep(&msg->params[i], "="); if (!msg->params[i]) continue; if (!strcmp(key, "NETWORK")) { - uiFormat(Network, Cold, NULL, "You arrive in %s", msg->params[i]); + uiFormat( + Network, Cold, tagTime(msg), + "You arrive in %s", msg->params[i] + ); } } } @@ -152,7 +163,12 @@ static void handleReplyMOTD(struct Message *msg) { require(msg, false, 2); char *line = msg->params[1]; if (!strncmp(line, "- ", 2)) line += 2; - uiFormat(Network, Cold, NULL, "%s", line); + uiFormat(Network, Cold, tagTime(msg), "%s", line); +} + +static void handlePing(struct Message *msg) { + require(msg, false, 1); + ircFormat("PONG :%s\r\n", msg->params[0]); } static const struct Handler { @@ -168,6 +184,7 @@ static const struct Handler { { "906", handleErrorSASLFail }, { "AUTHENTICATE", handleAuthenticate }, { "CAP", handleCap }, + { "PING", handlePing }, }; static int compar(const void *cmd, const void *_handler) { -- cgit 1.4.1 From 14066b79d424561b0ab4be74574edf6fae422378 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 02:30:35 -0500 Subject: Handle nickname errors --- handle.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 9157419..350a636 100644 --- a/handle.c +++ b/handle.c @@ -85,6 +85,17 @@ static const struct tm *tagTime(const struct Message *msg) { typedef void Handler(struct Message *msg); +static void handleErrorNicknameInUse(struct Message *msg) { + if (self.nick) return; + require(msg, false, 2); + ircFormat("NICK :%s_\r\n", msg->params[1]); +} + +static void handleErrorErroneousNickname(struct Message *msg) { + require(msg, false, 3); + errx(EX_CONFIG, "%s: %s", msg->params[1], msg->params[2]); +} + static void handleCap(struct Message *msg) { require(msg, false, 3); enum Cap caps = capParse(msg->params[2]); @@ -178,6 +189,8 @@ static const struct Handler { { "001", handleReplyWelcome }, { "005", handleReplyISupport }, { "372", handleReplyMOTD }, + { "432", handleErrorErroneousNickname }, + { "433", handleErrorNicknameInUse }, { "900", handleReplyLoggedIn }, { "904", handleErrorSASLFail }, { "905", handleErrorSASLFail }, -- cgit 1.4.1 From ec83332e15d31c1ffbb7112ff6743f2a5c815c71 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:13:50 -0500 Subject: Implement window switching and status line --- chat.c | 15 ++------------- chat.h | 1 + handle.c | 22 +++++++++++++++++++--- ui.c | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 16 deletions(-) (limited to 'handle.c') diff --git a/chat.c b/chat.c index ceaf1b5..d4ed31c 100644 --- a/chat.c +++ b/chat.c @@ -76,19 +76,8 @@ int main(int argc, char *argv[]) { ircConfig(insecure, cert, priv); uiInit(); - uiFormat(Network, Cold, NULL, C "3Trave" U "ling" U C "0,3.." C "0,4." R); - uiFormat( - Network, Cold, NULL, - "Jackdaws love my big sphinx of quartz. " - "The quick brown fox jumps over the lazy dog. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - "The quick brown fox jumps over the lazy dog. " - "The quick brown fox jumps over the lazy dog. " - "Jackdaws love my big sphinx of quartz. " - "Jackdaws love my big sphinx of quartz. " - ); + uiShowID(Network); + uiFormat(Network, Cold, NULL, "Traveling..."); uiDraw(); int irc = ircConnect(host, port); diff --git a/chat.h b/chat.h index fc5043d..9060f29 100644 --- a/chat.h +++ b/chat.h @@ -117,6 +117,7 @@ void handle(struct Message msg); enum Heat { Cold, Warm, Hot }; void uiInit(void); void uiDraw(void); +void uiShowID(size_t id); void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str); void uiFormat( size_t id, enum Heat heat, const struct tm *time, const char *format, ... diff --git a/handle.c b/handle.c index 350a636..609867e 100644 --- a/handle.c +++ b/handle.c @@ -65,9 +65,11 @@ static void set(char **field, const char *value) { if (!*field) err(EX_OSERR, "strdup"); } -static void require(const struct Message *msg, bool origin, size_t len) { - if (origin && !msg->nick) { - errx(EX_PROTOCOL, "%s missing origin", msg->cmd); +static void require(struct Message *msg, bool origin, size_t len) { + if (origin) { + if (!msg->nick) errx(EX_PROTOCOL, "%s missing origin", msg->cmd); + if (!msg->user) msg->user = msg->nick; + if (!msg->host) msg->host = msg->user; } for (size_t i = 0; i < len; ++i) { if (msg->params[i]) continue; @@ -177,6 +179,19 @@ static void handleReplyMOTD(struct Message *msg) { uiFormat(Network, Cold, tagTime(msg), "%s", line); } +static void handleJoin(struct Message *msg) { + require(msg, true, 1); + size_t id = idFor(msg->params[0]); + if (self.nick && !strcmp(msg->nick, self.nick)) { + uiShowID(id); + } + uiFormat( + id, Cold, tagTime(msg), + C"%02d%s"C" arrives in "C"%02d%s"C, + hash(msg->user), msg->nick, hash(idNames[id]), idNames[id] + ); +} + static void handlePing(struct Message *msg) { require(msg, false, 1); ircFormat("PONG :%s\r\n", msg->params[0]); @@ -197,6 +212,7 @@ static const struct Handler { { "906", handleErrorSASLFail }, { "AUTHENTICATE", handleAuthenticate }, { "CAP", handleCap }, + { "JOIN", handleJoin }, { "PING", handlePing }, }; diff --git a/ui.c b/ui.c index 90ba726..3ae6592 100644 --- a/ui.c +++ b/ui.c @@ -262,6 +262,40 @@ static void styleAdd(WINDOW *win, const char *str) { } } +static void statusUpdate(void) { + wmove(status, 0, 0); + int num; + const struct Window *window; + for (num = 0, window = windows.head; window; ++num, window = window->next) { + if (!window->unread && window != windows.active) continue; + enum Color color = hash(idNames[window->id]); // FIXME: queries. + int unread; + char buf[256]; + snprintf( + buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", + color, (window == windows.active ? V : ""), + num, idNames[window->id], + &unread, (window->heat > Warm ? White : color), window->unread, + color + ); + if (!window->unread) buf[unread] = '\0'; + styleAdd(status, buf); + } + wclrtoeol(status); +} + +void uiShowID(size_t id) { + struct Window *window = windowFor(id); + window->heat = Cold; + window->unread = 0; + window->mark = false; + if (windows.active) windows.active->mark = true; + windows.other = windows.active; + windows.active = window; + touchwin(window->pad); + statusUpdate(); +} + void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { (void)time; struct Window *window = windowFor(id); -- cgit 1.4.1 From 8bb9ea7b7ff2e98bbe629f9f2e63f1dcb70250e3 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:27:50 -0500 Subject: Add idColors --- chat.c | 7 +++++++ chat.h | 47 +++++++++++++++++++++++++---------------------- handle.c | 3 ++- ui.c | 8 ++++---- 4 files changed, 38 insertions(+), 27 deletions(-) (limited to 'handle.c') diff --git a/chat.c b/chat.c index d4ed31c..b61dd34 100644 --- a/chat.c +++ b/chat.c @@ -29,6 +29,13 @@ char *idNames[IDCap] = { [Debug] = "", [Network] = "", }; + +enum Color idColors[IDCap] = { + [None] = Black, + [Debug] = Red, + [Network] = Gray, +}; + size_t idNext = Network + 1; struct Self self; diff --git a/chat.h b/chat.h index 9060f29..4ced983 100644 --- a/chat.h +++ b/chat.h @@ -26,8 +26,21 @@ typedef unsigned char byte; +#define B "\2" +#define C "\3" +#define R "\17" +#define V "\26" +#define I "\35" +#define U "\37" +enum Color { + White, Black, Blue, Green, Red, Brown, Magenta, Orange, + Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, + Default = 99, +}; + enum { None, Debug, Network, IDCap = 256 }; extern char *idNames[IDCap]; +extern enum Color idColors[IDCap]; extern size_t idNext; static inline size_t idFind(const char *name) { @@ -36,6 +49,7 @@ static inline size_t idFind(const char *name) { } return None; } + static inline size_t idFor(const char *name) { size_t id = idFind(name); if (id) return id; @@ -83,28 +97,6 @@ struct Message { char *params[ParamCap]; }; -#define B "\2" -#define C "\3" -#define R "\17" -#define V "\26" -#define I "\35" -#define U "\37" -enum Color { - White, Black, Blue, Green, Red, Brown, Magenta, Orange, - Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, - Default = 99, -}; -static inline enum Color hash(const char *str) { - if (*str == '~') str++; - uint32_t hash = 0; - for (; *str; ++str) { - hash = (hash << 5) | (hash >> 27); - hash ^= *str; - hash *= 0x27220A95; - } - return 2 + hash % 14; -} - void ircConfig(bool insecure, const char *cert, const char *priv); int ircConnect(const char *host, const char *port); void ircRecv(void); @@ -140,6 +132,17 @@ void termTitle(const char *title); void termMode(enum TermMode mode, bool set); enum TermEvent termEvent(char ch); +static inline enum Color hash(const char *str) { + if (*str == '~') str++; + uint32_t hash = 0; + for (; *str; ++str) { + hash = (hash << 5) | (hash >> 27); + hash ^= *str; + hash *= 0x27220A95; + } + return 2 + hash % 14; +} + #define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4) static const char Base64[64] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" diff --git a/handle.c b/handle.c index 609867e..f52f6f9 100644 --- a/handle.c +++ b/handle.c @@ -183,12 +183,13 @@ static void handleJoin(struct Message *msg) { require(msg, true, 1); size_t id = idFor(msg->params[0]); if (self.nick && !strcmp(msg->nick, self.nick)) { + idColors[id] = hash(msg->params[0]); uiShowID(id); } uiFormat( id, Cold, tagTime(msg), C"%02d%s"C" arrives in "C"%02d%s"C, - hash(msg->user), msg->nick, hash(idNames[id]), idNames[id] + hash(msg->user), msg->nick, idColors[id], idNames[id] ); } diff --git a/ui.c b/ui.c index 3ae6592..961e448 100644 --- a/ui.c +++ b/ui.c @@ -268,15 +268,15 @@ static void statusUpdate(void) { const struct Window *window; for (num = 0, window = windows.head; window; ++num, window = window->next) { if (!window->unread && window != windows.active) continue; - enum Color color = hash(idNames[window->id]); // FIXME: queries. int unread; char buf[256]; snprintf( buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", - color, (window == windows.active ? V : ""), + idColors[window->id], (window == windows.active ? V : ""), num, idNames[window->id], - &unread, (window->heat > Warm ? White : color), window->unread, - color + &unread, (window->heat > Warm ? White : idColors[window->id]), + window->unread, + idColors[window->id] ); if (!window->unread) buf[unread] = '\0'; styleAdd(status, buf); -- cgit 1.4.1 From 052cd2ed2688867f8b980d283ea3aa410d9dd6aa Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:34:05 -0500 Subject: Remove style string macros --- chat.h | 6 ------ handle.c | 2 +- irc.c | 2 +- ui.c | 4 ++-- 4 files changed, 4 insertions(+), 10 deletions(-) (limited to 'handle.c') diff --git a/chat.h b/chat.h index 4ced983..275fef9 100644 --- a/chat.h +++ b/chat.h @@ -26,12 +26,6 @@ typedef unsigned char byte; -#define B "\2" -#define C "\3" -#define R "\17" -#define V "\26" -#define I "\35" -#define U "\37" enum Color { White, Black, Blue, Green, Red, Brown, Magenta, Orange, Yellow, LightGreen, Cyan, LightCyan, LightBlue, Pink, Gray, LightGray, diff --git a/handle.c b/handle.c index f52f6f9..ef61129 100644 --- a/handle.c +++ b/handle.c @@ -188,7 +188,7 @@ static void handleJoin(struct Message *msg) { } uiFormat( id, Cold, tagTime(msg), - C"%02d%s"C" arrives in "C"%02d%s"C, + "\3%02d%s\3 arrives in \3%02d%s\3", hash(msg->user), msg->nick, idColors[id], idNames[id] ); } diff --git a/irc.c b/irc.c index f07fcc8..d8c6a21 100644 --- a/irc.c +++ b/irc.c @@ -105,7 +105,7 @@ static void debug(char dir, const char *line) { if (!self.debug) return; size_t len = strcspn(line, "\r\n"); uiFormat( - Debug, Cold, NULL, C"%d%c%c"C" %.*s", + Debug, Cold, NULL, "\3%d%c%c\3 %.*s", Gray, dir, dir, (int)len, line ); if (!isatty(STDERR_FILENO)) { diff --git a/ui.c b/ui.c index 961e448..3f74e14 100644 --- a/ui.c +++ b/ui.c @@ -271,8 +271,8 @@ static void statusUpdate(void) { int unread; char buf[256]; snprintf( - buf, sizeof(buf), C"%d%s %d %s %n("C"%02d%d"C"%d) ", - idColors[window->id], (window == windows.active ? V : ""), + buf, sizeof(buf), "\3%d%s %d %s %n(\3%02d%d\3%d) ", + idColors[window->id], (window == windows.active ? "\26" : ""), num, idNames[window->id], &unread, (window->heat > Warm ? White : idColors[window->id]), window->unread, -- cgit 1.4.1 From dce7891331fcf3b86095b64bea8853942dfd667c Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 03:43:18 -0500 Subject: Add extremely basid handlePrivmsg --- handle.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'handle.c') diff --git a/handle.c b/handle.c index ef61129..da635b4 100644 --- a/handle.c +++ b/handle.c @@ -193,6 +193,18 @@ static void handleJoin(struct Message *msg) { ); } +static void handlePrivmsg(struct Message *msg) { + require(msg, true, 2); + bool query = self.nick && !strcmp(msg->params[0], self.nick); + size_t id = idFor(query ? msg->nick : msg->params[0]); + if (query) idColors[id] = hash(msg->user); + uiFormat( + id, Warm, tagTime(msg), + "\3%d<%s>\3 %s", + hash(msg->user), msg->nick, msg->params[1] + ); +} + static void handlePing(struct Message *msg) { require(msg, false, 1); ircFormat("PONG :%s\r\n", msg->params[0]); @@ -215,6 +227,7 @@ static const struct Handler { { "CAP", handleCap }, { "JOIN", handleJoin }, { "PING", handlePing }, + { "PRIVMSG", handlePrivmsg }, }; static int compar(const void *cmd, const void *_handler) { -- cgit 1.4.1 From b535f0abdde6fb79f9f972d0b39c8b0a7a837339 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 17:26:20 -0500 Subject: Handle notices and actions --- handle.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index da635b4..2af5837 100644 --- a/handle.c +++ b/handle.c @@ -193,15 +193,31 @@ static void handleJoin(struct Message *msg) { ); } +static bool isAction(struct Message *msg) { + if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; + msg->params[1] += 8; + size_t len = strlen(msg->params[1]); + if (msg->params[1][len - 1] == '\1') msg->params[1][len - 1] = '\0'; + return true; +} + static void handlePrivmsg(struct Message *msg) { require(msg, true, 2); - bool query = self.nick && !strcmp(msg->params[0], self.nick); - size_t id = idFor(query ? msg->nick : msg->params[0]); - if (query) idColors[id] = hash(msg->user); + bool query = msg->params[0][0] != '#'; // FIXME: CHANTYPES. + bool network = query && strchr(msg->nick, '.'); + bool notice = (msg->cmd[0] == 'N'); + bool action = isAction(msg); + // TODO: Send services to Network? + size_t id = (network ? Network : idFor(query ? msg->nick : msg->params[0])); + if (query && !network) idColors[id] = hash(msg->user); uiFormat( id, Warm, tagTime(msg), - "\3%d<%s>\3 %s", - hash(msg->user), msg->nick, msg->params[1] + "\3%d%s%s%s\3 %s", + hash(msg->user), + (action ? "* " : notice ? "-" : "<"), + msg->nick, + (action ? "" : notice ? "-" : ">"), + msg->params[1] ); } @@ -226,6 +242,7 @@ static const struct Handler { { "AUTHENTICATE", handleAuthenticate }, { "CAP", handleCap }, { "JOIN", handleJoin }, + { "NOTICE", handlePrivmsg }, { "PING", handlePing }, { "PRIVMSG", handlePrivmsg }, }; -- cgit 1.4.1 From 0d6a60cc6634cafe03671e9d5a1a64295c98bb9d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 17:37:36 -0500 Subject: Save NETWORK, CHANTYPES, PREFIX from ISUPPORT --- chat.c | 4 ++++ chat.h | 11 ++++++++++- handle.c | 14 +++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) (limited to 'handle.c') diff --git a/chat.c b/chat.c index b61dd34..162f68f 100644 --- a/chat.c +++ b/chat.c @@ -80,6 +80,10 @@ int main(int argc, char *argv[]) { if (!user) user = nick; if (!real) real = nick; + set(&self.network, host); + set(&self.chanTypes, "#&"); + set(&self.prefixes, "@+"); + ircConfig(insecure, cert, priv); uiInit(); diff --git a/chat.h b/chat.h index 275fef9..f9de779 100644 --- a/chat.h +++ b/chat.h @@ -65,12 +65,21 @@ enum Cap { extern struct Self { bool debug; + char *plain; const char *join; enum Cap caps; - char *plain; + char *network; + char *chanTypes; + char *prefixes; char *nick; } self; +static inline void set(char **field, const char *value) { + free(*field); + *field = strdup(value); + if (!*field) err(EX_OSERR, "strdup"); +} + #define ENUM_TAG \ X("time", TagTime) diff --git a/handle.c b/handle.c index 2af5837..2766cc8 100644 --- a/handle.c +++ b/handle.c @@ -59,12 +59,6 @@ static const char *capList(enum Cap caps) { return buf; } -static void set(char **field, const char *value) { - free(*field); - *field = strdup(value); - if (!*field) err(EX_OSERR, "strdup"); -} - static void require(struct Message *msg, bool origin, size_t len) { if (origin) { if (!msg->nick) errx(EX_PROTOCOL, "%s missing origin", msg->cmd); @@ -158,16 +152,22 @@ static void handleReplyWelcome(struct Message *msg) { } static void handleReplyISupport(struct Message *msg) { - // TODO: Extract CHANTYPES and PREFIX for future use. for (size_t i = 1; i < ParamCap; ++i) { if (!msg->params[i]) break; char *key = strsep(&msg->params[i], "="); if (!msg->params[i]) continue; if (!strcmp(key, "NETWORK")) { + set(&self.network, msg->params[i]); uiFormat( Network, Cold, tagTime(msg), "You arrive in %s", msg->params[i] ); + } else if (!strcmp(key, "CHANTYPES")) { + set(&self.chanTypes, msg->params[i]); + } else if (!strcmp(key, "PREFIX")) { + strsep(&msg->params[i], ")"); + if (!msg->params[i]) continue; + set(&self.prefixes, msg->params[i]); } } } -- cgit 1.4.1 From aec28a9327c963ffda0a438107b9442bc90b84e4 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 17:45:19 -0500 Subject: Check queries against chanTypes --- handle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 2766cc8..688dcdb 100644 --- a/handle.c +++ b/handle.c @@ -203,7 +203,7 @@ static bool isAction(struct Message *msg) { static void handlePrivmsg(struct Message *msg) { require(msg, true, 2); - bool query = msg->params[0][0] != '#'; // FIXME: CHANTYPES. + bool query = !strchr(self.chanTypes, msg->params[0][0]); bool network = query && strchr(msg->nick, '.'); bool notice = (msg->cmd[0] == 'N'); bool action = isAction(msg); -- cgit 1.4.1 From 2f9a9c663a577e7087c1160a0cd651f706e4921b Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 2 Feb 2020 17:50:28 -0500 Subject: Remove services TODO Two goals: 1. Messages should always be routed to the same place. 2. You should be able to see your messages to *Serv and its responses together. --- handle.c | 1 - 1 file changed, 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 688dcdb..4bc2e3d 100644 --- a/handle.c +++ b/handle.c @@ -207,7 +207,6 @@ static void handlePrivmsg(struct Message *msg) { bool network = query && strchr(msg->nick, '.'); bool notice = (msg->cmd[0] == 'N'); bool action = isAction(msg); - // TODO: Send services to Network? size_t id = (network ? Network : idFor(query ? msg->nick : msg->params[0])); if (query && !network) idColors[id] = hash(msg->user); uiFormat( -- cgit 1.4.1 From 26e9dd9adfd4df90cd4cc6ef14d91cdad2efb239 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 3 Feb 2020 18:41:52 -0500 Subject: Use time_t rather than struct tm --- chat.h | 4 ++-- handle.c | 11 ++++++----- ui.c | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) (limited to 'handle.c') diff --git a/chat.h b/chat.h index 76d69c9..9165c13 100644 --- a/chat.h +++ b/chat.h @@ -115,9 +115,9 @@ void uiShow(void); void uiHide(void); void uiDraw(void); void uiShowID(size_t id); -void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str); +void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( - size_t id, enum Heat heat, const struct tm *time, const char *format, ... + size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); static inline enum Color hash(const char *str) { diff --git a/handle.c b/handle.c index 4bc2e3d..ef49f7c 100644 --- a/handle.c +++ b/handle.c @@ -71,12 +71,13 @@ static void require(struct Message *msg, bool origin, size_t len) { } } -static const struct tm *tagTime(const struct Message *msg) { +static const time_t *tagTime(const struct Message *msg) { + static time_t time; + struct tm tm; if (!msg->tags[TagTime]) return NULL; - static struct tm time; - char *rest = strptime(msg->tags[TagTime], "%FT%T", &time); - time.tm_gmtoff = 0; - return (rest ? &time : NULL); + if (!strptime(msg->tags[TagTime], "%FT%T", &tm)) return NULL; + time = timegm(&tm); + return &time; } typedef void Handler(struct Message *msg); diff --git a/ui.c b/ui.c index 072ee84..e2746f1 100644 --- a/ui.c +++ b/ui.c @@ -372,7 +372,7 @@ void uiShowID(size_t id) { statusUpdate(); } -void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) { +void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str) { (void)time; struct Window *window = windowFor(id); waddch(window->pad, '\n'); @@ -387,7 +387,7 @@ void uiWrite(size_t id, enum Heat heat, const struct tm *time, const char *str) } void uiFormat( - size_t id, enum Heat heat, const struct tm *time, const char *format, ... + size_t id, enum Heat heat, const time_t *time, const char *format, ... ) { char buf[1024]; va_list ap; -- cgit 1.4.1 From d57df09511a5e4136559ccdd01ab56e906827f96 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 19:50:23 -0500 Subject: Align word wrapping with tab character Also fixes handling whitespace directly after control codes. --- handle.c | 2 +- irc.c | 2 +- ui.c | 17 +++++++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index ef49f7c..29d1500 100644 --- a/handle.c +++ b/handle.c @@ -212,7 +212,7 @@ static void handlePrivmsg(struct Message *msg) { if (query && !network) idColors[id] = hash(msg->user); uiFormat( id, Warm, tagTime(msg), - "\3%d%s%s%s\3 %s", + "\3%d%s%s%s\3\t%s", hash(msg->user), (action ? "* " : notice ? "-" : "<"), msg->nick, diff --git a/irc.c b/irc.c index d8c6a21..2d6f00b 100644 --- a/irc.c +++ b/irc.c @@ -105,7 +105,7 @@ static void debug(char dir, const char *line) { if (!self.debug) return; size_t len = strcspn(line, "\r\n"); uiFormat( - Debug, Cold, NULL, "\3%d%c%c\3 %.*s", + Debug, Cold, NULL, "\3%d%c%c\3\t%.*s", Gray, dir, dir, (int)len, line ); if (!isatty(STDERR_FILENO)) { diff --git a/ui.c b/ui.c index 0c2a64e..e93c08c 100644 --- a/ui.c +++ b/ui.c @@ -287,14 +287,24 @@ static void styleAdd(WINDOW *win, const char *str, bool show) { getmaxyx(win, y, width); size_t len; + int align = 0; struct Style style = Reset; while (*str) { - if (*str == ' ') { + if (*str == '\t') { + waddch(win, ' '); + getyx(win, y, align); + str++; + } else if (*str == ' ') { getyx(win, y, x); const char *word = &str[strspn(str, " ")]; if (width - x - 1 <= wordWidth(word)) { waddch(win, '\n'); + getyx(win, y, x); + wmove(win, y, align); str = word; + } else { + waddch(win, ' '); + str++; } } @@ -313,9 +323,8 @@ static void styleAdd(WINDOW *win, const char *str, bool show) { if (str - code > 1) waddnstr(win, &code[1], str - &code[1]); } - size_t sp = strspn(str, " "); - sp += strcspn(&str[sp], " "); - if (sp < len) len = sp; + size_t ws = strcspn(str, "\t "); + if (ws < len) len = ws; wattr_set( win, -- cgit 1.4.1 From ea93c9a6d98904c2334f86358a261cc6e556bdf4 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 20:27:18 -0500 Subject: Set self.color --- handle.c | 1 + 1 file changed, 1 insertion(+) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 29d1500..305d70d 100644 --- a/handle.c +++ b/handle.c @@ -184,6 +184,7 @@ static void handleJoin(struct Message *msg) { require(msg, true, 1); size_t id = idFor(msg->params[0]); if (self.nick && !strcmp(msg->nick, self.nick)) { + self.color = hash(msg->user); idColors[id] = hash(msg->params[0]); uiShowID(id); } -- cgit 1.4.1 From de4c9df0748f8f3792f4dfc4db4eb7853679646d Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 4 Feb 2020 20:40:49 -0500 Subject: Align join messages after nick --- handle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 305d70d..32f8939 100644 --- a/handle.c +++ b/handle.c @@ -190,7 +190,7 @@ static void handleJoin(struct Message *msg) { } uiFormat( id, Cold, tagTime(msg), - "\3%02d%s\3 arrives in \3%02d%s\3", + "\3%02d%s\3\tarrives in \3%02d%s\3", hash(msg->user), msg->nick, idColors[id], idNames[id] ); } -- cgit 1.4.1 From 37ec1e8232ab5aadd1989be1c7b518c98438cfd0 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 00:24:54 -0500 Subject: Align MOTD after - --- handle.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 32f8939..b5585ba 100644 --- a/handle.c +++ b/handle.c @@ -176,8 +176,11 @@ static void handleReplyISupport(struct Message *msg) { static void handleReplyMOTD(struct Message *msg) { require(msg, false, 2); char *line = msg->params[1]; - if (!strncmp(line, "- ", 2)) line += 2; - uiFormat(Network, Cold, tagTime(msg), "%s", line); + if (!strncmp(line, "- ", 2)) { + uiFormat(Network, Cold, tagTime(msg), "\3%d-\3\t%s", Gray, &line[2]); + } else { + uiFormat(Network, Cold, tagTime(msg), "%s", line); + } } static void handleJoin(struct Message *msg) { -- cgit 1.4.1 From 7414a8a11cd8d16fea47e30513b3a5eaeb232ba1 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 00:40:24 -0500 Subject: Save own username for message echoing --- chat.h | 1 + command.c | 2 +- handle.c | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'handle.c') diff --git a/chat.h b/chat.h index c8b31c2..90c7da8 100644 --- a/chat.h +++ b/chat.h @@ -73,6 +73,7 @@ extern struct Self { char *chanTypes; char *prefixes; char *nick; + char *user; enum Color color; } self; diff --git a/command.c b/command.c index ab05587..76d7d7b 100644 --- a/command.c +++ b/command.c @@ -23,7 +23,7 @@ void command(size_t id, char *input) { ircFormat("PRIVMSG %s :%s\r\n", idNames[id], input); struct Message msg = { .nick = self.nick, - // TODO: .user, + .user = self.user, .cmd = "PRIVMSG", .params[0] = idNames[id], .params[1] = input, diff --git a/handle.c b/handle.c index b5585ba..85783d7 100644 --- a/handle.c +++ b/handle.c @@ -187,7 +187,10 @@ static void handleJoin(struct Message *msg) { require(msg, true, 1); size_t id = idFor(msg->params[0]); if (self.nick && !strcmp(msg->nick, self.nick)) { - self.color = hash(msg->user); + if (!self.user) { + set(&self.user, msg->user); + self.color = hash(msg->user); + } idColors[id] = hash(msg->params[0]); uiShowID(id); } -- cgit 1.4.1 From 3085779d86f3ed66bd24eccf1f64ffc1bd71afcd Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 5 Feb 2020 23:27:43 -0500 Subject: Handle ERROR --- handle.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 85783d7..89ebcb2 100644 --- a/handle.c +++ b/handle.c @@ -233,6 +233,11 @@ static void handlePing(struct Message *msg) { ircFormat("PONG :%s\r\n", msg->params[0]); } +static void handleError(struct Message *msg) { + require(msg, false, 1); + errx(EX_UNAVAILABLE, "%s", msg->params[0]); +} + static const struct Handler { const char *cmd; Handler *fn; @@ -248,6 +253,7 @@ static const struct Handler { { "906", handleErrorSASLFail }, { "AUTHENTICATE", handleAuthenticate }, { "CAP", handleCap }, + { "ERROR", handleError }, { "JOIN", handleJoin }, { "NOTICE", handlePrivmsg }, { "PING", handlePing }, -- cgit 1.4.1 From db499dc5f50dba23c2ab218d439cfce51c41bc6b Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 01:03:21 -0500 Subject: Send self.join without colon If someone is weird enough to use channel keys, they can -j '#foo key'. --- handle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 89ebcb2..a111b16 100644 --- a/handle.c +++ b/handle.c @@ -149,7 +149,7 @@ static void handleErrorSASLFail(struct Message *msg) { static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); - if (self.join) ircFormat("JOIN :%s\r\n", self.join); + if (self.join) ircFormat("JOIN %s\r\n", self.join); } static void handleReplyISupport(struct Message *msg) { -- cgit 1.4.1 From 5fb492f8cda7598cbf1a977b0b3c66f9dc1b24f0 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 01:16:35 -0500 Subject: Handle PART --- handle.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'handle.c') diff --git a/handle.c b/handle.c index a111b16..8a68c95 100644 --- a/handle.c +++ b/handle.c @@ -201,6 +201,18 @@ static void handleJoin(struct Message *msg) { ); } +static void handlePart(struct Message *msg) { + require(msg, true, 1); + size_t id = idFor(msg->params[0]); + uiFormat( + id, Cold, tagTime(msg), + "\3%02d%s\3\tleaves \3%02d%s\3%s%s", + hash(msg->user), msg->nick, idColors[id], idNames[id], + (msg->params[1] ? ": " : ""), + (msg->params[1] ? msg->params[1] : "") + ); +} + static bool isAction(struct Message *msg) { if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; msg->params[1] += 8; @@ -256,6 +268,7 @@ static const struct Handler { { "ERROR", handleError }, { "JOIN", handleJoin }, { "NOTICE", handlePrivmsg }, + { "PART", handlePart }, { "PING", handlePing }, { "PRIVMSG", handlePrivmsg }, }; -- cgit 1.4.1 From 5e98d83f83f12f208cc9089d87a18c73a8a6fcfc Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 02:07:39 -0500 Subject: Handle TOPIC and replies --- handle.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 8a68c95..e636434 100644 --- a/handle.c +++ b/handle.c @@ -197,22 +197,57 @@ static void handleJoin(struct Message *msg) { uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tarrives in \3%02d%s\3", - hash(msg->user), msg->nick, idColors[id], idNames[id] + hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] ); } static void handlePart(struct Message *msg) { require(msg, true, 1); - size_t id = idFor(msg->params[0]); uiFormat( - id, Cold, tagTime(msg), + idFor(msg->params[0]), Cold, tagTime(msg), "\3%02d%s\3\tleaves \3%02d%s\3%s%s", - hash(msg->user), msg->nick, idColors[id], idNames[id], + hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], (msg->params[1] ? ": " : ""), (msg->params[1] ? msg->params[1] : "") ); } +static void handleReplyNoTopic(struct Message *msg) { + require(msg, false, 2); + uiFormat( + idFor(msg->params[1]), Cold, tagTime(msg), + "There is no sign in \3%02d%s\3", + hash(msg->params[1]), msg->params[1] + ); +} + +static void handleReplyTopic(struct Message *msg) { + require(msg, false, 3); + uiFormat( + idFor(msg->params[1]), Cold, tagTime(msg), + "The sign in \3%02d%s\3 reads: %s", + hash(msg->params[1]), msg->params[1], msg->params[2] + ); +} + +static void handleTopic(struct Message *msg) { + require(msg, true, 2); + if (msg->params[1][0]) { + uiFormat( + idFor(msg->params[0]), 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] + ); + } else { + uiFormat( + idFor(msg->params[0]), Warm, tagTime(msg), + "\3%02d%s\3\tremoves the sign in \3%02d%s\3", + hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] + ); + } +} + static bool isAction(struct Message *msg) { if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; msg->params[1] += 8; @@ -256,6 +291,8 @@ static const struct Handler { } Handlers[] = { { "001", handleReplyWelcome }, { "005", handleReplyISupport }, + { "331", handleReplyNoTopic }, + { "332", handleReplyTopic }, { "372", handleReplyMOTD }, { "432", handleErrorErroneousNickname }, { "433", handleErrorNicknameInUse }, @@ -271,6 +308,7 @@ static const struct Handler { { "PART", handlePart }, { "PING", handlePing }, { "PRIVMSG", handlePrivmsg }, + { "TOPIC", handleTopic }, }; static int compar(const void *cmd, const void *_handler) { -- cgit 1.4.1 From 1e6e533538f5c18adc64f21155cb76277dcb2a9b Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 03:44:49 -0500 Subject: Send CAP END if CAP LS doesn't list anything good --- handle.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index e636434..cb080e9 100644 --- a/handle.c +++ b/handle.c @@ -98,7 +98,11 @@ static void handleCap(struct Message *msg) { enum Cap caps = capParse(msg->params[2]); if (!strcmp(msg->params[1], "LS")) { caps &= ~CapSASL; - ircFormat("CAP REQ :%s\r\n", capList(caps)); + if (caps) { + ircFormat("CAP REQ :%s\r\n", capList(caps)); + } else { + if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); + } } else if (!strcmp(msg->params[1], "ACK")) { self.caps |= caps; if (caps & CapSASL) { -- cgit 1.4.1 From 30b3780e57e532e928d445b882b218decb88af55 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 04:01:11 -0500 Subject: Route own query messages correctly --- handle.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index cb080e9..9244e66 100644 --- a/handle.c +++ b/handle.c @@ -263,11 +263,20 @@ static bool isAction(struct Message *msg) { static void handlePrivmsg(struct Message *msg) { require(msg, true, 2); bool query = !strchr(self.chanTypes, msg->params[0][0]); - bool network = query && strchr(msg->nick, '.'); + bool network = strchr(msg->nick, '.'); + bool mine = self.nick && !strcmp(msg->nick, self.nick); + size_t id; + if (query && network) { + id = Network; + } else if (query && !mine) { + id = idFor(msg->nick); + idColors[id] = hash(msg->user); + } else { + id = idFor(msg->params[0]); + } + bool notice = (msg->cmd[0] == 'N'); bool action = isAction(msg); - size_t id = (network ? Network : idFor(query ? msg->nick : msg->params[0])); - if (query && !network) idColors[id] = hash(msg->user); uiFormat( id, Warm, tagTime(msg), "\3%d%s%s%s\3\t%s", -- cgit 1.4.1 From 32ec697092a8d9b4925e64519643c9005f2d408c Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 04:18:15 -0500 Subject: Handle mentions --- handle.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 9244e66..01e2e5e 100644 --- a/handle.c +++ b/handle.c @@ -14,6 +14,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -260,6 +261,21 @@ static bool isAction(struct Message *msg) { return true; } +static bool isMention(const struct Message *msg) { + if (!self.nick) return false; + 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] : ' '); + char b = (match[len] ? match[len] : ' '); + if ((isspace(a) || ispunct(a)) && (isspace(b) || ispunct(b))) { + return true; + } + match = &match[len]; + } + return false; +} + static void handlePrivmsg(struct Message *msg) { require(msg, true, 2); bool query = !strchr(self.chanTypes, msg->params[0][0]); @@ -277,9 +293,11 @@ static void handlePrivmsg(struct Message *msg) { bool notice = (msg->cmd[0] == 'N'); bool action = isAction(msg); + bool mention = !mine && isMention(msg); uiFormat( - id, Warm, tagTime(msg), - "\3%d%s%s%s\3\t%s", + id, (mention || query ? Hot : Warm), tagTime(msg), + "%s\3%d%s%s%s\17\t%s", + (mention ? "\26" : ""), hash(msg->user), (action ? "* " : notice ? "-" : "<"), msg->nick, -- cgit 1.4.1 From e1f10958c9954592a81b6b52693e3f63304d22d8 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 04:37:28 -0500 Subject: Never consider notices hot --- handle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 01e2e5e..49011f5 100644 --- a/handle.c +++ b/handle.c @@ -295,7 +295,7 @@ static void handlePrivmsg(struct Message *msg) { bool action = isAction(msg); bool mention = !mine && isMention(msg); uiFormat( - id, (mention || query ? Hot : Warm), tagTime(msg), + id, (!notice && (mention || query) ? Hot : Warm), tagTime(msg), "%s\3%d%s%s%s\17\t%s", (mention ? "\26" : ""), hash(msg->user), -- cgit 1.4.1 From 34514cf2ee6dd020ca812653ce3a23e737d2bf62 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 18:48:49 -0500 Subject: Render actions in italic Also render italic as normal if it's unsupported, as that is what would happen anyway if curses has A_ITALIC but the terminal has no sitm. That format string is kinda bad. --- handle.c | 8 +++++--- ui.c | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 49011f5..ab482fd 100644 --- a/handle.c +++ b/handle.c @@ -294,14 +294,16 @@ static void handlePrivmsg(struct Message *msg) { bool notice = (msg->cmd[0] == 'N'); bool action = isAction(msg); bool mention = !mine && isMention(msg); + const char *italic = (action ? "\35" : ""); + const char *reverse = (mention ? "\26" : ""); uiFormat( id, (!notice && (mention || query) ? Hot : Warm), tagTime(msg), - "%s\3%d%s%s%s\17\t%s", - (mention ? "\26" : ""), - hash(msg->user), + "%s%s\3%d%s%s%s\3%s\t%s", + italic, reverse, hash(msg->user), (action ? "* " : notice ? "-" : "<"), msg->nick, (action ? "" : notice ? "-" : ">"), + reverse, msg->params[1] ); } diff --git a/ui.c b/ui.c index b0e30b9..9f0bb88 100644 --- a/ui.c +++ b/ui.c @@ -39,7 +39,7 @@ #undef lines #ifndef A_ITALIC -#define A_ITALIC A_UNDERLINE +#define A_ITALIC A_NORMAL #endif #define BOTTOM (LINES - 1) -- cgit 1.4.1 From 87e42cc62768435dea48a86a60729cd5696f67f1 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 6 Feb 2020 22:59:49 -0500 Subject: Color notices LightGray by default --- handle.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index ab482fd..fb49206 100644 --- a/handle.c +++ b/handle.c @@ -294,18 +294,26 @@ static void handlePrivmsg(struct Message *msg) { bool notice = (msg->cmd[0] == 'N'); bool action = isAction(msg); bool mention = !mine && isMention(msg); - const char *italic = (action ? "\35" : ""); - const char *reverse = (mention ? "\26" : ""); - uiFormat( - id, (!notice && (mention || query) ? Hot : Warm), tagTime(msg), - "%s%s\3%d%s%s%s\3%s\t%s", - italic, reverse, hash(msg->user), - (action ? "* " : notice ? "-" : "<"), - msg->nick, - (action ? "" : notice ? "-" : ">"), - reverse, - msg->params[1] - ); + if (notice) { + uiFormat( + id, Warm, tagTime(msg), + "%s\3%d-%s-\17\3%d\t%s", + (mention ? "\26" : ""), hash(msg->user), msg->nick, + LightGray, msg->params[1] + ); + } else if (action) { + uiFormat( + id, (mention || query ? Hot : Warm), tagTime(msg), + "%s\35\3%d* %s\17\35\t%s", + (mention ? "\26" : ""), hash(msg->user), msg->nick, msg->params[1] + ); + } else { + uiFormat( + id, (mention || query ? Hot : Warm), tagTime(msg), + "%s\3%d<%s>\17\t%s", + (mention ? "\26" : ""), hash(msg->user), msg->nick, msg->params[1] + ); + } } static void handlePing(struct Message *msg) { -- cgit 1.4.1 From d314523b90f41cfdbca867ad0ad48f2f68f66c58 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 23:33:23 -0500 Subject: Update completion on join, part, privmsg --- chat.h | 1 + complete.c | 13 +++++++++++++ handle.c | 8 +++++++- 3 files changed, 21 insertions(+), 1 deletion(-) (limited to 'handle.c') diff --git a/chat.h b/chat.h index f164e7a..aec5a68 100644 --- a/chat.h +++ b/chat.h @@ -153,6 +153,7 @@ void completeAccept(void); void completeReject(void); void completeAdd(size_t id, const char *str, enum Color color); void completeTouch(size_t id, const char *str, enum Color color); +void completeRemove(size_t id, const char *str); FILE *configOpen(const char *path, const char *mode); int getopt_config( diff --git a/complete.c b/complete.c index 8247149..5067512 100644 --- a/complete.c +++ b/complete.c @@ -109,3 +109,16 @@ void completeAccept(void) { void completeReject(void) { match = NULL; } + +void completeRemove(size_t id, const char *str) { + struct Node *next = NULL; + for (struct Node *node = head; node; node = next) { + next = node->next; + if (id && node->id != id) continue; + if (strcmp(node->str, str)) continue; + if (match == node) match = NULL; + detach(node); + free(node->str); + free(node); + } +} diff --git a/handle.c b/handle.c index fb49206..4faabba 100644 --- a/handle.c +++ b/handle.c @@ -154,6 +154,7 @@ static void handleErrorSASLFail(struct Message *msg) { static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); + completeTouch(None, self.nick, Default); if (self.join) ircFormat("JOIN %s\r\n", self.join); } @@ -197,8 +198,10 @@ static void handleJoin(struct Message *msg) { self.color = hash(msg->user); } idColors[id] = hash(msg->params[0]); + completeTouch(None, msg->params[0], idColors[id]); uiShowID(id); } + completeTouch(id, msg->nick, hash(msg->user)); uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tarrives in \3%02d%s\3", @@ -208,8 +211,10 @@ static void handleJoin(struct Message *msg) { static void handlePart(struct Message *msg) { require(msg, true, 1); + size_t id = idFor(msg->params[0]); + completeRemove(id, msg->nick); uiFormat( - idFor(msg->params[0]), Cold, tagTime(msg), + id, Cold, tagTime(msg), "\3%02d%s\3\tleaves \3%02d%s\3%s%s", hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0], (msg->params[1] ? ": " : ""), @@ -294,6 +299,7 @@ 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)); if (notice) { uiFormat( id, Warm, tagTime(msg), -- cgit 1.4.1 From 8b7cc1a0ed95e8a3ff413fa77eb12a3dca7fccb4 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 7 Feb 2020 23:44:03 -0500 Subject: Clear completion for ID on self-part --- chat.h | 1 + complete.c | 12 ++++++++++++ handle.c | 3 +++ 3 files changed, 16 insertions(+) (limited to 'handle.c') diff --git a/chat.h b/chat.h index aec5a68..6eeed60 100644 --- a/chat.h +++ b/chat.h @@ -154,6 +154,7 @@ void completeReject(void); void completeAdd(size_t id, const char *str, enum Color color); void completeTouch(size_t id, const char *str, enum Color color); void completeRemove(size_t id, const char *str); +void completeClear(size_t id); FILE *configOpen(const char *path, const char *mode); int getopt_config( diff --git a/complete.c b/complete.c index 5067512..437bb7d 100644 --- a/complete.c +++ b/complete.c @@ -122,3 +122,15 @@ void completeRemove(size_t id, const char *str) { free(node); } } + +void completeClear(size_t id) { + struct Node *next = NULL; + for (struct Node *node = head; node; node = next) { + next = node->next; + if (node->id != id) continue; + if (match == node) match = NULL; + detach(node); + free(node->str); + free(node); + } +} diff --git a/handle.c b/handle.c index 4faabba..b73d200 100644 --- a/handle.c +++ b/handle.c @@ -212,6 +212,9 @@ static void handleJoin(struct Message *msg) { static void handlePart(struct Message *msg) { require(msg, true, 1); size_t id = idFor(msg->params[0]); + if (self.nick && !strcmp(msg->nick, self.nick)) { + completeClear(id); + } completeRemove(id, msg->nick); uiFormat( id, Cold, tagTime(msg), -- cgit 1.4.1 From 58e1d5b4e2fabead1aae356dd060bfc9748bdd5e Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 00:01:59 -0500 Subject: Handle NICK --- chat.h | 2 ++ complete.c | 21 +++++++++++++++++++++ handle.c | 17 +++++++++++++++++ 3 files changed, 40 insertions(+) (limited to 'handle.c') diff --git a/chat.h b/chat.h index 6eeed60..413cee4 100644 --- a/chat.h +++ b/chat.h @@ -151,8 +151,10 @@ char *editTail(void); const char *complete(size_t id, const char *prefix); void completeAccept(void); void completeReject(void); +size_t completeID(const char *str); void completeAdd(size_t id, const char *str, enum Color color); void completeTouch(size_t id, const char *str, enum Color color); +void completeReplace(size_t id, const char *old, const char *new); void completeRemove(size_t id, const char *str); void completeClear(size_t id); diff --git a/complete.c b/complete.c index 437bb7d..c194536 100644 --- a/complete.c +++ b/complete.c @@ -110,6 +110,27 @@ void completeReject(void) { match = NULL; } +size_t completeID(const char *str) { + for (match = (match ? match->next : head); match; match = match->next) { + if (match->id && !strcmp(match->str, str)) return match->id; + } + return None; +} + +void completeReplace(size_t id, const char *old, const char *new) { + struct Node *next = NULL; + for (struct Node *node = head; node; node = node->next) { + next = node->next; + if (id && node->id != id) continue; + if (strcmp(node->str, old)) continue; + if (match == node) match = NULL; + free(node->str); + node->str = strdup(new); + if (!node->str) err(EX_OSERR, "strdup"); + prepend(detach(node)); + } +} + void completeRemove(size_t id, const char *str) { struct Node *next = NULL; for (struct Node *node = head; node; node = next) { diff --git a/handle.c b/handle.c index b73d200..fe64f33 100644 --- a/handle.c +++ b/handle.c @@ -261,6 +261,22 @@ static void handleTopic(struct Message *msg) { } } +static void handleNick(struct Message *msg) { + require(msg, true, 1); + if (self.nick && !strcmp(msg->nick, self.nick)) { + set(&self.nick, msg->params[0]); + } + size_t id; + completeReplace(None, msg->nick, msg->params[0]); + while (None != (id = completeID(msg->params[0]))) { + uiFormat( + id, Cold, 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] + ); + } +} + static bool isAction(struct Message *msg) { if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; msg->params[1] += 8; @@ -354,6 +370,7 @@ static const struct Handler { { "CAP", handleCap }, { "ERROR", handleError }, { "JOIN", handleJoin }, + { "NICK", handleNick }, { "NOTICE", handlePrivmsg }, { "PART", handlePart }, { "PING", handlePing }, -- cgit 1.4.1 From 7ebfeea33041f3d438e2de84760af950000393db Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 00:25:09 -0500 Subject: Handle NAMES reply --- handle.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'handle.c') diff --git a/handle.c b/handle.c index fe64f33..44329ff 100644 --- a/handle.c +++ b/handle.c @@ -14,6 +14,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -225,6 +226,32 @@ static void handlePart(struct Message *msg) { ); } +static void handleReplyNames(struct Message *msg) { + require(msg, false, 4); + size_t id = idFor(msg->params[2]); + char buf[1024]; + size_t len = 0; + while (msg->params[3]) { + char *name = strsep(&msg->params[3], " "); + name += strspn(name, self.prefixes); + char *nick = strsep(&name, "!"); + char *user = strsep(&name, "@"); + enum Color color = (user ? hash(user) : Default); + completeAdd(id, nick, color); + int n = snprintf( + &buf[len], sizeof(buf) - len, + "%s\3%02d%s\3", (len ? ", " : ""), color, nick + ); + assert(n > 0 && len + n < sizeof(buf)); + len += n; + } + uiFormat( + id, Cold, tagTime(msg), + "In \3%02d%s\3 are %s", + hash(msg->params[2]), msg->params[2], buf + ); +} + static void handleReplyNoTopic(struct Message *msg) { require(msg, false, 2); uiFormat( @@ -359,6 +386,7 @@ static const struct Handler { { "005", handleReplyISupport }, { "331", handleReplyNoTopic }, { "332", handleReplyTopic }, + { "353", handleReplyNames }, { "372", handleReplyMOTD }, { "432", handleErrorErroneousNickname }, { "433", handleErrorNicknameInUse }, -- cgit 1.4.1 From f14175ebede46eb9e1fbf239a5c3b349951d34fc Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 00:36:23 -0500 Subject: Handle QUIT --- handle.c | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 44329ff..de9e73a 100644 --- a/handle.c +++ b/handle.c @@ -226,6 +226,37 @@ static void handlePart(struct Message *msg) { ); } +static void handleNick(struct Message *msg) { + require(msg, true, 1); + if (self.nick && !strcmp(msg->nick, self.nick)) { + set(&self.nick, msg->params[0]); + } + size_t id; + while (None != (id = completeID(msg->nick))) { + uiFormat( + id, Cold, 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] + ); + } + completeReplace(None, msg->nick, msg->params[0]); +} + +static void handleQuit(struct Message *msg) { + require(msg, true, 0); + size_t id; + while (None != (id = completeID(msg->nick))) { + uiFormat( + id, Cold, tagTime(msg), + "\3%02d%s\3\tleaves%s%s", + hash(msg->user), msg->nick, + (msg->params[0] ? ": " : ""), + (msg->params[0] ? msg->params[0] : "") + ); + } + completeRemove(None, msg->nick); +} + static void handleReplyNames(struct Message *msg) { require(msg, false, 4); size_t id = idFor(msg->params[2]); @@ -288,22 +319,6 @@ static void handleTopic(struct Message *msg) { } } -static void handleNick(struct Message *msg) { - require(msg, true, 1); - if (self.nick && !strcmp(msg->nick, self.nick)) { - set(&self.nick, msg->params[0]); - } - size_t id; - completeReplace(None, msg->nick, msg->params[0]); - while (None != (id = completeID(msg->params[0]))) { - uiFormat( - id, Cold, 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] - ); - } -} - static bool isAction(struct Message *msg) { if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; msg->params[1] += 8; @@ -403,6 +418,7 @@ static const struct Handler { { "PART", handlePart }, { "PING", handlePing }, { "PRIVMSG", handlePrivmsg }, + { "QUIT", handleQuit }, { "TOPIC", handleTopic }, }; -- cgit 1.4.1 From b5707af4b842f521104c5fba07e5685612ff91f2 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 00:58:17 -0500 Subject: Handle KICK See I knew the color cache in complete would be useful in at least one place! --- chat.h | 3 ++- complete.c | 5 +++++ handle.c | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) (limited to 'handle.c') diff --git a/chat.h b/chat.h index 413cee4..bd36d27 100644 --- a/chat.h +++ b/chat.h @@ -151,12 +151,13 @@ char *editTail(void); const char *complete(size_t id, const char *prefix); void completeAccept(void); void completeReject(void); -size_t completeID(const char *str); void completeAdd(size_t id, const char *str, enum Color color); void completeTouch(size_t id, const char *str, enum Color color); void completeReplace(size_t id, const char *old, const char *new); void completeRemove(size_t id, const char *str); void completeClear(size_t id); +size_t completeID(const char *str); +enum Color completeColor(size_t id, const char *str); FILE *configOpen(const char *path, const char *mode); int getopt_config( diff --git a/complete.c b/complete.c index c194536..2f5275f 100644 --- a/complete.c +++ b/complete.c @@ -90,6 +90,11 @@ void completeTouch(size_t id, const char *str, enum Color color) { prepend(node ? detach(node) : alloc(id, str, color)); } +enum Color completeColor(size_t id, const char *str) { + struct Node *node = find(id, str); + return (node ? node->color : Default); +} + static struct Node *match; const char *complete(size_t id, const char *prefix) { diff --git a/handle.c b/handle.c index de9e73a..8ebc3b1 100644 --- a/handle.c +++ b/handle.c @@ -226,6 +226,25 @@ static void handlePart(struct Message *msg) { ); } +static void handleKick(struct Message *msg) { + require(msg, true, 2); + size_t id = idFor(msg->params[0]); + bool kicked = self.nick && !strcmp(msg->params[1], self.nick); + completeTouch(id, msg->nick, hash(msg->user)); + uiFormat( + id, (kicked ? Hot : Cold), tagTime(msg), + "%s\3%02d%s\17\tkicks \3%02d%s\3 out of \3%02d%s\3%s%s", + (kicked ? "\26" : ""), + hash(msg->user), msg->nick, + completeColor(id, msg->params[1]), msg->params[1], + hash(msg->params[0]), msg->params[0], + (msg->params[2] ? ": " : ""), + (msg->params[2] ? msg->params[2] : "") + ); + completeRemove(id, msg->params[1]); + if (kicked) completeClear(id); +} + static void handleNick(struct Message *msg) { require(msg, true, 1); if (self.nick && !strcmp(msg->nick, self.nick)) { @@ -413,6 +432,7 @@ static const struct Handler { { "CAP", handleCap }, { "ERROR", handleError }, { "JOIN", handleJoin }, + { "KICK", handleKick }, { "NICK", handleNick }, { "NOTICE", handlePrivmsg }, { "PART", handlePart }, -- cgit 1.4.1 From b6bf6d62b0bb6d203ce41e4b375c415ca8fde719 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 03:15:17 -0500 Subject: Only show expected topic/names replies --- chat.h | 5 +++++ command.c | 8 ++++++++ handle.c | 23 ++++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) (limited to 'handle.c') diff --git a/chat.h b/chat.h index 9daa38c..34e1812 100644 --- a/chat.h +++ b/chat.h @@ -114,6 +114,11 @@ void ircSend(const char *ptr, size_t len); void ircFormat(const char *format, ...) __attribute__((format(printf, 1, 2))); +extern struct Replies { + size_t topic; + size_t names; +} replies; + void handle(struct Message msg); void command(size_t id, char *input); const char *commandIsPrivmsg(size_t id, const char *input); diff --git a/command.c b/command.c index 1d1c756..9879dbe 100644 --- a/command.c +++ b/command.c @@ -71,7 +71,15 @@ static void commandMe(size_t id, char *params) { } static void commandJoin(size_t id, char *params) { + size_t count = 1; + if (params) { + for (char *ch = params; *ch && *ch != ' '; ++ch) { + if (*ch == ',') count++; + } + } ircFormat("JOIN %s\r\n", (params ? params : idNames[id])); + replies.topic += count; + replies.names += count; } static void commandPart(size_t id, char *params) { diff --git a/handle.c b/handle.c index 8ebc3b1..0780767 100644 --- a/handle.c +++ b/handle.c @@ -25,6 +25,8 @@ #include "chat.h" +struct Replies replies; + static const char *CapNames[] = { #define X(name, id) [id##Bit] = name, ENUM_CAP @@ -156,7 +158,15 @@ static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); completeTouch(None, self.nick, Default); - if (self.join) ircFormat("JOIN %s\r\n", self.join); + if (self.join) { + size_t count = 1; + for (const char *ch = self.join; *ch && *ch != ' '; ++ch) { + if (*ch == ',') count++; + } + ircFormat("JOIN %s\r\n", self.join); + replies.topic += count; + replies.names += count; + } } static void handleReplyISupport(struct Message *msg) { @@ -278,6 +288,7 @@ static void handleQuit(struct Message *msg) { static void handleReplyNames(struct Message *msg) { require(msg, false, 4); + if (!replies.names) return; size_t id = idFor(msg->params[2]); char buf[1024]; size_t len = 0; @@ -302,8 +313,15 @@ static void handleReplyNames(struct Message *msg) { ); } +static void handleReplyEndOfNames(struct Message *msg) { + (void)msg; + if (replies.names) replies.names--; +} + static void handleReplyNoTopic(struct Message *msg) { require(msg, false, 2); + if (!replies.topic) return; + replies.topic--; uiFormat( idFor(msg->params[1]), Cold, tagTime(msg), "There is no sign in \3%02d%s\3", @@ -313,6 +331,8 @@ static void handleReplyNoTopic(struct Message *msg) { static void handleReplyTopic(struct Message *msg) { require(msg, false, 3); + if (!replies.topic) return; + replies.topic--; uiFormat( idFor(msg->params[1]), Cold, tagTime(msg), "The sign in \3%02d%s\3 reads: %s", @@ -421,6 +441,7 @@ static const struct Handler { { "331", handleReplyNoTopic }, { "332", handleReplyTopic }, { "353", handleReplyNames }, + { "366", handleReplyEndOfNames }, { "372", handleReplyMOTD }, { "432", handleErrorErroneousNickname }, { "433", handleErrorNicknameInUse }, -- cgit 1.4.1 From f502260dd0aa73b09bfbb7289b50a67592866166 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 18:29:01 -0500 Subject: Scan messages for URLs --- Makefile | 1 + catgirl.1 | 9 ++++++ chat.h | 4 +++ command.c | 11 ++++++++ handle.c | 15 ++++++++-- url.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 url.c (limited to 'handle.c') diff --git a/Makefile b/Makefile index 48aba7b..bcbb0d8 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ OBJS += edit.o OBJS += handle.o OBJS += irc.o OBJS += ui.o +OBJS += url.o dev: tags all diff --git a/catgirl.1 b/catgirl.1 index 5394d33..f489d07 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -156,6 +156,15 @@ Close the named, numbered or current window. Toggle logging in the .Sy window. +.It Ic /open Op Ar count +Open each of +.Ar count +most recent URLs. +.It Ic /open Ar nick | substring +Open the most recent URL from +.Ar nick +or matching +.Ar substring . .It Ic /window Ar name Switch to window by name. .It Ic /window Ar num , Ic / Ns Ar num diff --git a/chat.h b/chat.h index 909527e..583107a 100644 --- a/chat.h +++ b/chat.h @@ -169,6 +169,10 @@ void completeClear(size_t id); size_t completeID(const char *str); enum Color completeColor(size_t id, const char *str); +void urlScan(size_t id, const char *nick, const char *mesg); +void urlOpenCount(size_t id, size_t count); +void urlOpenMatch(size_t id, const char *str); + FILE *configOpen(const char *path, const char *mode); int getopt_config( int argc, char *const *argv, diff --git a/command.c b/command.c index eaabc9c..4100928 100644 --- a/command.c +++ b/command.c @@ -144,6 +144,16 @@ static void commandClose(size_t id, char *params) { } } +static void commandOpen(size_t id, char *params) { + if (!params) { + urlOpenCount(id, 1); + } else if (isdigit(params[0])) { + urlOpenCount(id, strtoul(params, NULL, 10)); + } else { + urlOpenMatch(id, params); + } +} + static const struct Handler { const char *cmd; Command *fn; @@ -155,6 +165,7 @@ static const struct Handler { { "/names", commandNames }, { "/nick", commandNick }, { "/notice", commandNotice }, + { "/open", commandOpen }, { "/part", commandPart }, { "/query", commandQuery }, { "/quit", commandQuit }, diff --git a/handle.c b/handle.c index 0780767..f919fcb 100644 --- a/handle.c +++ b/handle.c @@ -193,6 +193,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); if (!strncmp(line, "- ", 2)) { uiFormat(Network, Cold, tagTime(msg), "\3%d-\3\t%s", Gray, &line[2]); } else { @@ -227,6 +228,7 @@ static void handlePart(struct Message *msg) { completeClear(id); } completeRemove(id, msg->nick); + urlScan(id, msg->nick, msg->params[1]); uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tleaves \3%02d%s\3%s%s", @@ -241,6 +243,7 @@ static void handleKick(struct Message *msg) { size_t id = idFor(msg->params[0]); bool kicked = self.nick && !strcmp(msg->params[1], self.nick); completeTouch(id, msg->nick, hash(msg->user)); + urlScan(id, msg->nick, msg->params[2]); uiFormat( id, (kicked ? Hot : Cold), tagTime(msg), "%s\3%02d%s\17\tkicks \3%02d%s\3 out of \3%02d%s\3%s%s", @@ -275,6 +278,7 @@ static void handleQuit(struct Message *msg) { require(msg, true, 0); size_t id; while (None != (id = completeID(msg->nick))) { + urlScan(id, msg->nick, msg->params[0]); uiFormat( id, Cold, tagTime(msg), "\3%02d%s\3\tleaves%s%s", @@ -333,8 +337,10 @@ static void handleReplyTopic(struct Message *msg) { require(msg, false, 3); if (!replies.topic) return; replies.topic--; + size_t id = idFor(msg->params[1]); + urlScan(id, NULL, msg->params[2]); uiFormat( - idFor(msg->params[1]), Cold, tagTime(msg), + id, Cold, tagTime(msg), "The sign in \3%02d%s\3 reads: %s", hash(msg->params[1]), msg->params[1], msg->params[2] ); @@ -342,16 +348,18 @@ static void handleReplyTopic(struct Message *msg) { static void handleTopic(struct Message *msg) { require(msg, true, 2); + size_t id = idFor(msg->params[0]); if (msg->params[1][0]) { + urlScan(id, msg->nick, msg->params[1]); uiFormat( - idFor(msg->params[0]), Warm, tagTime(msg), + 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] ); } else { uiFormat( - idFor(msg->params[0]), Warm, tagTime(msg), + id, Warm, tagTime(msg), "\3%02d%s\3\tremoves the sign in \3%02d%s\3", hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] ); @@ -400,6 +408,7 @@ static void handlePrivmsg(struct Message *msg) { bool action = isAction(msg); bool mention = !mine && isMention(msg); if (!notice && !mine) completeTouch(id, msg->nick, hash(msg->user)); + urlScan(id, msg->nick, msg->params[1]); if (notice) { uiFormat( id, Warm, tagTime(msg), diff --git a/url.c b/url.c new file mode 100644 index 0000000..7790461 --- /dev/null +++ b/url.c @@ -0,0 +1,96 @@ +/* Copyright (C) 2020 C. McEnroe + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +static const char *Pattern = { + "(" + "cvs|" + "ftp|" + "git|" + "gopher|" + "http|" + "https|" + "irc|" + "ircs|" + "magnet|" + "sftp|" + "ssh|" + "svn|" + "telnet|" + "vnc" + ")" + ":[^[:space:]>\"]+" +}; +static regex_t Regex; + +static void compile(void) { + static bool compiled; + if (compiled) return; + compiled = true; + int error = regcomp(&Regex, Pattern, REG_EXTENDED); + if (!error) return; + char buf[256]; + regerror(error, &Regex, buf, sizeof(buf)); + errx(EX_SOFTWARE, "regcomp: %s: %s", buf, Pattern); +} + +enum { Cap = 32 }; +static struct { + size_t ids[Cap]; + char *nicks[Cap]; + char *urls[Cap]; + size_t len; +} ring; + +static void push(size_t id, const char *nick, const char *url, size_t len) { + size_t i = ring.len++ % Cap; + free(ring.nicks[i]); + free(ring.urls[i]); + ring.ids[i] = id; + ring.nicks[i] = NULL; + if (nick) { + ring.nicks[i] = strdup(nick); + if (!ring.nicks[i]) err(EX_OSERR, "strdup"); + } + ring.urls[i] = strndup(url, len); + if (!ring.urls[i]) err(EX_OSERR, "strndup"); +} + +void urlScan(size_t id, const char *nick, const char *mesg) { + if (!mesg) return; + compile(); + regmatch_t match = {0}; + for (const char *ptr = mesg; *ptr; ptr += match.rm_eo) { + if (regexec(&Regex, ptr, 1, &match, 0)) break; + push(id, nick, &ptr[match.rm_so], match.rm_eo - match.rm_so); + } +} + +void urlOpenCount(size_t id, size_t count) { + // TODO +} + +void urlOpenMatch(size_t id, const char *str) { + // TODO +} -- cgit 1.4.1 From 9cbec9ca7ee5a730f92587274bb6d7713c5671bf Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 22:51:13 -0500 Subject: Color mentions Sort of like Textual does, but only in the first part of the messaage, either before a colon or before a space. Hopefully this makes it less costly than it would be, and prevents false positives on people with common nouns for nicks. --- handle.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index f919fcb..4ec0a90 100644 --- a/handle.c +++ b/handle.c @@ -389,6 +389,53 @@ static bool isMention(const struct Message *msg) { return false; } +static const char *colorMentions(size_t id, struct Message *msg) { + char *mention; + char final; + if (strchr(msg->params[1], ':')) { + mention = strsep(&msg->params[1], ":"); + final = ':'; + } else if (strchr(msg->params[1], ' ')) { + mention = strsep(&msg->params[1], " "); + final = ' '; + } else { + mention = msg->params[1]; + msg->params[1] = ""; + final = '\0'; + } + + static char buf[1024]; + size_t len = 0; + while (*mention) { + size_t skip = strspn(mention, ", "); + int n = snprintf( + &buf[len], sizeof(buf) - len, + "%.*s", (int)skip, mention + ); + assert(n >= 0 && len + n < sizeof(buf)); + len += n; + mention += skip; + + size_t word = strcspn(mention, ", "); + char punct = mention[word]; + mention[word] = '\0'; + + n = snprintf( + &buf[len], sizeof(buf) - len, + "\3%02d%s\3", completeColor(id, mention), mention + ); + assert(n > 0 && len + n < sizeof(buf)); + len += n; + + mention[word] = punct; + mention += word; + } + assert(len + 1 < sizeof(buf)); + buf[len++] = final; + buf[len] = '\0'; + return buf; +} + static void handlePrivmsg(struct Message *msg) { require(msg, true, 2); bool query = !strchr(self.chanTypes, msg->params[0][0]); @@ -423,10 +470,12 @@ static void handlePrivmsg(struct Message *msg) { (mention ? "\26" : ""), hash(msg->user), msg->nick, msg->params[1] ); } else { + const char *mentions = colorMentions(id, msg); uiFormat( id, (mention || query ? Hot : Warm), tagTime(msg), - "%s\3%d<%s>\17\t%s", - (mention ? "\26" : ""), hash(msg->user), msg->nick, msg->params[1] + "%s\3%d<%s>\17\t%s%s", + (mention ? "\26" : ""), hash(msg->user), msg->nick, + mentions, msg->params[1] ); } } -- cgit 1.4.1 From ec73174c4c90ada7cad983f91ce42f27faa9749c Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 22:55:11 -0500 Subject: Use unexpected NAMES replies to populate complete --- handle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 4ec0a90..b4601e4 100644 --- a/handle.c +++ b/handle.c @@ -292,7 +292,6 @@ static void handleQuit(struct Message *msg) { static void handleReplyNames(struct Message *msg) { require(msg, false, 4); - if (!replies.names) return; size_t id = idFor(msg->params[2]); char buf[1024]; size_t len = 0; @@ -303,6 +302,7 @@ static void handleReplyNames(struct Message *msg) { char *user = strsep(&name, "@"); enum Color color = (user ? hash(user) : Default); completeAdd(id, nick, color); + if (!replies.names) continue; int n = snprintf( &buf[len], sizeof(buf) - len, "%s\3%02d%s\3", (len ? ", " : ""), color, nick @@ -310,6 +310,7 @@ static void handleReplyNames(struct Message *msg) { assert(n > 0 && len + n < sizeof(buf)); len += n; } + if (!replies.names) return; uiFormat( id, Cold, tagTime(msg), "In \3%02d%s\3 are %s", -- cgit 1.4.1 From b30b93f67212c8d4c172972fd3399cabb8d1be6e Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sat, 8 Feb 2020 23:29:32 -0500 Subject: Use fmemopen to build colored mentions string --- handle.c | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index b4601e4..cf0e853 100644 --- a/handle.c +++ b/handle.c @@ -406,34 +406,25 @@ static const char *colorMentions(size_t id, struct Message *msg) { } static char buf[1024]; - size_t len = 0; + FILE *str = fmemopen(buf, sizeof(buf), "w"); + if (!str) err(EX_OSERR, "fmemopen"); + while (*mention) { size_t skip = strspn(mention, ", "); - int n = snprintf( - &buf[len], sizeof(buf) - len, - "%.*s", (int)skip, mention - ); - assert(n >= 0 && len + n < sizeof(buf)); - len += n; + fwrite(mention, skip, 1, str); mention += skip; - size_t word = strcspn(mention, ", "); - char punct = mention[word]; - mention[word] = '\0'; - - n = snprintf( - &buf[len], sizeof(buf) - len, - "\3%02d%s\3", completeColor(id, mention), mention - ); - assert(n > 0 && len + n < sizeof(buf)); - len += n; - - mention[word] = punct; - mention += word; + size_t len = strcspn(mention, ", "); + char punct = mention[len]; + mention[len] = '\0'; + fprintf(str, "\3%02d%s\3", completeColor(id, mention), mention); + mention[len] = punct; + mention += len; } - assert(len + 1 < sizeof(buf)); - buf[len++] = final; - buf[len] = '\0'; + fputc(final, str); + + fclose(str); + buf[sizeof(buf) - 1] = '\0'; return buf; } -- cgit 1.4.1 From a212a7ae2c93092068c8a9c483c4575cc65e7491 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 00:53:55 -0500 Subject: Show realname on JOIN if it is different from nick --- catgirl.1 | 10 +++++++++- chat.h | 1 + handle.c | 11 +++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) (limited to 'handle.c') diff --git a/catgirl.1 b/catgirl.1 index 4dabb4f..fd00105 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -1,4 +1,4 @@ -.Dd February 8, 2020 +.Dd February 9, 2020 .Dt CATGIRL 1 .Os . @@ -287,6 +287,14 @@ join = #ascii.town .Bl -item .It .Rs +.%A Kiyoshi Aman +.%T IRCv3.1 extended-join Extension +.%I IRCv3 Working Group +.%U https://ircv3.net/specs/extensions/extended-join-3.1 +.Re +. +.It +.Rs .%A Waldo Bastian .%A Ryan Lortie .%A Lennart Poettering diff --git a/chat.h b/chat.h index 8bc8e81..896549e 100644 --- a/chat.h +++ b/chat.h @@ -59,6 +59,7 @@ static inline size_t idFor(const char *name) { } #define ENUM_CAP \ + X("extended-join", CapExtendedJoin) \ X("sasl", CapSASL) \ X("server-time", CapServerTime) \ X("userhost-in-names", CapUserhostInNames) diff --git a/handle.c b/handle.c index cf0e853..0297595 100644 --- a/handle.c +++ b/handle.c @@ -214,10 +214,17 @@ static void handleJoin(struct Message *msg) { uiShowID(id); } completeTouch(id, msg->nick, hash(msg->user)); + if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) { + msg->params[2] = NULL; + } uiFormat( id, Cold, tagTime(msg), - "\3%02d%s\3\tarrives in \3%02d%s\3", - hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0] + "\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] ? ") " : ""), + hash(msg->params[0]), msg->params[0] ); } -- cgit 1.4.1 From 82cf44585831ea5a237f3b603a9d8ffd2d350b54 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 02:16:17 -0500 Subject: Add self.nick to completion in Network, not None --- handle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 0297595..2cc7a25 100644 --- a/handle.c +++ b/handle.c @@ -157,7 +157,7 @@ static void handleErrorSASLFail(struct Message *msg) { static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); - completeTouch(None, self.nick, Default); + completeTouch(Network, self.nick, Default); if (self.join) { size_t count = 1; for (const char *ch = self.join; *ch && *ch != ' '; ++ch) { -- cgit 1.4.1 From 3436cd1068ca37cf4043bc8dc83e3b8890edcb2b Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 9 Feb 2020 16:45:49 -0500 Subject: Add /whois --- catgirl.1 | 2 ++ chat.h | 1 + command.c | 8 +++++ handle.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+) (limited to 'handle.c') diff --git a/catgirl.1 b/catgirl.1 index 8679f22..ac558a9 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -166,6 +166,8 @@ Quit IRC. Send a raw IRC command. .It Ic /topic Op Ar topic Show or set the topic of the channel. +.It Ic /whois Ar nick +Query information about a user. .El . .Ss UI Commands diff --git a/chat.h b/chat.h index 24360f0..f79cc70 100644 --- a/chat.h +++ b/chat.h @@ -120,6 +120,7 @@ void ircFormat(const char *format, ...) extern struct Replies { size_t topic; size_t names; + size_t whois; } replies; void handle(struct Message msg); diff --git a/command.c b/command.c index 6d9ef9b..3e201cc 100644 --- a/command.c +++ b/command.c @@ -124,6 +124,13 @@ static void commandNames(size_t id, char *params) { replies.names++; } +static void commandWhois(size_t id, char *params) { + (void)id; + if (!params) return; + ircFormat("WHOIS :%s\r\n", params); + replies.whois++; +} + static void commandQuery(size_t id, char *params) { if (!params) return; size_t query = idFor(params); @@ -203,6 +210,7 @@ static const struct Handler { { "/quit", commandQuit }, { "/quote", commandQuote }, { "/topic", commandTopic }, + { "/whois", commandWhois }, { "/window", commandWindow }, }; diff --git a/handle.c b/handle.c index 2cc7a25..2ef2477 100644 --- a/handle.c +++ b/handle.c @@ -374,6 +374,97 @@ static void handleTopic(struct Message *msg) { } } +static void handleReplyWhoisUser(struct Message *msg) { + require(msg, false, 6); + if (!replies.whois) return; + completeTouch(Network, msg->params[1], hash(msg->params[2])); + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\tis %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 handleReplyWhoisServer(struct Message *msg) { + require(msg, false, 4); + if (!replies.whois) return; + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\tis connected to %s (%s)", + completeColor(Network, msg->params[1]), msg->params[1], + 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) { idle /= 60; unit = "minute"; } + if (idle / 60) { idle /= 60; unit = "hour"; } + if (idle / 24) { idle /= 24; unit = "day"; } + time_t signon = (msg->params[3] ? strtoul(msg->params[3], NULL, 10) : 0); + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\tis idle for %lu %s%s%s%.*s", + completeColor(Network, msg->params[1]), msg->params[1], + idle, unit, (idle != 1 ? "s" : ""), + (signon ? ", signed on " : ""), + 24, (signon ? ctime(&signon) : "") + ); +} + +static void handleReplyWhoisChannels(struct Message *msg) { + require(msg, false, 3); + if (!replies.whois) return; + char buf[1024]; + size_t len = 0; + while (msg->params[2]) { + char *channel = strsep(&msg->params[2], " "); + channel += strspn(channel, self.prefixes); + int n = snprintf( + &buf[len], sizeof(buf) - len, + "%s\3%02d%s\3", (len ? ", " : ""), hash(channel), channel + ); + assert(n > 0 && len + n < sizeof(buf)); + len += n; + } + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\tis in %s", + completeColor(Network, msg->params[1]), msg->params[1], buf + ); +} + +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]; + msg->params[3] = msg->params[0]; + } + uiFormat( + Network, Warm, tagTime(msg), + "\3%02d%s\3\t%s%s%s", + completeColor(Network, msg->params[1]), msg->params[1], + msg->params[2], + (msg->params[3] ? " " : ""), + (msg->params[3] ? msg->params[3] : "") + ); +} + +static void handleReplyEndOfWhois(struct Message *msg) { + require(msg, false, 2); + if (!replies.whois) return; + if (!self.nick || strcmp(msg->params[1], self.nick)) { + completeRemove(Network, msg->params[1]); + } + replies.whois--; +} + static bool isAction(struct Message *msg) { if (strncmp(msg->params[1], "\1ACTION ", 8)) return false; msg->params[1] += 8; @@ -495,6 +586,15 @@ static const struct Handler { } Handlers[] = { { "001", handleReplyWelcome }, { "005", handleReplyISupport }, + { "276", handleReplyWhoisGeneric }, + { "307", handleReplyWhoisGeneric }, + { "311", handleReplyWhoisUser }, + { "312", handleReplyWhoisServer }, + { "313", handleReplyWhoisGeneric }, + { "317", handleReplyWhoisIdle }, + { "318", handleReplyEndOfWhois }, + { "319", handleReplyWhoisChannels }, + { "330", handleReplyWhoisGeneric }, { "331", handleReplyNoTopic }, { "332", handleReplyTopic }, { "353", handleReplyNames }, @@ -502,6 +602,7 @@ static const struct Handler { { "372", handleReplyMOTD }, { "432", handleErrorErroneousNickname }, { "433", handleErrorNicknameInUse }, + { "671", handleReplyWhoisGeneric }, { "900", handleReplyLoggedIn }, { "904", handleErrorSASLFail }, { "905", handleErrorSASLFail }, -- cgit 1.4.1 From fabb89077d445e6b682f0e38305de7387d07af24 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 01:29:30 -0500 Subject: Update prompt when own nick changes --- handle.c | 1 + 1 file changed, 1 insertion(+) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 2ef2477..708acc7 100644 --- a/handle.c +++ b/handle.c @@ -269,6 +269,7 @@ static void handleNick(struct Message *msg) { require(msg, true, 1); if (self.nick && !strcmp(msg->nick, self.nick)) { set(&self.nick, msg->params[0]); + uiRead(); // Update prompt. } size_t id; while (None != (id = completeID(msg->nick))) { -- cgit 1.4.1 From 8e55c049b50e7a08dae819a5d7f704bdfaf4966c Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 03:58:00 -0500 Subject: Avoid coloring mentions if there are control codes This was breaking leading color codes. --- handle.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 708acc7..fd2a67f 100644 --- a/handle.c +++ b/handle.c @@ -490,19 +490,16 @@ static bool isMention(const struct Message *msg) { } static const char *colorMentions(size_t id, struct Message *msg) { - char *mention; - char final; - if (strchr(msg->params[1], ':')) { - mention = strsep(&msg->params[1], ":"); - final = ':'; - } else if (strchr(msg->params[1], ' ')) { - mention = strsep(&msg->params[1], " "); - final = ' '; - } else { - mention = msg->params[1]; - msg->params[1] = ""; - final = '\0'; + char *split = strchr(msg->params[1], ':'); + if (!split) split = strchr(msg->params[1], ' '); + if (!split) split = &msg->params[1][strlen(msg->params[1])]; + for (char *ch = msg->params[1]; ch < split; ++ch) { + if (iscntrl(*ch)) return ""; } + char delimit = *split; + char *mention = msg->params[1]; + msg->params[1] = (delimit ? &split[1] : split); + *split = '\0'; static char buf[1024]; FILE *str = fmemopen(buf, sizeof(buf), "w"); @@ -520,7 +517,7 @@ static const char *colorMentions(size_t id, struct Message *msg) { mention[len] = punct; mention += len; } - fputc(final, str); + fputc(delimit, str); fclose(str); buf[sizeof(buf) - 1] = '\0'; -- cgit 1.4.1 From 80a79467efca8f17e440cb63009c60dd8e78cc63 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 20:24:07 -0500 Subject: Only automatically switch to expected joins --- chat.h | 1 + command.c | 1 + handle.c | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'handle.c') diff --git a/chat.h b/chat.h index 03a0a50..f47b244 100644 --- a/chat.h +++ b/chat.h @@ -120,6 +120,7 @@ void ircFormat(const char *format, ...) __attribute__((format(printf, 1, 2))); extern struct Replies { + size_t join; size_t topic; size_t names; size_t whois; diff --git a/command.c b/command.c index cab1d26..5cb43cf 100644 --- a/command.c +++ b/command.c @@ -86,6 +86,7 @@ static void commandJoin(size_t id, char *params) { } } ircFormat("JOIN %s\r\n", (params ? params : idNames[id])); + replies.join += count; replies.topic += count; replies.names += count; } diff --git a/handle.c b/handle.c index fd2a67f..0db7fd9 100644 --- a/handle.c +++ b/handle.c @@ -164,6 +164,7 @@ static void handleReplyWelcome(struct Message *msg) { if (*ch == ',') count++; } ircFormat("JOIN %s\r\n", self.join); + replies.join += count; replies.topic += count; replies.names += count; } @@ -211,7 +212,10 @@ static void handleJoin(struct Message *msg) { } idColors[id] = hash(msg->params[0]); completeTouch(None, msg->params[0], idColors[id]); - uiShowID(id); + if (replies.join) { + uiShowID(id); + replies.join--; + } } completeTouch(id, msg->nick, hash(msg->user)); if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) { -- cgit 1.4.1 From 36e0bbc4cd783a826313de57fe77edf35e912d49 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Mon, 10 Feb 2020 20:58:14 -0500 Subject: Split on <> in colorMentions This allows it to color the nick in the common case of pasting " something they said" into the chat. Technically it should color the brackets too but that would be too much work. --- handle.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'handle.c') diff --git a/handle.c b/handle.c index 0db7fd9..ce56a51 100644 --- a/handle.c +++ b/handle.c @@ -510,11 +510,11 @@ static const char *colorMentions(size_t id, struct Message *msg) { if (!str) err(EX_OSERR, "fmemopen"); while (*mention) { - size_t skip = strspn(mention, ", "); + size_t skip = strspn(mention, ",<> "); fwrite(mention, skip, 1, str); mention += skip; - size_t len = strcspn(mention, ", "); + size_t len = strcspn(mention, ",<> "); char punct = mention[len]; mention[len] = '\0'; fprintf(str, "\3%02d%s\3", completeColor(id, mention), mention); -- cgit 1.4.1