From 9b54047bfcef30b8ba45d4992dada394d38731fb Mon Sep 17 00:00:00 2001
From: Alcor <alcor@tilde.club>
Date: Fri, 8 May 2026 20:41:50 +0200
Subject: [PATCH 09/19] add /wallops command (v2)

Special thanks to RektIRC for help with testing.
---
 catgirl.1 |  2 ++
 chat.h    |  1 +
 command.c | 34 +++++++++++++++++++++++++++++-----
 input.c   |  4 ++++
 4 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/catgirl.1 b/catgirl.1
index 9f8ceeb..eb644a6 100644
--- a/catgirl.1
+++ b/catgirl.1
@@ -811,6 +811,8 @@ Remove masks from the channel ban exception list.
 Remove masks from the channel invite list.
 .It Ic /voice Op Ar nick ...
 Grant users or yourself voice in the channel.
+.It Ic /wallops Ar message
+Send a WALLOPS.
 .El
 .
 .Sh KEY BINDINGS
diff --git a/chat.h b/chat.h
index 14cb676..06382b3 100644
--- a/chat.h
+++ b/chat.h
@@ -303,6 +303,7 @@ void command(uint id, char *input);
 const char *commandIsPrivmsg(uint id, const char *input);
 const char *commandIsNotice(uint id, const char *input);
 const char *commandIsAction(uint id, const char *input);
+const char *commandIsWallops(uint id, const char *input);
 size_t commandWillSplit(uint id, const char *input);
 void commandCompletion(void);
 
diff --git a/command.c b/command.c
index 351c4fe..f187719 100644
--- a/command.c
+++ b/command.c
@@ -53,24 +53,33 @@ static void commandQuote(uint id, char *params) {
 
 static void echoMessage(char *cmd, uint id, char *params) {
 	if (!params) return;
-	ircFormat("%s %s :%s\r\n", cmd, idNames[id], params);
+	bool wallops = (*cmd == 'W');
+	if (wallops) {
+		ircFormat("%s :%s\r\n", cmd, params);
+		return; /* no echo */
+	} else {
+		ircFormat("%s %s :%s\r\n", cmd, idNames[id], params);
+	}
 	struct Message msg = {
 		.nick = self.nick,
 		.user = self.user,
 		.cmd = cmd,
-		.params[0] = idNames[id],
-		.params[1] = params,
+		.params[0] = (wallops ? params : idNames[id]),
+		.params[1] = (wallops ? NULL : params),
 	};
 	handle(&msg);
 }
 
 static int splitChunk(const char *cmd, uint id) {
+	bool wallops = (*cmd == 'W');
 	int overhead = snprintf(
-		NULL, 0, ":%s!%*s@%*s %s %s :\r\n",
+		NULL, 0, ":%s!%*s@%*s %s%s%s :\r\n",
 		self.nick,
 		(self.user ? 0 : network.userLen), (self.user ?: "*"),
 		(self.host ? 0 : network.hostLen), (self.host ?: "*"),
-		cmd, idNames[id]
+		cmd,
+		(wallops ? "" : " "),
+		(wallops ? "" : idNames[id])
 	);
 	assert(overhead > 0 && overhead < 512);
 	return 512 - overhead;
@@ -315,6 +324,12 @@ static void commandVoice(uint id, char *params) {
 	}
 }
 
+static void commandWallops(uint id, char *params) {
+	(void)id;
+	if (!params) return;
+	splitMessage("WALLOPS", Network, params);
+}
+
 static void commandDevoice(uint id, char *params) {
 	channelListMode(id, '-', 'v', (params ?: self.nick));
 }
@@ -619,6 +634,7 @@ static const struct Handler {
 	{ "/unignore", commandUnignore, 0, 0 },
 	{ "/uninvex", commandUninvex, 0, 0 },
 	{ "/voice", commandVoice, 0, 0 },
+	{ "/wallops", commandWallops, Multiline, 0 },
 	{ "/whois", commandWhois, 0, 0 },
 	{ "/whowas", commandWhowas, 0, 0 },
 	{ "/window", commandWindow, 0, 0 },
@@ -650,6 +666,12 @@ const char *commandIsAction(uint id, const char *input) {
 	return &input[4];
 }
 
+const char *commandIsWallops(uint id, const char *input) {
+	if (id == Debug) return NULL;
+	if (strncmp(input, "/wallops ", 9)) return NULL;
+	return &input[9];
+}
+
 size_t commandWillSplit(uint id, const char *input) {
 	int chunk;
 	const char *params;
@@ -659,6 +681,8 @@ size_t commandWillSplit(uint id, const char *input) {
 		chunk = splitChunk("NOTICE", id);
 	} else if (NULL != (params = commandIsAction(id, input))) {
 		chunk = splitChunk("PRIVMSG \1ACTION\1", id);
+	} else if (NULL != (params = commandIsWallops(id, input))) {
+		chunk = splitChunk("WALLOPS", id);
 	} else if (id != Network && id != Debug && !strncmp(input, "/say ", 5)) {
 		params = &input[5];
 		chunk = splitChunk("PRIVMSG", id);
diff --git a/input.c b/input.c
index 5eff915..49eb1d9 100644
--- a/input.c
+++ b/input.c
@@ -184,6 +184,7 @@ void inputUpdate(void) {
 	const char *privmsg = commandIsPrivmsg(id, buf);
 	const char *notice = commandIsNotice(id, buf);
 	const char *action = commandIsAction(id, buf);
+	const char *wallops = commandIsWallops(id, buf);
 	if (privmsg) {
 		prefix = "<"; suffix = "> ";
 		skip = privmsg;
@@ -196,6 +197,9 @@ void inputUpdate(void) {
 		stylePrompt.attr |= Italic;
 		styleInput.attr |= Italic;
 		skip = action;
+	} else if (wallops) {
+		prefix = "!"; suffix = "! ";
+		skip = wallops;
 	} else if (id == Debug && buf[0] != '/') {
 		prompt = "<< ";
 		stylePrompt.fg = Gray;
-- 
2.47.3

