2020-02-01 06:18:01 +00:00
|
|
|
/* Copyright (C) 2020 C. McEnroe <june@causal.agency>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* 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 <https://www.gnu.org/licenses/>.
|
2020-06-08 21:48:07 +00:00
|
|
|
*
|
|
|
|
* Additional permission under GNU GPL version 3 section 7:
|
|
|
|
*
|
|
|
|
* If you modify this Program, or any covered work, by linking or
|
2020-08-04 16:19:14 +00:00
|
|
|
* combining it with OpenSSL (or a modified version of that library),
|
2020-06-08 21:48:07 +00:00
|
|
|
* containing parts covered by the terms of the OpenSSL License and the
|
|
|
|
* original SSLeay license, the licensors of this Program grant you
|
|
|
|
* additional permission to convey the resulting work. Corresponding
|
|
|
|
* Source for a non-source form of such a combination shall include the
|
2020-08-04 16:19:14 +00:00
|
|
|
* source code for the parts of OpenSSL used as well as that of the
|
2020-06-08 21:48:07 +00:00
|
|
|
* covered work.
|
2020-02-01 06:18:01 +00:00
|
|
|
*/
|
|
|
|
|
2020-02-08 05:25:09 +00:00
|
|
|
#include <assert.h>
|
2020-02-06 09:18:15 +00:00
|
|
|
#include <ctype.h>
|
2020-02-01 06:18:01 +00:00
|
|
|
#include <err.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sysexits.h>
|
2020-11-08 17:42:01 +00:00
|
|
|
#include <wchar.h>
|
2020-02-01 06:18:01 +00:00
|
|
|
|
|
|
|
#include "chat.h"
|
|
|
|
|
2020-12-30 21:49:55 +00:00
|
|
|
uint replies[ReplyCap];
|
2020-02-08 08:15:17 +00:00
|
|
|
|
2020-02-01 06:18:01 +00:00
|
|
|
static const char *CapNames[] = {
|
2020-02-01 07:19:55 +00:00
|
|
|
#define X(name, id) [id##Bit] = name,
|
2020-02-01 06:18:01 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-06-07 04:09:58 +00:00
|
|
|
static void capList(char *buf, size_t cap, enum Cap caps) {
|
|
|
|
*buf = '\0';
|
|
|
|
char *ptr = buf, *end = &buf[cap];
|
2020-02-01 06:18:01 +00:00
|
|
|
for (size_t i = 0; i < ARRAY_LEN(CapNames); ++i) {
|
|
|
|
if (caps & (1 << i)) {
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(
|
|
|
|
ptr, end, "%s%s", (ptr > buf ? " " : ""), CapNames[i]
|
|
|
|
);
|
2020-02-01 06:18:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-16 03:19:55 +00:00
|
|
|
static void require(struct Message *msg, bool origin, uint len) {
|
2020-02-02 08:13:50 +00:00
|
|
|
if (origin) {
|
2020-09-18 22:13:09 +00:00
|
|
|
if (!msg->nick) msg->nick = "*.*";
|
2020-02-02 08:13:50 +00:00
|
|
|
if (!msg->user) msg->user = msg->nick;
|
|
|
|
if (!msg->host) msg->host = msg->user;
|
2020-02-01 06:18:01 +00:00
|
|
|
}
|
2020-02-16 03:19:55 +00:00
|
|
|
for (uint i = 0; i < len; ++i) {
|
2020-02-01 06:18:01 +00:00
|
|
|
if (msg->params[i]) continue;
|
2020-02-16 03:19:55 +00:00
|
|
|
errx(EX_PROTOCOL, "%s missing parameter %u", msg->cmd, 1 + i);
|
2020-02-01 06:18:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-03 23:41:52 +00:00
|
|
|
static const time_t *tagTime(const struct Message *msg) {
|
|
|
|
static time_t time;
|
|
|
|
struct tm tm;
|
2020-02-02 06:58:03 +00:00
|
|
|
if (!msg->tags[TagTime]) return NULL;
|
2020-02-03 23:41:52 +00:00
|
|
|
if (!strptime(msg->tags[TagTime], "%FT%T", &tm)) return NULL;
|
|
|
|
time = timegm(&tm);
|
|
|
|
return &time;
|
2020-02-02 06:58:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
typedef void Handler(struct Message *msg);
|
|
|
|
|
2020-12-30 04:10:15 +00:00
|
|
|
static void handleStandardReply(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
|
|
|
for (uint i = 2; i < ParamCap - 1; ++i) {
|
|
|
|
if (msg->params[i + 1]) continue;
|
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
|
|
|
"%s", msg->params[i]
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-20 06:38:31 +00:00
|
|
|
static void handleErrorGeneric(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
|
|
|
if (msg->params[2]) {
|
|
|
|
size_t len = strlen(msg->params[2]);
|
|
|
|
if (msg->params[2][len - 1] == '.') msg->params[2][len - 1] = '\0';
|
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
|
|
|
"%s: %s", msg->params[2], msg->params[1]
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
|
|
|
"%s", msg->params[1]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 07:30:35 +00:00
|
|
|
static void handleErrorNicknameInUse(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
2020-02-20 06:38:31 +00:00
|
|
|
if (!strcmp(self.nick, "*")) {
|
|
|
|
ircFormat("NICK :%s_\r\n", msg->params[1]);
|
|
|
|
} else {
|
|
|
|
handleErrorGeneric(msg);
|
|
|
|
}
|
2020-02-02 07:30:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void handleErrorErroneousNickname(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
2020-02-13 00:30:07 +00:00
|
|
|
if (!strcmp(self.nick, "*")) {
|
|
|
|
errx(EX_CONFIG, "%s: %s", msg->params[1], msg->params[2]);
|
|
|
|
} else {
|
2020-02-20 06:38:31 +00:00
|
|
|
handleErrorGeneric(msg);
|
2020-02-13 00:30:07 +00:00
|
|
|
}
|
2020-02-02 07:30:35 +00:00
|
|
|
}
|
|
|
|
|
2020-02-01 06:18:01 +00:00
|
|
|
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;
|
2020-02-29 06:03:46 +00:00
|
|
|
if (caps & CapConsumer && self.pos) {
|
|
|
|
ircFormat("CAP REQ %s=%zu\r\n", CapNames[CapConsumerBit], self.pos);
|
|
|
|
caps &= ~CapConsumer;
|
|
|
|
}
|
2020-02-06 08:44:49 +00:00
|
|
|
if (caps) {
|
2021-06-07 04:09:58 +00:00
|
|
|
char buf[512];
|
|
|
|
capList(buf, sizeof(buf), caps);
|
|
|
|
ircFormat("CAP REQ :%s\r\n", buf);
|
2020-02-06 08:44:49 +00:00
|
|
|
} else {
|
|
|
|
if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n");
|
|
|
|
}
|
2020-02-01 06:18:01 +00:00
|
|
|
} 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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-11 22:40:08 +00:00
|
|
|
#define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4)
|
|
|
|
|
|
|
|
static void base64(char *dst, const byte *src, size_t len) {
|
|
|
|
static const char Base64[64] = {
|
|
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
|
|
};
|
|
|
|
size_t i = 0;
|
|
|
|
while (len > 2) {
|
|
|
|
dst[i++] = Base64[0x3F & (src[0] >> 2)];
|
|
|
|
dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)];
|
|
|
|
dst[i++] = Base64[0x3F & (src[1] << 2 | src[2] >> 6)];
|
|
|
|
dst[i++] = Base64[0x3F & src[2]];
|
|
|
|
src += 3;
|
|
|
|
len -= 3;
|
|
|
|
}
|
|
|
|
if (len) {
|
|
|
|
dst[i++] = Base64[0x3F & (src[0] >> 2)];
|
|
|
|
if (len > 1) {
|
|
|
|
dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)];
|
|
|
|
dst[i++] = Base64[0x3F & (src[1] << 2)];
|
|
|
|
} else {
|
|
|
|
dst[i++] = Base64[0x3F & (src[0] << 4)];
|
|
|
|
dst[i++] = '=';
|
|
|
|
}
|
|
|
|
dst[i++] = '=';
|
|
|
|
}
|
|
|
|
dst[i] = '\0';
|
|
|
|
}
|
|
|
|
|
2020-02-01 06:18:01 +00:00
|
|
|
static void handleAuthenticate(struct Message *msg) {
|
|
|
|
(void)msg;
|
|
|
|
if (!self.plain) {
|
|
|
|
ircFormat("AUTHENTICATE +\r\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-09 16:44:01 +00:00
|
|
|
byte buf[299] = {0};
|
2020-02-01 06:18:01 +00:00
|
|
|
size_t len = 1 + strlen(self.plain);
|
2021-09-09 16:44:01 +00:00
|
|
|
if (sizeof(buf) < len) errx(EX_USAGE, "SASL PLAIN is too long");
|
|
|
|
memcpy(&buf[1], self.plain, len - 1);
|
|
|
|
byte *sep = memchr(buf, ':', len);
|
|
|
|
if (!sep) errx(EX_USAGE, "SASL PLAIN missing colon");
|
|
|
|
*sep = 0;
|
2020-02-01 06:18:01 +00:00
|
|
|
|
|
|
|
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]);
|
2020-02-09 07:16:17 +00:00
|
|
|
completeTouch(Network, self.nick, Default);
|
2021-06-18 16:28:09 +00:00
|
|
|
if (self.mode) ircFormat("MODE %s %s\r\n", self.nick, self.mode);
|
2020-02-08 08:15:17 +00:00
|
|
|
if (self.join) {
|
2021-02-21 19:23:17 +00:00
|
|
|
uint count = 1;
|
|
|
|
for (const char *ch = self.join; *ch && *ch != ' '; ++ch) {
|
|
|
|
if (*ch == ',') count++;
|
2021-02-05 02:25:59 +00:00
|
|
|
}
|
2021-02-21 19:23:17 +00:00
|
|
|
ircFormat("JOIN %s\r\n", self.join);
|
|
|
|
if (count == 1) replies[ReplyJoin]++;
|
|
|
|
replies[ReplyTopicAuto] += count;
|
|
|
|
replies[ReplyNamesAuto] += count;
|
2020-02-08 08:15:17 +00:00
|
|
|
}
|
2020-02-01 06:18:01 +00:00
|
|
|
}
|
|
|
|
|
2020-02-02 00:37:48 +00:00
|
|
|
static void handleReplyISupport(struct Message *msg) {
|
2020-02-16 03:19:55 +00:00
|
|
|
for (uint i = 1; i < ParamCap; ++i) {
|
2020-02-02 00:37:48 +00:00
|
|
|
if (!msg->params[i]) break;
|
|
|
|
char *key = strsep(&msg->params[i], "=");
|
|
|
|
if (!strcmp(key, "NETWORK")) {
|
2020-02-25 07:24:29 +00:00
|
|
|
if (!msg->params[i]) continue;
|
2020-02-15 09:54:53 +00:00
|
|
|
set(&network.name, msg->params[i]);
|
2020-02-02 06:58:03 +00:00
|
|
|
uiFormat(
|
|
|
|
Network, Cold, tagTime(msg),
|
|
|
|
"You arrive in %s", msg->params[i]
|
|
|
|
);
|
2020-03-23 17:13:43 +00:00
|
|
|
} else if (!strcmp(key, "USERLEN")) {
|
|
|
|
if (!msg->params[i]) continue;
|
|
|
|
network.userLen = strtoul(msg->params[i], NULL, 10);
|
|
|
|
} else if (!strcmp(key, "HOSTLEN")) {
|
|
|
|
if (!msg->params[i]) continue;
|
|
|
|
network.hostLen = strtoul(msg->params[i], NULL, 10);
|
2020-02-02 22:37:36 +00:00
|
|
|
} else if (!strcmp(key, "CHANTYPES")) {
|
2020-02-25 07:24:29 +00:00
|
|
|
if (!msg->params[i]) continue;
|
2020-02-15 09:54:53 +00:00
|
|
|
set(&network.chanTypes, msg->params[i]);
|
2021-04-02 21:03:10 +00:00
|
|
|
} else if (!strcmp(key, "STATUSMSG")) {
|
|
|
|
if (!msg->params[i]) continue;
|
|
|
|
set(&network.statusmsg, msg->params[i]);
|
2020-02-02 22:37:36 +00:00
|
|
|
} else if (!strcmp(key, "PREFIX")) {
|
2020-02-15 10:29:54 +00:00
|
|
|
strsep(&msg->params[i], "(");
|
2020-04-05 17:11:19 +00:00
|
|
|
char *modes = strsep(&msg->params[i], ")");
|
|
|
|
char *prefixes = msg->params[i];
|
|
|
|
if (!modes || !prefixes || strlen(modes) != strlen(prefixes)) {
|
|
|
|
errx(EX_PROTOCOL, "invalid PREFIX value");
|
|
|
|
}
|
|
|
|
set(&network.prefixModes, modes);
|
|
|
|
set(&network.prefixes, prefixes);
|
2020-02-15 10:29:54 +00:00
|
|
|
} else if (!strcmp(key, "CHANMODES")) {
|
2020-04-05 17:11:19 +00:00
|
|
|
char *list = strsep(&msg->params[i], ",");
|
|
|
|
char *param = strsep(&msg->params[i], ",");
|
|
|
|
char *setParam = strsep(&msg->params[i], ",");
|
|
|
|
char *channel = strsep(&msg->params[i], ",");
|
|
|
|
if (!list || !param || !setParam || !channel) {
|
|
|
|
errx(EX_PROTOCOL, "invalid CHANMODES value");
|
|
|
|
}
|
|
|
|
set(&network.listModes, list);
|
|
|
|
set(&network.paramModes, param);
|
|
|
|
set(&network.setParamModes, setParam);
|
|
|
|
set(&network.channelModes, channel);
|
2020-02-16 23:31:50 +00:00
|
|
|
} else if (!strcmp(key, "EXCEPTS")) {
|
2020-04-03 21:10:52 +00:00
|
|
|
network.excepts = (msg->params[i] ?: "e")[0];
|
2020-02-16 23:31:50 +00:00
|
|
|
} else if (!strcmp(key, "INVEX")) {
|
2020-04-03 21:10:52 +00:00
|
|
|
network.invex = (msg->params[i] ?: "I")[0];
|
2020-02-02 00:37:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleReplyMOTD(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
|
|
|
char *line = msg->params[1];
|
2021-01-11 22:59:12 +00:00
|
|
|
urlScan(Network, NULL, line);
|
2020-02-05 05:24:54 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-02-02 06:58:03 +00:00
|
|
|
}
|
|
|
|
|
2020-03-09 07:08:59 +00:00
|
|
|
static void handleErrorNoMOTD(struct Message *msg) {
|
|
|
|
(void)msg;
|
|
|
|
}
|
|
|
|
|
2020-10-02 23:38:37 +00:00
|
|
|
static void handleReplyHelp(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
2021-01-11 22:59:12 +00:00
|
|
|
urlScan(Network, NULL, msg->params[2]);
|
2020-10-02 23:38:37 +00:00
|
|
|
uiWrite(Network, Warm, tagTime(msg), msg->params[2]);
|
|
|
|
}
|
|
|
|
|
2020-02-02 08:13:50 +00:00
|
|
|
static void handleJoin(struct Message *msg) {
|
|
|
|
require(msg, true, 1);
|
2020-02-16 03:19:55 +00:00
|
|
|
uint id = idFor(msg->params[0]);
|
2020-02-11 22:52:55 +00:00
|
|
|
if (!strcmp(msg->nick, self.nick)) {
|
2020-03-22 18:32:20 +00:00
|
|
|
if (!self.user || strcmp(self.user, msg->user)) {
|
2020-02-05 05:40:24 +00:00
|
|
|
set(&self.user, msg->user);
|
|
|
|
self.color = hash(msg->user);
|
|
|
|
}
|
2020-03-22 18:32:20 +00:00
|
|
|
if (!self.host || strcmp(self.host, msg->host)) {
|
|
|
|
set(&self.host, msg->host);
|
|
|
|
}
|
2020-02-02 08:27:50 +00:00
|
|
|
idColors[id] = hash(msg->params[0]);
|
2020-02-08 04:33:23 +00:00
|
|
|
completeTouch(None, msg->params[0], idColors[id]);
|
2020-12-30 21:49:55 +00:00
|
|
|
if (replies[ReplyJoin]) {
|
2020-02-11 01:24:07 +00:00
|
|
|
uiShowID(id);
|
2020-12-30 21:49:55 +00:00
|
|
|
replies[ReplyJoin]--;
|
2020-02-11 01:24:07 +00:00
|
|
|
}
|
2020-02-02 08:13:50 +00:00
|
|
|
}
|
2020-02-08 04:33:23 +00:00
|
|
|
completeTouch(id, msg->nick, hash(msg->user));
|
2020-02-09 05:53:55 +00:00
|
|
|
if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) {
|
|
|
|
msg->params[2] = NULL;
|
|
|
|
}
|
2020-02-02 08:13:50 +00:00
|
|
|
uiFormat(
|
2021-01-16 18:30:59 +00:00
|
|
|
id, filterCheck(Cold, id, msg), tagTime(msg),
|
2020-02-09 05:53:55 +00:00
|
|
|
"\3%02d%s\3\t%s%s%sarrives in \3%02d%s\3",
|
|
|
|
hash(msg->user), msg->nick,
|
|
|
|
(msg->params[2] ? "(" : ""),
|
2020-04-03 21:10:52 +00:00
|
|
|
(msg->params[2] ?: ""),
|
2021-04-28 01:06:16 +00:00
|
|
|
(msg->params[2] ? "\17) " : ""),
|
2020-02-09 05:53:55 +00:00
|
|
|
hash(msg->params[0]), msg->params[0]
|
2020-02-02 08:13:50 +00:00
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(id, tagTime(msg), "%s arrives in %s", msg->nick, msg->params[0]);
|
2020-02-02 08:13:50 +00:00
|
|
|
}
|
|
|
|
|
2020-03-22 18:32:20 +00:00
|
|
|
static void handleChghost(struct Message *msg) {
|
|
|
|
require(msg, true, 2);
|
|
|
|
if (strcmp(msg->nick, self.nick)) return;
|
|
|
|
if (!self.user || strcmp(self.user, msg->params[0])) {
|
|
|
|
set(&self.user, msg->params[0]);
|
|
|
|
self.color = hash(msg->params[0]);
|
|
|
|
}
|
|
|
|
if (!self.host || strcmp(self.host, msg->params[1])) {
|
|
|
|
set(&self.host, msg->params[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 06:16:35 +00:00
|
|
|
static void handlePart(struct Message *msg) {
|
|
|
|
require(msg, true, 1);
|
2020-02-16 03:19:55 +00:00
|
|
|
uint id = idFor(msg->params[0]);
|
2020-02-11 22:52:55 +00:00
|
|
|
if (!strcmp(msg->nick, self.nick)) {
|
2020-02-08 04:44:03 +00:00
|
|
|
completeClear(id);
|
|
|
|
}
|
2020-02-08 04:33:23 +00:00
|
|
|
completeRemove(id, msg->nick);
|
2021-01-16 18:30:59 +00:00
|
|
|
enum Heat heat = filterCheck(Cold, id, msg);
|
2020-07-10 02:43:35 +00:00
|
|
|
if (heat > Ice) urlScan(id, msg->nick, msg->params[1]);
|
2020-02-06 06:16:35 +00:00
|
|
|
uiFormat(
|
2020-07-10 02:43:35 +00:00
|
|
|
id, heat, tagTime(msg),
|
2020-02-06 06:16:35 +00:00
|
|
|
"\3%02d%s\3\tleaves \3%02d%s\3%s%s",
|
2020-02-06 07:07:39 +00:00
|
|
|
hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0],
|
2020-04-03 21:10:52 +00:00
|
|
|
(msg->params[1] ? ": " : ""), (msg->params[1] ?: "")
|
2020-02-06 06:16:35 +00:00
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s leaves %s%s%s",
|
|
|
|
msg->nick, msg->params[0],
|
2020-04-03 21:10:52 +00:00
|
|
|
(msg->params[1] ? ": " : ""), (msg->params[1] ?: "")
|
2020-03-25 22:58:48 +00:00
|
|
|
);
|
2020-02-06 06:16:35 +00:00
|
|
|
}
|
|
|
|
|
2020-02-08 05:58:17 +00:00
|
|
|
static void handleKick(struct Message *msg) {
|
|
|
|
require(msg, true, 2);
|
2020-02-16 03:19:55 +00:00
|
|
|
uint id = idFor(msg->params[0]);
|
2020-02-11 22:52:55 +00:00
|
|
|
bool kicked = !strcmp(msg->params[1], self.nick);
|
2020-02-08 05:58:17 +00:00
|
|
|
completeTouch(id, msg->nick, hash(msg->user));
|
2020-02-08 23:29:01 +00:00
|
|
|
urlScan(id, msg->nick, msg->params[2]);
|
2020-02-08 05:58:17 +00:00
|
|
|
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],
|
2020-04-03 21:10:52 +00:00
|
|
|
(msg->params[2] ? ": " : ""), (msg->params[2] ?: "")
|
2020-02-08 05:58:17 +00:00
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s kicks %s out of %s%s%s",
|
|
|
|
msg->nick, msg->params[1], msg->params[0],
|
2020-04-03 21:10:52 +00:00
|
|
|
(msg->params[2] ? ": " : ""), (msg->params[2] ?: "")
|
2020-03-25 22:58:48 +00:00
|
|
|
);
|
2020-02-08 05:58:17 +00:00
|
|
|
completeRemove(id, msg->params[1]);
|
|
|
|
if (kicked) completeClear(id);
|
|
|
|
}
|
|
|
|
|
2020-02-08 05:36:23 +00:00
|
|
|
static void handleNick(struct Message *msg) {
|
|
|
|
require(msg, true, 1);
|
2020-02-11 22:52:55 +00:00
|
|
|
if (!strcmp(msg->nick, self.nick)) {
|
2020-02-08 05:36:23 +00:00
|
|
|
set(&self.nick, msg->params[0]);
|
2020-02-10 06:29:30 +00:00
|
|
|
uiRead(); // Update prompt.
|
2020-02-08 05:36:23 +00:00
|
|
|
}
|
2020-03-30 23:44:45 +00:00
|
|
|
for (uint id; (id = completeID(msg->nick));) {
|
2020-02-11 23:23:04 +00:00
|
|
|
if (!strcmp(idNames[id], msg->nick)) {
|
|
|
|
set(&idNames[id], msg->params[0]);
|
|
|
|
}
|
2020-02-08 05:36:23 +00:00
|
|
|
uiFormat(
|
2021-01-16 18:30:59 +00:00
|
|
|
id, filterCheck(Cold, id, msg), tagTime(msg),
|
2020-02-08 05:36:23 +00:00
|
|
|
"\3%02d%s\3\tis now known as \3%02d%s\3",
|
|
|
|
hash(msg->user), msg->nick, hash(msg->user), msg->params[0]
|
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
if (id == Network) continue;
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s is now known as %s",
|
|
|
|
msg->nick, msg->params[0]
|
|
|
|
);
|
2020-02-08 05:36:23 +00:00
|
|
|
}
|
|
|
|
completeReplace(None, msg->nick, msg->params[0]);
|
|
|
|
}
|
|
|
|
|
2020-12-30 05:15:11 +00:00
|
|
|
static void handleSetname(struct Message *msg) {
|
|
|
|
require(msg, true, 1);
|
|
|
|
for (uint id; (id = completeID(msg->nick));) {
|
|
|
|
uiFormat(
|
2021-01-16 18:30:59 +00:00
|
|
|
id, filterCheck(Cold, id, msg), tagTime(msg),
|
2021-06-12 14:35:36 +00:00
|
|
|
"\3%02d%s\3\tis now known as \3%02d%s\3 (%s\17)",
|
2020-12-30 05:15:11 +00:00
|
|
|
hash(msg->user), msg->nick, hash(msg->user), msg->nick,
|
|
|
|
msg->params[0]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-08 05:36:23 +00:00
|
|
|
static void handleQuit(struct Message *msg) {
|
|
|
|
require(msg, true, 0);
|
2020-03-30 23:44:45 +00:00
|
|
|
for (uint id; (id = completeID(msg->nick));) {
|
2021-01-16 18:30:59 +00:00
|
|
|
enum Heat heat = filterCheck(Cold, id, msg);
|
2020-07-10 02:43:35 +00:00
|
|
|
if (heat > Ice) urlScan(id, msg->nick, msg->params[0]);
|
2020-02-08 05:36:23 +00:00
|
|
|
uiFormat(
|
2020-07-10 02:43:35 +00:00
|
|
|
id, heat, tagTime(msg),
|
2020-02-08 05:36:23 +00:00
|
|
|
"\3%02d%s\3\tleaves%s%s",
|
|
|
|
hash(msg->user), msg->nick,
|
2020-04-03 21:10:52 +00:00
|
|
|
(msg->params[0] ? ": " : ""), (msg->params[0] ?: "")
|
2020-02-08 05:36:23 +00:00
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
if (id == Network) continue;
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s leaves%s%s",
|
|
|
|
msg->nick,
|
2020-04-03 21:10:52 +00:00
|
|
|
(msg->params[0] ? ": " : ""), (msg->params[0] ?: "")
|
2020-03-25 22:58:48 +00:00
|
|
|
);
|
2020-02-08 05:36:23 +00:00
|
|
|
}
|
|
|
|
completeRemove(None, msg->nick);
|
|
|
|
}
|
|
|
|
|
2020-02-20 08:18:48 +00:00
|
|
|
static void handleInvite(struct Message *msg) {
|
|
|
|
require(msg, true, 2);
|
|
|
|
if (!strcmp(msg->params[0], self.nick)) {
|
2021-07-02 19:24:11 +00:00
|
|
|
set(&self.invited, msg->params[1]);
|
2020-02-20 08:18:48 +00:00
|
|
|
uiFormat(
|
2021-01-16 18:30:59 +00:00
|
|
|
Network, filterCheck(Hot, Network, msg), tagTime(msg),
|
2020-02-20 08:18:48 +00:00
|
|
|
"\3%02d%s\3\tinvites you to \3%02d%s\3",
|
|
|
|
hash(msg->user), msg->nick, hash(msg->params[1]), msg->params[1]
|
|
|
|
);
|
|
|
|
} else {
|
2020-03-25 22:58:48 +00:00
|
|
|
uint id = idFor(msg->params[1]);
|
2020-02-20 08:18:48 +00:00
|
|
|
uiFormat(
|
2020-03-25 22:58:48 +00:00
|
|
|
id, Cold, tagTime(msg),
|
2020-02-20 08:18:48 +00:00
|
|
|
"\3%02d%s\3\tinvites %s to \3%02d%s\3",
|
|
|
|
hash(msg->user), msg->nick,
|
|
|
|
msg->params[0],
|
|
|
|
hash(msg->params[1]), msg->params[1]
|
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s invites %s to %s",
|
|
|
|
msg->nick, msg->params[0], msg->params[1]
|
|
|
|
);
|
2020-02-20 08:18:48 +00:00
|
|
|
}
|
2020-02-20 06:52:58 +00:00
|
|
|
}
|
|
|
|
|
2020-02-20 08:18:48 +00:00
|
|
|
static void handleReplyInviting(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
|
|
|
struct Message invite = {
|
|
|
|
.nick = self.nick,
|
|
|
|
.user = self.user,
|
|
|
|
.cmd = "INVITE",
|
|
|
|
.params[0] = msg->params[1],
|
|
|
|
.params[1] = msg->params[2],
|
|
|
|
};
|
|
|
|
handleInvite(&invite);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleErrorUserOnChannel(struct Message *msg) {
|
2021-05-20 20:36:39 +00:00
|
|
|
require(msg, false, 3);
|
2020-02-20 08:18:48 +00:00
|
|
|
uint id = idFor(msg->params[2]);
|
2020-02-20 07:13:23 +00:00
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
id, Warm, tagTime(msg),
|
2020-02-20 08:18:48 +00:00
|
|
|
"\3%02d%s\3 is already in \3%02d%s\3",
|
|
|
|
completeColor(id, msg->params[1]), msg->params[1],
|
|
|
|
hash(msg->params[2]), msg->params[2]
|
2020-02-20 07:13:23 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-08 05:25:09 +00:00
|
|
|
static void handleReplyNames(struct Message *msg) {
|
|
|
|
require(msg, false, 4);
|
2020-02-16 03:19:55 +00:00
|
|
|
uint id = idFor(msg->params[2]);
|
2021-06-07 04:09:58 +00:00
|
|
|
char buf[1024];
|
|
|
|
char *ptr = buf, *end = &buf[sizeof(buf)];
|
2020-02-08 05:25:09 +00:00
|
|
|
while (msg->params[3]) {
|
|
|
|
char *name = strsep(&msg->params[3], " ");
|
2020-02-16 03:19:05 +00:00
|
|
|
char *prefixes = strsep(&name, "!");
|
|
|
|
char *nick = &prefixes[strspn(prefixes, network.prefixes)];
|
2020-02-08 05:25:09 +00:00
|
|
|
char *user = strsep(&name, "@");
|
|
|
|
enum Color color = (user ? hash(user) : Default);
|
|
|
|
completeAdd(id, nick, color);
|
2021-02-21 19:23:17 +00:00
|
|
|
if (!replies[ReplyNames] && !replies[ReplyNamesAuto]) continue;
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(
|
|
|
|
ptr, end, "%s\3%02d%s\3", (ptr > buf ? ", " : ""), color, prefixes
|
|
|
|
);
|
2020-02-08 05:25:09 +00:00
|
|
|
}
|
2021-06-07 04:09:58 +00:00
|
|
|
if (ptr == buf) return;
|
2020-02-08 05:25:09 +00:00
|
|
|
uiFormat(
|
2021-02-21 19:23:17 +00:00
|
|
|
id, (replies[ReplyNamesAuto] ? Cold : Warm), tagTime(msg),
|
2020-09-30 23:21:34 +00:00
|
|
|
"In \3%02d%s\3 are %s",
|
2020-02-08 05:25:09 +00:00
|
|
|
hash(msg->params[2]), msg->params[2], buf
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-02-21 19:23:17 +00:00
|
|
|
static void handleReplyEndOfNames(struct Message *msg) {
|
|
|
|
(void)msg;
|
|
|
|
if (replies[ReplyNamesAuto]) {
|
|
|
|
replies[ReplyNamesAuto]--;
|
|
|
|
} else if (replies[ReplyNames]) {
|
|
|
|
replies[ReplyNames]--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 04:09:58 +00:00
|
|
|
static struct {
|
|
|
|
char buf[1024];
|
|
|
|
char *ptr;
|
|
|
|
char *end;
|
|
|
|
} who = {
|
|
|
|
.ptr = who.buf,
|
|
|
|
.end = &who.buf[sizeof(who.buf)],
|
|
|
|
};
|
2020-09-30 23:21:34 +00:00
|
|
|
|
|
|
|
static void handleReplyWho(struct Message *msg) {
|
|
|
|
require(msg, false, 7);
|
2021-06-07 04:09:58 +00:00
|
|
|
if (who.ptr == who.buf) {
|
|
|
|
who.ptr = seprintf(
|
|
|
|
who.ptr, who.end, "The operators of \3%02d%s\3 are ",
|
2020-09-30 23:21:34 +00:00
|
|
|
hash(msg->params[1]), msg->params[1]
|
|
|
|
);
|
2020-09-30 21:52:39 +00:00
|
|
|
}
|
2020-09-30 23:21:34 +00:00
|
|
|
char *prefixes = &msg->params[6][1];
|
|
|
|
if (prefixes[0] == '*') prefixes++;
|
|
|
|
prefixes[strspn(prefixes, network.prefixes)] = '\0';
|
|
|
|
if (!prefixes[0] || prefixes[0] == '+') return;
|
2021-06-07 04:09:58 +00:00
|
|
|
who.ptr = seprintf(
|
|
|
|
who.ptr, who.end, "%s\3%02d%s%s\3%s",
|
|
|
|
(who.ptr[-1] == ' ' ? "" : ", "),
|
2020-09-30 23:21:34 +00:00
|
|
|
hash(msg->params[2]), prefixes, msg->params[5],
|
|
|
|
(msg->params[6][0] == 'H' ? "" : " (away)")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleReplyEndOfWho(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
2021-06-07 04:09:58 +00:00
|
|
|
uiWrite(idFor(msg->params[1]), Warm, tagTime(msg), who.buf);
|
|
|
|
who.ptr = who.buf;
|
2020-02-08 08:15:17 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 07:07:39 +00:00
|
|
|
static void handleReplyNoTopic(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
idFor(msg->params[1]), Warm, tagTime(msg),
|
2020-02-06 07:07:39 +00:00
|
|
|
"There is no sign in \3%02d%s\3",
|
|
|
|
hash(msg->params[1]), msg->params[1]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-07-13 14:22:33 +00:00
|
|
|
static void topicComplete(uint id, const char *topic) {
|
|
|
|
char buf[512];
|
|
|
|
const char *prev = complete(id, "/topic ");
|
|
|
|
if (prev) {
|
|
|
|
snprintf(buf, sizeof(buf), "%s", prev);
|
|
|
|
completeRemove(id, buf);
|
|
|
|
}
|
|
|
|
if (topic) {
|
|
|
|
snprintf(buf, sizeof(buf), "/topic %s", topic);
|
|
|
|
completeAdd(id, buf, Default);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 07:07:39 +00:00
|
|
|
static void handleReplyTopic(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
2020-07-13 14:22:33 +00:00
|
|
|
uint id = idFor(msg->params[1]);
|
|
|
|
topicComplete(id, msg->params[2]);
|
2021-02-21 19:23:17 +00:00
|
|
|
if (!replies[ReplyTopic] && !replies[ReplyTopicAuto]) return;
|
2020-02-08 23:29:01 +00:00
|
|
|
urlScan(id, NULL, msg->params[2]);
|
2020-02-06 07:07:39 +00:00
|
|
|
uiFormat(
|
2021-02-21 19:23:17 +00:00
|
|
|
id, (replies[ReplyTopicAuto] ? Cold : Warm), tagTime(msg),
|
2020-02-06 07:07:39 +00:00
|
|
|
"The sign in \3%02d%s\3 reads: %s",
|
|
|
|
hash(msg->params[1]), msg->params[1], msg->params[2]
|
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "The sign in %s reads: %s",
|
|
|
|
msg->params[1], msg->params[2]
|
|
|
|
);
|
2021-02-21 19:23:17 +00:00
|
|
|
if (replies[ReplyTopicAuto]) {
|
|
|
|
replies[ReplyTopicAuto]--;
|
|
|
|
} else {
|
|
|
|
replies[ReplyTopic]--;
|
|
|
|
}
|
2020-02-06 07:07:39 +00:00
|
|
|
}
|
|
|
|
|
2020-11-08 17:42:01 +00:00
|
|
|
static void swap(wchar_t *a, wchar_t *b) {
|
|
|
|
wchar_t x = *a;
|
|
|
|
*a = *b;
|
|
|
|
*b = x;
|
|
|
|
}
|
|
|
|
|
2020-02-06 07:07:39 +00:00
|
|
|
static void handleTopic(struct Message *msg) {
|
|
|
|
require(msg, true, 2);
|
2020-02-16 03:19:55 +00:00
|
|
|
uint id = idFor(msg->params[0]);
|
2020-11-08 17:42:01 +00:00
|
|
|
if (!msg->params[1][0]) {
|
2020-07-13 14:22:33 +00:00
|
|
|
topicComplete(id, NULL);
|
2020-02-06 07:07:39 +00:00
|
|
|
uiFormat(
|
2020-02-08 23:29:01 +00:00
|
|
|
id, Warm, tagTime(msg),
|
2020-02-06 07:07:39 +00:00
|
|
|
"\3%02d%s\3\tremoves the sign in \3%02d%s\3",
|
|
|
|
hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0]
|
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s removes the sign in %s",
|
|
|
|
msg->nick, msg->params[0]
|
|
|
|
);
|
2020-11-08 17:42:01 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char buf[1024];
|
2021-06-07 04:09:58 +00:00
|
|
|
char *ptr = buf, *end = &buf[sizeof(buf)];
|
2020-11-08 17:42:01 +00:00
|
|
|
const char *prev = complete(id, "/topic ");
|
|
|
|
completeReject();
|
|
|
|
if (prev) {
|
|
|
|
prev += 7;
|
|
|
|
} else {
|
|
|
|
goto plain;
|
|
|
|
}
|
|
|
|
|
|
|
|
wchar_t old[512];
|
|
|
|
wchar_t new[512];
|
|
|
|
if (swprintf(old, ARRAY_LEN(old), L"%s", prev) < 0) goto plain;
|
|
|
|
if (swprintf(new, ARRAY_LEN(new), L"%s", msg->params[1]) < 0) goto plain;
|
|
|
|
|
2021-05-23 17:26:19 +00:00
|
|
|
if (!hashBound) {
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(ptr, end, "%c%ls%c -> %c%ls%c", R, old, O, R, new, O);
|
2021-05-23 17:26:19 +00:00
|
|
|
goto plain;
|
|
|
|
}
|
|
|
|
|
2020-11-08 17:42:01 +00:00
|
|
|
size_t pre;
|
|
|
|
for (pre = 0; old[pre] && new[pre] && old[pre] == new[pre]; ++pre);
|
|
|
|
wchar_t *osuf = &old[wcslen(old)];
|
|
|
|
wchar_t *nsuf = &new[wcslen(new)];
|
|
|
|
while (osuf > &old[pre] && nsuf > &new[pre] && osuf[-1] == nsuf[-1]) {
|
|
|
|
osuf--;
|
|
|
|
nsuf--;
|
2020-02-06 07:07:39 +00:00
|
|
|
}
|
2020-11-08 17:42:01 +00:00
|
|
|
|
|
|
|
wchar_t nul = L'\0';
|
|
|
|
swap(&new[pre], &nul);
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(ptr, end, "%ls", new);
|
2020-11-08 17:42:01 +00:00
|
|
|
swap(&new[pre], &nul);
|
|
|
|
swap(osuf, &nul);
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(ptr, end, "\3%02d,%02d%ls", Default, Brown, &old[pre]);
|
2020-11-08 17:42:01 +00:00
|
|
|
swap(osuf, &nul);
|
|
|
|
swap(nsuf, &nul);
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(ptr, end, "\3%02d,%02d%ls", Default, Green, &new[pre]);
|
2020-11-08 17:42:01 +00:00
|
|
|
swap(nsuf, &nul);
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(ptr, end, "\3%02d,%02d%ls", Default, Default, nsuf);
|
2020-11-08 17:42:01 +00:00
|
|
|
|
|
|
|
plain:
|
|
|
|
topicComplete(id, msg->params[1]);
|
|
|
|
urlScan(id, msg->nick, msg->params[1]);
|
|
|
|
uiFormat(
|
|
|
|
id, Warm, tagTime(msg),
|
|
|
|
"\3%02d%s\3\tplaces a new sign in \3%02d%s\3: %s",
|
|
|
|
hash(msg->user), msg->nick, hash(msg->params[0]), msg->params[0],
|
2021-06-07 04:09:58 +00:00
|
|
|
(ptr > buf ? buf : msg->params[1])
|
2020-11-08 17:42:01 +00:00
|
|
|
);
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s places a new sign in %s: %s",
|
|
|
|
msg->nick, msg->params[0], msg->params[1]
|
|
|
|
);
|
2020-02-06 07:07:39 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 12:33:42 +00:00
|
|
|
static const char *UserModes[256] = {
|
|
|
|
['O'] = "local oper",
|
|
|
|
['i'] = "invisible",
|
|
|
|
['o'] = "oper",
|
|
|
|
['r'] = "registered",
|
|
|
|
['w'] = "wallops",
|
|
|
|
};
|
|
|
|
|
|
|
|
static void handleReplyUserModeIs(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
2021-06-07 04:09:58 +00:00
|
|
|
char buf[1024];
|
|
|
|
char *ptr = buf, *end = &buf[sizeof(buf)];
|
2020-03-18 12:33:42 +00:00
|
|
|
for (char *ch = msg->params[1]; *ch; ++ch) {
|
|
|
|
if (*ch == '+') continue;
|
2020-03-28 16:08:10 +00:00
|
|
|
const char *name = UserModes[(byte)*ch];
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(
|
|
|
|
ptr, end, ", +%c%s%s", *ch, (name ? " " : ""), (name ?: "")
|
|
|
|
);
|
2020-03-18 12:33:42 +00:00
|
|
|
}
|
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
2020-03-22 15:40:58 +00:00
|
|
|
"\3%02d%s\3\tis %s",
|
2021-06-07 04:09:58 +00:00
|
|
|
self.color, self.nick, (ptr > buf ? &buf[2] : "modeless")
|
2020-03-18 12:33:42 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-22 15:18:21 +00:00
|
|
|
static const char *ChanModes[256] = {
|
|
|
|
['a'] = "protected",
|
|
|
|
['h'] = "halfop",
|
2020-02-25 07:54:15 +00:00
|
|
|
['i'] = "invite-only",
|
|
|
|
['k'] = "key",
|
|
|
|
['l'] = "client limit",
|
|
|
|
['m'] = "moderated",
|
|
|
|
['n'] = "no external messages",
|
|
|
|
['o'] = "operator",
|
2020-03-22 15:18:21 +00:00
|
|
|
['q'] = "founder",
|
2020-02-25 07:54:15 +00:00
|
|
|
['s'] = "secret",
|
|
|
|
['t'] = "protected topic",
|
|
|
|
['v'] = "voice",
|
|
|
|
};
|
|
|
|
|
2020-03-22 15:40:58 +00:00
|
|
|
static void handleReplyChannelModeIs(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
|
|
|
uint param = 3;
|
2021-06-07 04:09:58 +00:00
|
|
|
char buf[1024];
|
|
|
|
char *ptr = buf, *end = &buf[sizeof(buf)];
|
2020-03-22 15:40:58 +00:00
|
|
|
for (char *ch = msg->params[2]; *ch; ++ch) {
|
|
|
|
if (*ch == '+') continue;
|
|
|
|
const char *name = ChanModes[(byte)*ch];
|
|
|
|
if (
|
|
|
|
strchr(network.paramModes, *ch) ||
|
|
|
|
strchr(network.setParamModes, *ch)
|
|
|
|
) {
|
|
|
|
assert(param < ParamCap);
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(
|
|
|
|
ptr, end, ", +%c%s%s %s",
|
2020-04-03 21:10:52 +00:00
|
|
|
*ch, (name ? " " : ""), (name ?: ""),
|
2020-03-28 16:08:10 +00:00
|
|
|
msg->params[param++]
|
2020-03-22 15:40:58 +00:00
|
|
|
);
|
|
|
|
} else {
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(
|
|
|
|
ptr, end, ", +%c%s%s",
|
2020-04-03 21:10:52 +00:00
|
|
|
*ch, (name ? " " : ""), (name ?: "")
|
2020-03-28 16:08:10 +00:00
|
|
|
);
|
2020-03-22 15:40:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
idFor(msg->params[1]), Warm, tagTime(msg),
|
2020-03-28 16:08:10 +00:00
|
|
|
"\3%02d%s\3\tis %s",
|
2020-03-22 15:40:58 +00:00
|
|
|
hash(msg->params[1]), msg->params[1],
|
2021-06-07 04:09:58 +00:00
|
|
|
(ptr > buf ? &buf[2] : "modeless")
|
2020-03-22 15:40:58 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-20 09:18:25 +00:00
|
|
|
static void handleMode(struct Message *msg) {
|
|
|
|
require(msg, true, 2);
|
2020-03-22 15:26:16 +00:00
|
|
|
|
2020-02-20 09:18:25 +00:00
|
|
|
if (!strchr(network.chanTypes, msg->params[0][0])) {
|
2020-03-22 15:26:16 +00:00
|
|
|
bool set = true;
|
|
|
|
for (char *ch = msg->params[1]; *ch; ++ch) {
|
|
|
|
if (*ch == '+') { set = true; continue; }
|
|
|
|
if (*ch == '-') { set = false; continue; }
|
|
|
|
const char *name = UserModes[(byte)*ch];
|
2020-03-28 16:08:10 +00:00
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
|
|
|
"\3%02d%s\3\t%ssets \3%02d%s\3 %c%c%s%s",
|
|
|
|
hash(msg->user), msg->nick,
|
|
|
|
(set ? "" : "un"),
|
|
|
|
self.color, msg->params[0],
|
2020-04-03 21:10:52 +00:00
|
|
|
set["-+"], *ch, (name ? " " : ""), (name ?: "")
|
2020-03-22 15:26:16 +00:00
|
|
|
);
|
|
|
|
}
|
2020-02-20 09:18:25 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-02-20 09:53:06 +00:00
|
|
|
|
2020-02-20 09:18:25 +00:00
|
|
|
uint id = idFor(msg->params[0]);
|
2020-03-22 15:18:21 +00:00
|
|
|
bool set = true;
|
2020-03-28 16:08:10 +00:00
|
|
|
uint i = 2;
|
2020-02-20 09:18:25 +00:00
|
|
|
for (char *ch = msg->params[1]; *ch; ++ch) {
|
2020-03-22 15:18:21 +00:00
|
|
|
if (*ch == '+') { set = true; continue; }
|
|
|
|
if (*ch == '-') { set = false; continue; }
|
|
|
|
|
2020-03-28 16:08:10 +00:00
|
|
|
const char *verb = (set ? "sets" : "unsets");
|
2020-03-22 15:18:21 +00:00
|
|
|
const char *name = ChanModes[(byte)*ch];
|
|
|
|
if (*ch == network.excepts) name = "except";
|
|
|
|
if (*ch == network.invex) name = "invite";
|
2020-03-28 16:08:10 +00:00
|
|
|
const char *mode = (const char[]) {
|
|
|
|
set["-+"], *ch, (name ? ' ' : '\0'), '\0'
|
|
|
|
};
|
|
|
|
if (!name) name = "";
|
2020-03-22 15:18:21 +00:00
|
|
|
|
|
|
|
if (strchr(network.prefixModes, *ch)) {
|
2020-04-05 17:17:38 +00:00
|
|
|
if (i >= ParamCap || !msg->params[i]) {
|
|
|
|
errx(EX_PROTOCOL, "MODE missing %s parameter", mode);
|
|
|
|
}
|
2020-03-28 16:08:10 +00:00
|
|
|
char *nick = msg->params[i++];
|
2020-03-22 15:18:21 +00:00
|
|
|
char prefix = network.prefixes[
|
|
|
|
strchr(network.prefixModes, *ch) - network.prefixModes
|
|
|
|
];
|
2020-03-28 16:08:10 +00:00
|
|
|
uiFormat(
|
|
|
|
id, Cold, tagTime(msg),
|
|
|
|
"\3%02d%s\3\t%s \3%02d%c%s\3 %s%s in \3%02d%s\3",
|
|
|
|
hash(msg->user), msg->nick, verb,
|
2020-03-22 15:18:21 +00:00
|
|
|
completeColor(id, nick), prefix, nick,
|
2020-03-28 16:08:10 +00:00
|
|
|
mode, name, hash(msg->params[0]), msg->params[0]
|
|
|
|
);
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s %s %c%s %s%s in %s",
|
|
|
|
msg->nick, verb, prefix, nick, mode, name, msg->params[0]
|
2020-02-20 09:18:25 +00:00
|
|
|
);
|
2020-03-22 15:18:21 +00:00
|
|
|
}
|
2020-02-20 09:53:06 +00:00
|
|
|
|
2020-03-22 15:18:21 +00:00
|
|
|
if (strchr(network.listModes, *ch)) {
|
2020-04-05 17:17:38 +00:00
|
|
|
if (i >= ParamCap || !msg->params[i]) {
|
|
|
|
errx(EX_PROTOCOL, "MODE missing %s parameter", mode);
|
|
|
|
}
|
2020-03-28 16:08:10 +00:00
|
|
|
char *mask = msg->params[i++];
|
2020-02-20 09:53:06 +00:00
|
|
|
if (*ch == 'b') {
|
2020-03-28 16:08:10 +00:00
|
|
|
verb = (set ? "bans" : "unbans");
|
|
|
|
uiFormat(
|
|
|
|
id, Cold, tagTime(msg),
|
|
|
|
"\3%02d%s\3\t%s %c%c %s from \3%02d%s\3",
|
|
|
|
hash(msg->user), msg->nick, verb, set["-+"], *ch, mask,
|
2020-02-20 09:53:06 +00:00
|
|
|
hash(msg->params[0]), msg->params[0]
|
|
|
|
);
|
2020-03-28 16:08:10 +00:00
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s %s %c%c %s from %s",
|
|
|
|
msg->nick, verb, set["-+"], *ch, mask, msg->params[0]
|
|
|
|
);
|
2020-03-22 15:18:21 +00:00
|
|
|
} else {
|
2020-03-28 16:08:10 +00:00
|
|
|
verb = (set ? "adds" : "removes");
|
|
|
|
const char *to = (set ? "to" : "from");
|
|
|
|
uiFormat(
|
|
|
|
id, Cold, tagTime(msg),
|
|
|
|
"\3%02d%s\3\t%s %s %s the \3%02d%s\3 %s%s list",
|
|
|
|
hash(msg->user), msg->nick, verb, mask, to,
|
|
|
|
hash(msg->params[0]), msg->params[0], mode, name
|
|
|
|
);
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s %s %s %s the %s %s%s list",
|
|
|
|
msg->nick, verb, mask, to, msg->params[0], mode, name
|
2020-03-22 15:18:21 +00:00
|
|
|
);
|
2020-02-20 09:53:06 +00:00
|
|
|
}
|
2020-03-22 15:18:21 +00:00
|
|
|
}
|
2020-02-20 09:53:06 +00:00
|
|
|
|
2020-03-22 15:18:21 +00:00
|
|
|
if (strchr(network.paramModes, *ch)) {
|
2020-04-05 17:17:38 +00:00
|
|
|
if (i >= ParamCap || !msg->params[i]) {
|
|
|
|
errx(EX_PROTOCOL, "MODE missing %s parameter", mode);
|
|
|
|
}
|
2020-03-28 16:08:10 +00:00
|
|
|
char *param = msg->params[i++];
|
|
|
|
uiFormat(
|
|
|
|
id, Cold, tagTime(msg),
|
|
|
|
"\3%02d%s\3\t%s \3%02d%s\3 %s%s %s",
|
|
|
|
hash(msg->user), msg->nick, verb,
|
|
|
|
hash(msg->params[0]), msg->params[0], mode, name, param
|
|
|
|
);
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s %s %s %s%s %s",
|
|
|
|
msg->nick, verb, msg->params[0], mode, name, param
|
2020-02-25 07:54:15 +00:00
|
|
|
);
|
2020-03-22 15:18:21 +00:00
|
|
|
}
|
2020-02-25 07:54:15 +00:00
|
|
|
|
2020-03-22 15:18:21 +00:00
|
|
|
if (strchr(network.setParamModes, *ch) && set) {
|
2020-04-05 17:17:38 +00:00
|
|
|
if (i >= ParamCap || !msg->params[i]) {
|
|
|
|
errx(EX_PROTOCOL, "MODE missing %s parameter", mode);
|
|
|
|
}
|
2020-03-28 16:08:10 +00:00
|
|
|
char *param = msg->params[i++];
|
|
|
|
uiFormat(
|
|
|
|
id, Cold, tagTime(msg),
|
|
|
|
"\3%02d%s\3\t%s \3%02d%s\3 %s%s %s",
|
|
|
|
hash(msg->user), msg->nick, verb,
|
|
|
|
hash(msg->params[0]), msg->params[0], mode, name, param
|
|
|
|
);
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s %s %s %s%s %s",
|
|
|
|
msg->nick, verb, msg->params[0], mode, name, param
|
2020-03-22 15:18:21 +00:00
|
|
|
);
|
2020-02-20 09:18:25 +00:00
|
|
|
} else if (strchr(network.setParamModes, *ch)) {
|
2020-03-28 16:08:10 +00:00
|
|
|
uiFormat(
|
|
|
|
id, Cold, tagTime(msg),
|
|
|
|
"\3%02d%s\3\t%s \3%02d%s\3 %s%s",
|
|
|
|
hash(msg->user), msg->nick, verb,
|
|
|
|
hash(msg->params[0]), msg->params[0], mode, name
|
|
|
|
);
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s %s %s %s%s",
|
|
|
|
msg->nick, verb, msg->params[0], mode, name
|
2020-03-22 15:18:21 +00:00
|
|
|
);
|
|
|
|
}
|
2020-02-25 07:54:15 +00:00
|
|
|
|
2020-03-22 15:18:21 +00:00
|
|
|
if (strchr(network.channelModes, *ch)) {
|
2020-03-28 16:08:10 +00:00
|
|
|
uiFormat(
|
|
|
|
id, Cold, tagTime(msg),
|
|
|
|
"\3%02d%s\3\t%s \3%02d%s\3 %s%s",
|
|
|
|
hash(msg->user), msg->nick, verb,
|
|
|
|
hash(msg->params[0]), msg->params[0], mode, name
|
|
|
|
);
|
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s %s %s %s%s",
|
|
|
|
msg->nick, verb, msg->params[0], mode, name
|
2020-02-25 07:54:15 +00:00
|
|
|
);
|
2020-02-20 09:18:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-20 10:04:58 +00:00
|
|
|
static void handleErrorChanopPrivsNeeded(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
idFor(msg->params[1]), Warm, tagTime(msg),
|
2020-02-20 10:04:58 +00:00
|
|
|
"%s", msg->params[2]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-20 08:18:48 +00:00
|
|
|
static void handleErrorUserNotInChannel(struct Message *msg) {
|
|
|
|
require(msg, false, 4);
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
idFor(msg->params[2]), Warm, tagTime(msg),
|
2020-02-20 08:18:48 +00:00
|
|
|
"%s\tis not in \3%02d%s\3",
|
|
|
|
msg->params[1], hash(msg->params[2]), msg->params[2]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleErrorBanListFull(struct Message *msg) {
|
|
|
|
require(msg, false, 4);
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
idFor(msg->params[1]), Warm, tagTime(msg),
|
2020-04-03 21:10:52 +00:00
|
|
|
"%s", (msg->params[4] ?: msg->params[3])
|
2020-02-20 08:18:48 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-20 02:47:16 +00:00
|
|
|
static void handleReplyBanList(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
|
|
|
uint id = idFor(msg->params[1]);
|
|
|
|
if (msg->params[3] && msg->params[4]) {
|
|
|
|
char since[sizeof("0000-00-00 00:00:00")];
|
|
|
|
time_t time = strtol(msg->params[4], NULL, 10);
|
|
|
|
strftime(since, sizeof(since), "%F %T", localtime(&time));
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
id, Warm, tagTime(msg),
|
2020-02-20 08:44:57 +00:00
|
|
|
"Banned from \3%02d%s\3 since %s by \3%02d%s\3: %s",
|
2020-02-20 02:47:16 +00:00
|
|
|
hash(msg->params[1]), msg->params[1],
|
2020-02-20 08:44:57 +00:00
|
|
|
since, completeColor(id, msg->params[3]), msg->params[3],
|
2020-02-20 02:47:16 +00:00
|
|
|
msg->params[2]
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
id, Warm, tagTime(msg),
|
2020-02-20 08:44:57 +00:00
|
|
|
"Banned from \3%02d%s\3: %s",
|
2020-02-20 02:47:16 +00:00
|
|
|
hash(msg->params[1]), msg->params[1], msg->params[2]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 07:12:35 +00:00
|
|
|
static void onList(const char *list, struct Message *msg) {
|
2020-12-30 21:49:55 +00:00
|
|
|
require(msg, false, 3);
|
2020-02-25 07:12:35 +00:00
|
|
|
uint id = idFor(msg->params[1]);
|
|
|
|
if (msg->params[3] && msg->params[4]) {
|
|
|
|
char since[sizeof("0000-00-00 00:00:00")];
|
|
|
|
time_t time = strtol(msg->params[4], NULL, 10);
|
|
|
|
strftime(since, sizeof(since), "%F %T", localtime(&time));
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
id, Warm, tagTime(msg),
|
2020-02-25 07:12:35 +00:00
|
|
|
"On the \3%02d%s\3 %s list since %s by \3%02d%s\3: %s",
|
|
|
|
hash(msg->params[1]), msg->params[1], list,
|
|
|
|
since, completeColor(id, msg->params[3]), msg->params[3],
|
|
|
|
msg->params[2]
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
uiFormat(
|
2021-01-26 22:24:05 +00:00
|
|
|
id, Warm, tagTime(msg),
|
2020-02-25 07:12:35 +00:00
|
|
|
"On the \3%02d%s\3 %s list: %s",
|
|
|
|
hash(msg->params[1]), msg->params[1], list, msg->params[2]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleReplyExceptList(struct Message *msg) {
|
|
|
|
onList("except", msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleReplyInviteList(struct Message *msg) {
|
|
|
|
onList("invite", msg);
|
|
|
|
}
|
|
|
|
|
2020-02-12 07:39:23 +00:00
|
|
|
static void handleReplyList(struct Message *msg) {
|
|
|
|
require(msg, false, 4);
|
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
|
|
|
"In \3%02d%s\3 are %ld under the banner: %s",
|
|
|
|
hash(msg->params[1]), msg->params[1],
|
|
|
|
strtol(msg->params[2], NULL, 10),
|
|
|
|
msg->params[3]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-09 21:45:49 +00:00
|
|
|
static void handleReplyWhoisUser(struct Message *msg) {
|
|
|
|
require(msg, false, 6);
|
|
|
|
completeTouch(Network, msg->params[1], hash(msg->params[2]));
|
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
2021-04-28 01:06:16 +00:00
|
|
|
"\3%02d%s\3\tis %s!%s@%s (%s\17)",
|
2020-02-09 21:45:49 +00:00
|
|
|
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) {
|
2020-12-30 21:49:55 +00:00
|
|
|
if (!replies[ReplyWhois] && !replies[ReplyWhowas]) return;
|
2020-02-09 21:45:49 +00:00
|
|
|
require(msg, false, 4);
|
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
2020-12-30 19:29:32 +00:00
|
|
|
"\3%02d%s\3\t%s connected to %s (%s)",
|
2020-02-09 21:45:49 +00:00
|
|
|
completeColor(Network, msg->params[1]), msg->params[1],
|
2020-12-30 21:49:55 +00:00
|
|
|
(replies[ReplyWhowas] ? "was" : "is"), msg->params[2], msg->params[3]
|
2020-02-09 21:45:49 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleReplyWhoisIdle(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
|
|
|
unsigned long idle = strtoul(msg->params[2], NULL, 10);
|
|
|
|
const char *unit = "second";
|
2020-02-11 09:00:25 +00:00
|
|
|
if (idle / 60) {
|
|
|
|
idle /= 60; unit = "minute";
|
|
|
|
if (idle / 60) {
|
|
|
|
idle /= 60; unit = "hour";
|
|
|
|
if (idle / 24) {
|
|
|
|
idle /= 24; unit = "day";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-20 02:29:51 +00:00
|
|
|
char signon[sizeof("0000-00-00 00:00:00")];
|
2020-04-03 21:10:52 +00:00
|
|
|
time_t time = strtol((msg->params[3] ?: ""), NULL, 10);
|
2020-02-20 02:29:51 +00:00
|
|
|
strftime(signon, sizeof(signon), "%F %T", localtime(&time));
|
2020-02-09 21:45:49 +00:00
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
2020-02-20 02:29:51 +00:00
|
|
|
"\3%02d%s\3\tis idle for %lu %s%s%s%s",
|
2020-02-09 21:45:49 +00:00
|
|
|
completeColor(Network, msg->params[1]), msg->params[1],
|
|
|
|
idle, unit, (idle != 1 ? "s" : ""),
|
2020-04-03 21:10:52 +00:00
|
|
|
(msg->params[3] ? ", signed on " : ""), (msg->params[3] ? signon : "")
|
2020-02-09 21:45:49 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleReplyWhoisChannels(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
2021-06-07 04:09:58 +00:00
|
|
|
char buf[1024];
|
|
|
|
char *ptr = buf, *end = &buf[sizeof(buf)];
|
2020-02-09 21:45:49 +00:00
|
|
|
while (msg->params[2]) {
|
|
|
|
char *channel = strsep(&msg->params[2], " ");
|
2021-06-10 23:38:12 +00:00
|
|
|
if (!channel[0]) break;
|
2020-02-15 10:04:43 +00:00
|
|
|
char *name = &channel[strspn(channel, network.prefixes)];
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(
|
|
|
|
ptr, end, "%s\3%02d%s\3",
|
|
|
|
(ptr > buf ? ", " : ""), hash(name), channel
|
|
|
|
);
|
2020-02-09 21:45:49 +00:00
|
|
|
}
|
|
|
|
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 (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],
|
2020-04-03 21:10:52 +00:00
|
|
|
msg->params[2], (msg->params[3] ? " " : ""), (msg->params[3] ?: "")
|
2020-02-09 21:45:49 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleReplyEndOfWhois(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
2020-02-11 22:52:55 +00:00
|
|
|
if (strcmp(msg->params[1], self.nick)) {
|
2020-02-09 21:45:49 +00:00
|
|
|
completeRemove(Network, msg->params[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-30 19:29:32 +00:00
|
|
|
static void handleReplyWhowasUser(struct Message *msg) {
|
|
|
|
require(msg, false, 6);
|
|
|
|
completeTouch(Network, msg->params[1], hash(msg->params[2]));
|
|
|
|
uiFormat(
|
|
|
|
Network, Warm, tagTime(msg),
|
|
|
|
"\3%02d%s\3\twas %s!%s@%s (%s)",
|
|
|
|
hash(msg->params[2]), msg->params[1],
|
|
|
|
msg->params[1], msg->params[2], msg->params[3], msg->params[5]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handleReplyEndOfWhowas(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
|
|
|
if (strcmp(msg->params[1], self.nick)) {
|
|
|
|
completeRemove(Network, msg->params[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-12 05:17:28 +00:00
|
|
|
static void handleReplyAway(struct Message *msg) {
|
|
|
|
require(msg, false, 3);
|
|
|
|
// Might be part of a WHOIS response.
|
2020-02-16 03:19:55 +00:00
|
|
|
uint id;
|
2020-02-12 05:17:28 +00:00
|
|
|
if (completeColor(Network, msg->params[1]) != Default) {
|
|
|
|
id = Network;
|
|
|
|
} else {
|
|
|
|
id = idFor(msg->params[1]);
|
|
|
|
}
|
|
|
|
uiFormat(
|
|
|
|
id, Warm, tagTime(msg),
|
|
|
|
"\3%02d%s\3\tis away: %s",
|
|
|
|
completeColor(id, msg->params[1]), msg->params[1], msg->params[2]
|
|
|
|
);
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(
|
|
|
|
id, tagTime(msg), "%s is away: %s",
|
|
|
|
msg->params[1], msg->params[2]
|
|
|
|
);
|
2020-02-12 05:17:28 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 02:10:40 +00:00
|
|
|
static void handleReplyNowAway(struct Message *msg) {
|
|
|
|
require(msg, false, 2);
|
|
|
|
uiFormat(Network, Warm, tagTime(msg), "%s", msg->params[1]);
|
|
|
|
}
|
|
|
|
|
2020-02-02 22:26:20 +00:00
|
|
|
static bool isAction(struct Message *msg) {
|
2021-06-20 23:22:20 +00:00
|
|
|
if (strncmp(msg->params[1], "\1ACTION", 7)) return false;
|
|
|
|
if (msg->params[1][7] == ' ') {
|
|
|
|
msg->params[1] += 8;
|
|
|
|
} else if (msg->params[1][7] == '\1') {
|
|
|
|
msg->params[1] += 7;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2020-02-02 22:26:20 +00:00
|
|
|
size_t len = strlen(msg->params[1]);
|
2021-06-20 23:22:20 +00:00
|
|
|
if (msg->params[1][len - 1] == '\1') {
|
|
|
|
msg->params[1][len - 1] = '\0';
|
|
|
|
}
|
2020-02-02 22:26:20 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-02-06 09:18:15 +00:00
|
|
|
static bool isMention(const struct Message *msg) {
|
|
|
|
size_t len = strlen(self.nick);
|
|
|
|
const char *match = msg->params[1];
|
2020-08-19 01:26:17 +00:00
|
|
|
while (NULL != (match = strstr(match, self.nick))) {
|
2020-02-06 09:18:15 +00:00
|
|
|
char a = (match > msg->params[1] ? match[-1] : ' ');
|
2020-04-03 21:10:52 +00:00
|
|
|
char b = (match[len] ?: ' ');
|
2020-02-06 09:18:15 +00:00
|
|
|
if ((isspace(a) || ispunct(a)) && (isspace(b) || ispunct(b))) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
match = &match[len];
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-07 04:09:58 +00:00
|
|
|
static void colorMentions(char *buf, size_t cap, uint id, struct Message *msg) {
|
|
|
|
*buf = '\0';
|
|
|
|
|
2020-06-17 03:14:07 +00:00
|
|
|
char *split = strstr(msg->params[1], ": ");
|
2020-02-11 23:08:05 +00:00
|
|
|
if (!split) {
|
|
|
|
split = strchr(msg->params[1], ' ');
|
|
|
|
if (split) split = strchr(&split[1], ' ');
|
|
|
|
}
|
2020-02-10 08:58:00 +00:00
|
|
|
if (!split) split = &msg->params[1][strlen(msg->params[1])];
|
|
|
|
for (char *ch = msg->params[1]; ch < split; ++ch) {
|
2020-11-08 20:16:25 +00:00
|
|
|
if (iscntrl(*ch)) return;
|
2020-02-09 03:51:13 +00:00
|
|
|
}
|
2020-02-10 08:58:00 +00:00
|
|
|
char delimit = *split;
|
|
|
|
char *mention = msg->params[1];
|
|
|
|
msg->params[1] = (delimit ? &split[1] : split);
|
|
|
|
*split = '\0';
|
2020-02-09 03:51:13 +00:00
|
|
|
|
2021-06-07 04:09:58 +00:00
|
|
|
char *ptr = buf, *end = &buf[cap];
|
2020-02-09 03:51:13 +00:00
|
|
|
while (*mention) {
|
2020-02-11 01:58:14 +00:00
|
|
|
size_t skip = strspn(mention, ",<> ");
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(ptr, end, "%.*s", (int)skip, mention);
|
2020-02-09 03:51:13 +00:00
|
|
|
mention += skip;
|
|
|
|
|
2020-02-11 01:58:14 +00:00
|
|
|
size_t len = strcspn(mention, ",<> ");
|
2020-02-09 04:29:32 +00:00
|
|
|
char punct = mention[len];
|
|
|
|
mention[len] = '\0';
|
2020-02-11 23:15:25 +00:00
|
|
|
enum Color color = completeColor(id, mention);
|
|
|
|
if (color != Default) {
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(ptr, end, "\3%02d%s\3", color, mention);
|
2020-02-11 23:15:25 +00:00
|
|
|
} else {
|
2021-06-07 04:09:58 +00:00
|
|
|
ptr = seprintf(ptr, end, "%s", mention);
|
2020-02-11 23:15:25 +00:00
|
|
|
}
|
2020-02-09 04:29:32 +00:00
|
|
|
mention[len] = punct;
|
|
|
|
mention += len;
|
2020-02-09 03:51:13 +00:00
|
|
|
}
|
2021-06-07 04:09:58 +00:00
|
|
|
seprintf(ptr, end, "%c", delimit);
|
2020-02-09 03:51:13 +00:00
|
|
|
}
|
|
|
|
|
2020-02-02 08:43:18 +00:00
|
|
|
static void handlePrivmsg(struct Message *msg) {
|
|
|
|
require(msg, true, 2);
|
2021-04-02 21:03:10 +00:00
|
|
|
if (network.statusmsg) {
|
|
|
|
msg->params[0] += strspn(msg->params[0], network.statusmsg);
|
|
|
|
}
|
2020-02-15 09:54:53 +00:00
|
|
|
bool query = !strchr(network.chanTypes, msg->params[0][0]);
|
2020-09-18 22:13:09 +00:00
|
|
|
bool server = strchr(msg->nick, '.');
|
2020-02-11 22:52:55 +00:00
|
|
|
bool mine = !strcmp(msg->nick, self.nick);
|
2020-02-16 03:19:55 +00:00
|
|
|
uint id;
|
2020-02-15 09:54:53 +00:00
|
|
|
if (query && server) {
|
2020-02-06 09:01:11 +00:00
|
|
|
id = Network;
|
|
|
|
} else if (query && !mine) {
|
|
|
|
id = idFor(msg->nick);
|
|
|
|
idColors[id] = hash(msg->user);
|
|
|
|
} else {
|
|
|
|
id = idFor(msg->params[0]);
|
|
|
|
}
|
|
|
|
|
2020-02-02 22:26:20 +00:00
|
|
|
bool notice = (msg->cmd[0] == 'N');
|
2021-06-20 22:17:38 +00:00
|
|
|
bool action = !notice && isAction(msg);
|
2021-01-16 19:04:53 +00:00
|
|
|
bool highlight = !mine && isMention(msg);
|
|
|
|
enum Heat heat = filterCheck((highlight || query ? Hot : Warm), id, msg);
|
|
|
|
if (heat > Warm && !mine && !query) highlight = true;
|
2020-09-02 21:51:51 +00:00
|
|
|
if (!notice && !mine && heat > Ice) {
|
|
|
|
completeTouch(id, msg->nick, hash(msg->user));
|
|
|
|
}
|
2020-07-10 02:43:35 +00:00
|
|
|
if (heat > Ice) urlScan(id, msg->nick, msg->params[1]);
|
2020-09-02 21:51:51 +00:00
|
|
|
|
2021-06-07 04:09:58 +00:00
|
|
|
char buf[1024];
|
2020-02-07 03:59:49 +00:00
|
|
|
if (notice) {
|
2020-03-25 22:58:48 +00:00
|
|
|
if (id != Network) {
|
|
|
|
logFormat(id, tagTime(msg), "-%s- %s", msg->nick, msg->params[1]);
|
|
|
|
}
|
2020-02-07 03:59:49 +00:00
|
|
|
uiFormat(
|
2021-01-16 18:30:59 +00:00
|
|
|
id, filterCheck(Warm, id, msg), tagTime(msg),
|
2020-02-11 22:52:55 +00:00
|
|
|
"\3%d-%s-\3%d\t%s",
|
|
|
|
hash(msg->user), msg->nick, LightGray, msg->params[1]
|
2020-02-07 03:59:49 +00:00
|
|
|
);
|
|
|
|
} else if (action) {
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(id, tagTime(msg), "* %s %s", msg->nick, msg->params[1]);
|
2021-06-07 04:09:58 +00:00
|
|
|
colorMentions(buf, sizeof(buf), id, msg);
|
2020-02-07 03:59:49 +00:00
|
|
|
uiFormat(
|
2020-04-07 17:18:42 +00:00
|
|
|
id, heat, tagTime(msg),
|
2020-02-16 10:22:28 +00:00
|
|
|
"%s\35\3%d* %s\17\35\t%s%s",
|
2021-01-16 19:04:53 +00:00
|
|
|
(highlight ? "\26" : ""), hash(msg->user), msg->nick,
|
2020-11-08 20:16:25 +00:00
|
|
|
buf, msg->params[1]
|
2020-02-07 03:59:49 +00:00
|
|
|
);
|
|
|
|
} else {
|
2020-03-25 22:58:48 +00:00
|
|
|
logFormat(id, tagTime(msg), "<%s> %s", msg->nick, msg->params[1]);
|
2021-06-07 04:09:58 +00:00
|
|
|
colorMentions(buf, sizeof(buf), id, msg);
|
2020-02-07 03:59:49 +00:00
|
|
|
uiFormat(
|
2020-04-07 17:18:42 +00:00
|
|
|
id, heat, tagTime(msg),
|
2020-02-09 03:51:13 +00:00
|
|
|
"%s\3%d<%s>\17\t%s%s",
|
2021-01-16 19:04:53 +00:00
|
|
|
(highlight ? "\26" : ""), hash(msg->user), msg->nick,
|
2020-11-08 20:16:25 +00:00
|
|
|
buf, msg->params[1]
|
2020-02-07 03:59:49 +00:00
|
|
|
);
|
|
|
|
}
|
2020-02-02 08:43:18 +00:00
|
|
|
}
|
|
|
|
|
2020-02-02 06:58:03 +00:00
|
|
|
static void handlePing(struct Message *msg) {
|
|
|
|
require(msg, false, 1);
|
|
|
|
ircFormat("PONG :%s\r\n", msg->params[0]);
|
2020-02-02 00:37:48 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 04:27:43 +00:00
|
|
|
static void handleError(struct Message *msg) {
|
|
|
|
require(msg, false, 1);
|
|
|
|
errx(EX_UNAVAILABLE, "%s", msg->params[0]);
|
|
|
|
}
|
|
|
|
|
2020-02-01 06:18:01 +00:00
|
|
|
static const struct Handler {
|
|
|
|
const char *cmd;
|
2020-12-30 21:49:55 +00:00
|
|
|
int reply;
|
2020-02-01 06:18:01 +00:00
|
|
|
Handler *fn;
|
|
|
|
} Handlers[] = {
|
2020-12-30 21:49:55 +00:00
|
|
|
{ "001", 0, handleReplyWelcome },
|
|
|
|
{ "005", 0, handleReplyISupport },
|
|
|
|
{ "221", -ReplyMode, handleReplyUserModeIs },
|
|
|
|
{ "276", +ReplyWhois, handleReplyWhoisGeneric },
|
|
|
|
{ "301", 0, handleReplyAway },
|
|
|
|
{ "305", -ReplyAway, handleReplyNowAway },
|
|
|
|
{ "306", -ReplyAway, handleReplyNowAway },
|
|
|
|
{ "307", +ReplyWhois, handleReplyWhoisGeneric },
|
|
|
|
{ "311", +ReplyWhois, handleReplyWhoisUser },
|
|
|
|
{ "312", 0, handleReplyWhoisServer },
|
|
|
|
{ "313", +ReplyWhois, handleReplyWhoisGeneric },
|
|
|
|
{ "314", +ReplyWhowas, handleReplyWhowasUser },
|
|
|
|
{ "315", -ReplyWho, handleReplyEndOfWho },
|
|
|
|
{ "317", +ReplyWhois, handleReplyWhoisIdle },
|
|
|
|
{ "318", -ReplyWhois, handleReplyEndOfWhois },
|
|
|
|
{ "319", +ReplyWhois, handleReplyWhoisChannels },
|
|
|
|
{ "322", +ReplyList, handleReplyList },
|
|
|
|
{ "323", -ReplyList, NULL },
|
|
|
|
{ "324", -ReplyMode, handleReplyChannelModeIs },
|
|
|
|
{ "330", +ReplyWhois, handleReplyWhoisGeneric },
|
|
|
|
{ "331", -ReplyTopic, handleReplyNoTopic },
|
|
|
|
{ "332", 0, handleReplyTopic },
|
2021-01-28 20:31:00 +00:00
|
|
|
{ "335", +ReplyWhois, handleReplyWhoisGeneric },
|
2021-06-18 00:18:37 +00:00
|
|
|
{ "338", +ReplyWhois, handleReplyWhoisGeneric },
|
2020-12-30 21:49:55 +00:00
|
|
|
{ "341", 0, handleReplyInviting },
|
|
|
|
{ "346", +ReplyInvex, handleReplyInviteList },
|
|
|
|
{ "347", -ReplyInvex, NULL },
|
|
|
|
{ "348", +ReplyExcepts, handleReplyExceptList },
|
|
|
|
{ "349", -ReplyExcepts, NULL },
|
|
|
|
{ "352", +ReplyWho, handleReplyWho },
|
|
|
|
{ "353", 0, handleReplyNames },
|
2021-02-21 19:23:17 +00:00
|
|
|
{ "366", 0, handleReplyEndOfNames },
|
2020-12-30 21:49:55 +00:00
|
|
|
{ "367", +ReplyBan, handleReplyBanList },
|
|
|
|
{ "368", -ReplyBan, NULL },
|
|
|
|
{ "369", -ReplyWhowas, handleReplyEndOfWhowas },
|
|
|
|
{ "372", 0, handleReplyMOTD },
|
|
|
|
{ "378", +ReplyWhois, handleReplyWhoisGeneric },
|
|
|
|
{ "379", +ReplyWhois, handleReplyWhoisGeneric },
|
|
|
|
{ "422", 0, handleErrorNoMOTD },
|
|
|
|
{ "432", 0, handleErrorErroneousNickname },
|
|
|
|
{ "433", 0, handleErrorNicknameInUse },
|
|
|
|
{ "437", 0, handleErrorNicknameInUse },
|
|
|
|
{ "441", 0, handleErrorUserNotInChannel },
|
|
|
|
{ "443", 0, handleErrorUserOnChannel },
|
|
|
|
{ "478", 0, handleErrorBanListFull },
|
|
|
|
{ "482", 0, handleErrorChanopPrivsNeeded },
|
|
|
|
{ "671", +ReplyWhois, handleReplyWhoisGeneric },
|
|
|
|
{ "704", +ReplyHelp, handleReplyHelp },
|
|
|
|
{ "705", +ReplyHelp, handleReplyHelp },
|
|
|
|
{ "706", -ReplyHelp, NULL },
|
|
|
|
{ "900", 0, handleReplyLoggedIn },
|
|
|
|
{ "904", 0, handleErrorSASLFail },
|
|
|
|
{ "905", 0, handleErrorSASLFail },
|
|
|
|
{ "906", 0, handleErrorSASLFail },
|
|
|
|
{ "AUTHENTICATE", 0, handleAuthenticate },
|
|
|
|
{ "CAP", 0, handleCap },
|
|
|
|
{ "CHGHOST", 0, handleChghost },
|
|
|
|
{ "ERROR", 0, handleError },
|
|
|
|
{ "FAIL", 0, handleStandardReply },
|
|
|
|
{ "INVITE", 0, handleInvite },
|
|
|
|
{ "JOIN", 0, handleJoin },
|
|
|
|
{ "KICK", 0, handleKick },
|
|
|
|
{ "MODE", 0, handleMode },
|
|
|
|
{ "NICK", 0, handleNick },
|
|
|
|
{ "NOTE", 0, handleStandardReply },
|
|
|
|
{ "NOTICE", 0, handlePrivmsg },
|
|
|
|
{ "PART", 0, handlePart },
|
|
|
|
{ "PING", 0, handlePing },
|
|
|
|
{ "PRIVMSG", 0, handlePrivmsg },
|
|
|
|
{ "QUIT", 0, handleQuit },
|
|
|
|
{ "SETNAME", 0, handleSetname },
|
|
|
|
{ "TOPIC", 0, handleTopic },
|
|
|
|
{ "WARN", 0, handleStandardReply },
|
2020-02-01 06:18:01 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int compar(const void *cmd, const void *_handler) {
|
|
|
|
const struct Handler *handler = _handler;
|
|
|
|
return strcmp(cmd, handler->cmd);
|
|
|
|
}
|
|
|
|
|
2020-06-24 17:36:24 +00:00
|
|
|
void handle(struct Message *msg) {
|
|
|
|
if (!msg->cmd) return;
|
|
|
|
if (msg->tags[TagPos]) {
|
|
|
|
self.pos = strtoull(msg->tags[TagPos], NULL, 10);
|
2020-03-09 03:15:56 +00:00
|
|
|
}
|
2020-02-01 06:18:01 +00:00
|
|
|
const struct Handler *handler = bsearch(
|
2020-06-24 17:36:24 +00:00
|
|
|
msg->cmd, Handlers, ARRAY_LEN(Handlers), sizeof(*handler), compar
|
2020-02-01 06:18:01 +00:00
|
|
|
);
|
2020-02-20 06:38:31 +00:00
|
|
|
if (handler) {
|
2020-12-30 21:49:55 +00:00
|
|
|
if (handler->reply && !replies[abs(handler->reply)]) return;
|
|
|
|
if (handler->fn) handler->fn(msg);
|
|
|
|
if (handler->reply < 0) replies[abs(handler->reply)]--;
|
2020-06-24 17:36:24 +00:00
|
|
|
} else if (strcmp(msg->cmd, "400") >= 0 && strcmp(msg->cmd, "599") <= 0) {
|
|
|
|
handleErrorGeneric(msg);
|
2020-02-20 06:38:31 +00:00
|
|
|
}
|
2020-02-01 06:18:01 +00:00
|
|
|
}
|