diff options
Diffstat (limited to 'command.c')
-rw-r--r-- | command.c | 407 |
1 files changed, 290 insertions, 117 deletions
diff --git a/command.c b/command.c index 4c51433..502ff17 100644 --- a/command.c +++ b/command.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 C. McEnroe <june@causal.agency> +/* Copyright (C) 2020 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,8 +64,7 @@ static void echoMessage(char *cmd, uint id, char *params) { handle(&msg); } -static void splitMessage(char *cmd, uint id, char *params) { - if (!params) return; +static int splitChunk(const char *cmd, uint id) { int overhead = snprintf( NULL, 0, ":%s!%*s@%*s %s %s :\r\n", self.nick, @@ -74,22 +73,32 @@ static void splitMessage(char *cmd, uint id, char *params) { cmd, idNames[id] ); assert(overhead > 0 && overhead < 512); - int chunk = 512 - overhead; + return 512 - overhead; +} + +static int splitLen(int chunk, const char *params) { + int len = 0; + size_t cap = 1 + strlen(params); + for (int n = 0; params[len] != '\n' && len + n <= chunk; len += n) { + n = mblen(¶ms[len], cap - len); + if (n < 0) { + n = 1; + mblen(NULL, 0); + } + if (!n) break; + } + return len; +} + +static void splitMessage(char *cmd, uint id, char *params) { + if (!params) return; + int chunk = splitChunk(cmd, id); if (strlen(params) <= (size_t)chunk && !strchr(params, '\n')) { echoMessage(cmd, id, params); return; } - while (*params) { - int len = 0; - for (int n = 0; params[len] != '\n' && len + n <= chunk; len += n) { - n = mblen(¶ms[len], 1 + strlen(¶ms[len])); - if (n < 0) { - n = 1; - mblen(NULL, 0); - } - if (!n) break; - } + int len = splitLen(chunk, params); char ch = params[len]; params[len] = '\0'; echoMessage(cmd, id, params); @@ -109,25 +118,47 @@ static void commandNotice(uint id, char *params) { static void commandMe(uint id, char *params) { char buf[512]; - snprintf(buf, sizeof(buf), "\1ACTION %s\1", (params ?: "")); - echoMessage("PRIVMSG", id, buf); + if (!params) params = ""; + int chunk = splitChunk("PRIVMSG \1ACTION\1", id); + if (strlen(params) <= (size_t)chunk && !strchr(params, '\n')) { + snprintf(buf, sizeof(buf), "\1ACTION %s\1", params); + echoMessage("PRIVMSG", id, buf); + return; + } + while (*params) { + int len = splitLen(chunk, params); + snprintf(buf, sizeof(buf), "\1ACTION %.*s\1", len, params); + echoMessage("PRIVMSG", id, buf); + params += len; + if (*params == '\n') params++; + } } static void commandMsg(uint id, char *params) { - id = idFor(strsep(¶ms, " ")); - splitMessage("PRIVMSG", id, params); + if (!params) return; + char *nick = strsep(¶ms, " "); + uint msg = idFor(nick); + if (idColors[msg] == Default) { + idColors[msg] = completeColor(id, nick); + } + if (params) { + splitMessage("PRIVMSG", msg, params); + } else { + windowShow(windowFor(msg)); + } } static void commandJoin(uint id, char *params) { + if (!params && id == Network) params = self.invited; if (!params) params = idNames[id]; uint count = 1; for (char *ch = params; *ch && *ch != ' '; ++ch) { if (*ch == ',') count++; } ircFormat("JOIN %s\r\n", params); - replies.join += count; - replies.topic += count; - replies.names += count; + replies[ReplyJoin] += count; + replies[ReplyTopic] += count; + replies[ReplyNames] += count; } static void commandPart(uint id, char *params) { @@ -145,8 +176,14 @@ static void commandQuit(uint id, char *params) { static void commandNick(uint id, char *params) { (void)id; - if (!params) return; - ircFormat("NICK :%s\r\n", params); + if (params) { + ircFormat("NICK :%s\r\n", params); + } else { + uiFormat( + Network, Warm, NULL, "You are \3%02d%s", + self.color, self.nick + ); + } } static void commandAway(uint id, char *params) { @@ -156,7 +193,13 @@ static void commandAway(uint id, char *params) { } else { ircFormat("AWAY\r\n"); } - replies.away++; + replies[ReplyAway]++; +} + +static void commandSetname(uint id, char *params) { + (void)id; + if (!params) return; + ircFormat("SETNAME :%s\r\n", params); } static void commandTopic(uint id, char *params) { @@ -164,14 +207,43 @@ static void commandTopic(uint id, char *params) { ircFormat("TOPIC %s :%s\r\n", idNames[id], params); } else { ircFormat("TOPIC %s\r\n", idNames[id]); - replies.topic++; + replies[ReplyTopic]++; } } static void commandNames(uint id, char *params) { (void)params; ircFormat("NAMES %s\r\n", idNames[id]); - replies.names++; + replies[ReplyNames]++; +} + +static void commandOps(uint id, char *params) { + (void)params; + char buf[1024]; + char *ptr = buf, *end = &buf[sizeof(buf)]; + ptr = seprintf( + ptr, end, "The council of \3%02d%s\3 are ", + idColors[id], idNames[id] + ); + bool first = true; + struct Cursor curs = {0}; + for (const char *nick; (nick = completeEach(&curs, id));) { + char prefix = bitPrefix(*completeBits(id, nick)); + if (!prefix || prefix == '+') continue; + ptr = seprintf( + ptr, end, "%s\3%02d%c%s\3", + (first ? "" : ", "), completeColor(id, nick), prefix, nick + ); + first = false; + } + if (first) { + uiFormat( + id, Warm, NULL, "\3%02d%s\3 is a lawless wasteland", + idColors[id], idNames[id] + ); + } else { + uiWrite(id, Warm, NULL, buf); + } } static void commandInvite(uint id, char *params) { @@ -196,14 +268,20 @@ static void commandMode(uint id, char *params) { ircFormat("MODE %s %s\r\n", self.nick, params); } else { ircFormat("MODE %s\r\n", self.nick); - replies.mode++; + replies[ReplyMode]++; } } else { if (params) { + if (!params[1] || (params[0] == '+' && !params[2])) { + char m = (params[0] == '+' ? params[1] : params[0]); + if (m == 'b') replies[ReplyBan]++; + if (m == network.excepts) replies[ReplyExcepts]++; + if (m == network.invex) replies[ReplyInvex]++; + } ircFormat("MODE %s %s\r\n", idNames[id], params); } else { ircFormat("MODE %s\r\n", idNames[id]); - replies.mode++; + replies[ReplyMode]++; } } } @@ -221,7 +299,7 @@ static void commandOp(uint id, char *params) { if (params) { channelListMode(id, '+', 'o', params); } else { - ircFormat("PRIVMSG ChanServ :OP %s\r\n", idNames[id]); + ircFormat("CS OP %s\r\n", idNames[id]); } } @@ -233,7 +311,7 @@ static void commandVoice(uint id, char *params) { if (params) { channelListMode(id, '+', 'v', params); } else { - ircFormat("PRIVMSG ChanServ :VOICE %s\r\n", idNames[id]); + ircFormat("CS VOICE %s\r\n", idNames[id]); } } @@ -246,7 +324,7 @@ static void commandBan(uint id, char *params) { channelListMode(id, '+', 'b', params); } else { ircFormat("MODE %s b\r\n", idNames[id]); - replies.ban++; + replies[ReplyBan]++; } } @@ -260,7 +338,7 @@ static void commandExcept(uint id, char *params) { channelListMode(id, '+', network.excepts, params); } else { ircFormat("MODE %s %c\r\n", idNames[id], network.excepts); - replies.excepts++; + replies[ReplyExcepts]++; } } @@ -274,7 +352,7 @@ static void commandInvex(uint id, char *params) { channelListMode(id, '+', network.invex, params); } else { ircFormat("MODE %s %c\r\n", idNames[id], network.invex); - replies.invex++; + replies[ReplyInvex]++; } } @@ -290,40 +368,65 @@ static void commandList(uint id, char *params) { } else { ircFormat("LIST\r\n"); } - replies.list++; + replies[ReplyList]++; } static void commandWhois(uint id, char *params) { (void)id; + if (!params) params = self.nick; + uint count = 1; + for (char *ch = params; *ch; ++ch) { + if (*ch == ',') count++; + } + ircFormat("WHOIS %s\r\n", params); + replies[ReplyWhois] += count; +} + +static void commandWhowas(uint id, char *params) { + (void)id; if (!params) return; - ircFormat("WHOIS :%s\r\n", params); - replies.whois++; + ircFormat("WHOWAS %s\r\n", params); + replies[ReplyWhowas]++; } static void commandNS(uint id, char *params) { (void)id; - if (params) ircFormat("PRIVMSG NickServ :%s\r\n", params); + ircFormat("NS %s\r\n", (params ?: "HELP")); } static void commandCS(uint id, char *params) { (void)id; - if (params) ircFormat("PRIVMSG ChanServ :%s\r\n", params); + ircFormat("CS %s\r\n", (params ?: "HELP")); } static void commandQuery(uint id, char *params) { if (!params) return; uint query = idFor(params); - idColors[query] = completeColor(id, params); - uiShowID(query); + if (idColors[query] == Default) { + idColors[query] = completeColor(id, params); + } + windowShow(windowFor(query)); } static void commandWindow(uint id, char *params) { - if (!params) return; - if (isdigit(params[0])) { - uiShowNum(strtoul(params, NULL, 10)); + if (!params) { + windowList(); + } else if (isdigit(params[0])) { + windowShow(strtoul(params, NULL, 10)); } else { id = idFind(params); - if (id) uiShowID(id); + if (id) { + windowShow(windowFor(id)); + return; + } + struct Cursor curs = {0}; + for (const char *str; (str = completeSubstr(&curs, None, params));) { + id = idFind(str); + if (!id) continue; + completeAccept(&curs); + windowShow(windowFor(id)); + break; + } } } @@ -332,20 +435,20 @@ static void commandMove(uint id, char *params) { char *name = strsep(¶ms, " "); if (params) { id = idFind(name); - if (id) uiMoveID(id, strtoul(params, NULL, 10)); + if (id) windowMove(windowFor(id), strtoul(params, NULL, 10)); } else { - uiMoveID(id, strtoul(name, NULL, 10)); + windowMove(windowFor(id), strtoul(name, NULL, 10)); } } static void commandClose(uint id, char *params) { if (!params) { - uiCloseID(id); + windowClose(windowFor(id)); } else if (isdigit(params[0])) { - uiCloseNum(strtoul(params, NULL, 10)); + windowClose(strtoul(params, NULL, 10)); } else { id = idFind(params); - if (id) uiCloseID(id); + if (id) windowClose(windowFor(id)); } } @@ -363,33 +466,50 @@ static void commandCopy(uint id, char *params) { urlCopyMatch(id, params); } -static void commandIgnore(uint id, char *params) { +static void commandFilter(enum Heat heat, uint id, char *params) { if (params) { - const char *pattern = ignoreAdd(params); + struct Filter filter = filterAdd(heat, params); uiFormat( - id, Cold, NULL, "Ignoring \3%02d%s\3", - Brown, pattern + id, Cold, NULL, "%sing \3%02d%s %s %s %s", + (heat == Hot ? "Highlight" : "Ignor"), Brown, filter.mask, + (filter.cmd ?: ""), (filter.chan ?: ""), (filter.mesg ?: "") ); } else { - for (size_t i = 0; i < ignore.len; ++i) { + for (size_t i = 0; i < FilterCap && filters[i].mask; ++i) { + if (filters[i].heat != heat) continue; uiFormat( - Network, Warm, NULL, "Ignoring \3%02d%s\3", - Brown, ignore.patterns[i] + Network, Warm, NULL, "%sing \3%02d%s %s %s %s", + (heat == Hot ? "Highlight" : "Ignor"), Brown, filters[i].mask, + (filters[i].cmd ?: ""), (filters[i].chan ?: ""), + (filters[i].mesg ?: "") ); } } } -static void commandUnignore(uint id, char *params) { +static void commandUnfilter(enum Heat heat, uint id, char *params) { if (!params) return; - if (ignoreRemove(params)) { - uiFormat( - id, Cold, NULL, "No longer ignoring \3%02d%s\3", - Brown, params - ); - } else { - uiFormat(id, Cold, NULL, "Not ignoring \3%02d%s\3", Brown, params); - } + struct Filter filter = filterParse(heat, params); + bool found = filterRemove(filter); + uiFormat( + id, Cold, NULL, "%s %sing \3%02d%s %s %s %s", + (found ? "No longer" : "Not"), (heat == Hot ? "highlight" : "ignor"), + Brown, filter.mask, (filter.cmd ?: ""), (filter.chan ?: ""), + (filter.mesg ?: "") + ); +} + +static void commandHighlight(uint id, char *params) { + commandFilter(Hot, id, params); +} +static void commandIgnore(uint id, char *params) { + commandFilter(Ice, id, params); +} +static void commandUnhighlight(uint id, char *params) { + commandUnfilter(Hot, id, params); +} +static void commandUnignore(uint id, char *params) { + commandUnfilter(Ice, id, params); } static void commandExec(uint id, char *params) { @@ -399,26 +519,37 @@ static void commandExec(uint id, char *params) { if (pid < 0) err(EX_OSERR, "fork"); if (pid) return; + setsid(); close(STDIN_FILENO); dup2(execPipe[1], STDOUT_FILENO); dup2(utilPipe[1], STDERR_FILENO); const char *shell = getenv("SHELL") ?: "/bin/sh"; - execlp(shell, shell, "-c", params, NULL); + execl(shell, shell, "-c", params, NULL); warn("%s", shell); _exit(EX_UNAVAILABLE); } static void commandHelp(uint id, char *params) { (void)id; - uiHide(); + if (params) { + ircFormat("HELP :%s\r\n", params); + replies[ReplyHelp]++; + return; + } + if (self.restricted) { + uiFormat(id, Warm, NULL, "See catgirl(1) or /help index"); + return; + } + + uiHide(); pid_t pid = fork(); if (pid < 0) err(EX_OSERR, "fork"); if (pid) return; char buf[256]; - snprintf(buf, sizeof(buf), "ip%s$", (params ?: "COMMANDS")); + snprintf(buf, sizeof(buf), "%sp^COMMANDS$", (getenv("LESS") ?: "")); setenv("LESS", buf, 1); execlp("man", "man", "1", "catgirl", NULL); dup2(utilPipe[1], STDERR_FILENO); @@ -428,55 +559,61 @@ static void commandHelp(uint id, char *params) { enum Flag { BIT(Multiline), - BIT(Restricted), + BIT(Restrict), }; static const struct Handler { const char *cmd; Command *fn; enum Flag flags; + enum Cap caps; } Commands[] = { - { "/away", commandAway, 0 }, - { "/ban", commandBan, 0 }, - { "/close", commandClose, 0 }, - { "/copy", commandCopy, Restricted }, - { "/cs", commandCS, 0 }, - { "/debug", commandDebug, Restricted }, - { "/deop", commandDeop, 0 }, - { "/devoice", commandDevoice, 0 }, - { "/except", commandExcept, 0 }, - { "/exec", commandExec, Multiline | Restricted }, - { "/help", commandHelp, 0 }, - { "/ignore", commandIgnore, 0 }, - { "/invex", commandInvex, 0 }, - { "/invite", commandInvite, 0 }, - { "/join", commandJoin, Restricted }, - { "/kick", commandKick, 0 }, - { "/list", commandList, 0 }, - { "/me", commandMe, 0 }, - { "/mode", commandMode, 0 }, - { "/move", commandMove, 0 }, - { "/msg", commandMsg, Multiline | Restricted }, - { "/names", commandNames, 0 }, - { "/nick", commandNick, 0 }, - { "/notice", commandNotice, Multiline }, - { "/ns", commandNS, 0 }, - { "/o", commandOpen, Restricted }, - { "/op", commandOp, 0 }, - { "/open", commandOpen, Restricted }, - { "/part", commandPart, 0 }, - { "/query", commandQuery, Restricted }, - { "/quit", commandQuit, 0 }, - { "/quote", commandQuote, Multiline | Restricted }, - { "/say", commandPrivmsg, Multiline }, - { "/topic", commandTopic, 0 }, - { "/unban", commandUnban, 0 }, - { "/unexcept", commandUnexcept, 0 }, - { "/unignore", commandUnignore, 0 }, - { "/uninvex", commandUninvex, 0 }, - { "/voice", commandVoice, 0 }, - { "/whois", commandWhois, 0 }, - { "/window", commandWindow, 0 }, + { "/away", commandAway, 0, 0 }, + { "/ban", commandBan, 0, 0 }, + { "/close", commandClose, 0, 0 }, + { "/copy", commandCopy, Restrict, 0 }, + { "/cs", commandCS, 0, 0 }, + { "/debug", commandDebug, 0, 0 }, + { "/deop", commandDeop, 0, 0 }, + { "/devoice", commandDevoice, 0, 0 }, + { "/except", commandExcept, 0, 0 }, + { "/exec", commandExec, Multiline | Restrict, 0 }, + { "/help", commandHelp, 0, 0 }, // Restrict special case. + { "/highlight", commandHighlight, 0, 0 }, + { "/ignore", commandIgnore, 0, 0 }, + { "/invex", commandInvex, 0, 0 }, + { "/invite", commandInvite, 0, 0 }, + { "/join", commandJoin, 0, 0 }, + { "/kick", commandKick, 0, 0 }, + { "/list", commandList, 0, 0 }, + { "/me", commandMe, Multiline, 0 }, + { "/mode", commandMode, 0, 0 }, + { "/move", commandMove, 0, 0 }, + { "/msg", commandMsg, Multiline, 0 }, + { "/names", commandNames, 0, 0 }, + { "/nick", commandNick, 0, 0 }, + { "/notice", commandNotice, Multiline, 0 }, + { "/ns", commandNS, 0, 0 }, + { "/o", commandOpen, Restrict, 0 }, + { "/op", commandOp, 0, 0 }, + { "/open", commandOpen, Restrict, 0 }, + { "/ops", commandOps, 0, 0 }, + { "/part", commandPart, 0, 0 }, + { "/query", commandQuery, 0, 0 }, + { "/quit", commandQuit, 0, 0 }, + { "/quote", commandQuote, Multiline, 0 }, + { "/say", commandPrivmsg, Multiline, 0 }, + { "/setname", commandSetname, 0, CapSetname }, + { "/topic", commandTopic, 0, 0 }, + { "/unban", commandUnban, 0, 0 }, + { "/unexcept", commandUnexcept, 0, 0 }, + { "/unhighlight", commandUnhighlight, 0, 0 }, + { "/unignore", commandUnignore, 0, 0 }, + { "/uninvex", commandUninvex, 0, 0 }, + { "/voice", commandVoice, 0, 0 }, + { "/whois", commandWhois, 0, 0 }, + { "/whowas", commandWhowas, 0, 0 }, + { "/window", commandWindow, 0, 0 }, }; static int compar(const void *cmd, const void *_handler) { @@ -505,6 +642,41 @@ const char *commandIsAction(uint id, const char *input) { return &input[4]; } +size_t commandWillSplit(uint id, const char *input) { + int chunk; + const char *params; + if (NULL != (params = commandIsPrivmsg(id, input))) { + chunk = splitChunk("PRIVMSG", id); + } else if (NULL != (params = commandIsNotice(id, input))) { + chunk = splitChunk("NOTICE", id); + } else if (NULL != (params = commandIsAction(id, input))) { + chunk = splitChunk("PRIVMSG \1ACTION\1", id); + } else if (id != Network && id != Debug && !strncmp(input, "/say ", 5)) { + params = &input[5]; + chunk = splitChunk("PRIVMSG", id); + } else { + return 0; + } + if (strlen(params) <= (size_t)chunk) return 0; + for ( + int split; + params[(split = splitLen(chunk, params))]; + params = ¶ms[split + 1] + ) { + if (params[split] == '\n') continue; + return (params - input) + split; + } + return 0; +} + +static bool commandAvailable(const struct Handler *handler) { + if (handler->flags & Restrict && self.restricted) return false; + if (handler->caps && (handler->caps & self.caps) != handler->caps) { + return false; + } + return true; +} + void command(uint id, char *input) { if (id == Debug && input[0] != '/' && !self.restricted) { commandQuote(id, input); @@ -519,11 +691,11 @@ void command(uint id, char *input) { return; } + struct Cursor curs = {0}; const char *cmd = strsep(&input, " "); - const char *unique = complete(None, cmd); - if (unique && !complete(None, cmd)) { + const char *unique = completePrefix(&curs, None, cmd); + if (unique && !completePrefix(&curs, None, cmd)) { cmd = unique; - completeReject(); } const struct Handler *handler = bsearch( @@ -533,8 +705,8 @@ void command(uint id, char *input) { uiFormat(id, Warm, NULL, "No such command %s", cmd); return; } - if (self.restricted && handler->flags & Restricted) { - uiFormat(id, Warm, NULL, "Command %s is restricted", cmd); + if (!commandAvailable(handler)) { + uiFormat(id, Warm, NULL, "Command %s is unavailable", cmd); return; } @@ -550,8 +722,9 @@ void command(uint id, char *input) { handler->fn(id, input); } -void commandCompleteAdd(void) { +void commandCompletion(void) { for (size_t i = 0; i < ARRAY_LEN(Commands); ++i) { - completeAdd(None, Commands[i].cmd, Default); + if (!commandAvailable(&Commands[i])) continue; + completePush(None, Commands[i].cmd, Default); } } |