jcs
/wallops
/amendments
/8
irc: Simplify protocol processing, implement more action responses
jcs made amendment 8 over 2 years ago
--- irc.c Fri Feb 4 12:58:13 2022
+++ irc.c Sun Feb 6 19:30:53 2022
@@ -33,6 +33,11 @@ void irc_login(struct chatter *chatter);
void irc_process_server(struct chatter *chatter);
void irc_set_active_channel(struct chatter *chatter, char *channame);
void irc_parse_names(struct chatter *chatter, char *line);
+void irc_add_user_to_channel(struct chatter *chatter,
+ struct irc_user *user);
+void irc_remove_nick_from_channel(struct chatter *chatter, char *nick);
+void irc_parse_channel_mode_change(struct chatter *chatter, char *mode,
+ char *args);
void
irc_process(struct chatter *chatter)
@@ -126,6 +131,9 @@ irc_init(struct chatter *chatter)
void
irc_abort(struct chatter *chatter)
{
+ chatter->irc_state = IRC_STATE_DISCONNECTED;
+ irc_set_active_channel(chatter, NULL);
+
_TCPClose(&chatter->irc_close_pb, chatter->irc_stream, nil, nil,
false);
_TCPAbort(&chatter->irc_close_pb, chatter->irc_stream, nil, nil,
@@ -142,7 +150,6 @@ irc_close(struct chatter *chatter)
chatter_printf(chatter, "$B*!*$0 Disconnecting");
irc_abort(chatter);
- chatter->irc_state = IRC_STATE_DISCONNECTED;
}
short
@@ -251,9 +258,6 @@ irc_printf(struct chatter *chatter, const char *format
buf[len - 1] = '\0';
}
-#if 0
- chatter_printf(chatter, ">>>[%ld] %s", len, buf);
-#endif
irc_send(chatter, buf, len);
return len;
@@ -347,7 +351,7 @@ irc_process_server(struct chatter *chatter)
{
struct irc_msg msg;
struct irc_user *user;
- char *line;
+ char *line, *word;
size_t size, n, lastbreak;
short state;
@@ -356,124 +360,168 @@ irc_process_server(struct chatter *chatter)
return;
memset(&msg, 0, sizeof(msg));
+
+ word = strsep(&line, " ");
- for (n = 0, lastbreak = 0, state = 0; n < size; n++) {
- if (!(line[n] == ' ' || n == size - 1))
- continue;
-
- line[n] = '\0';
-
- switch (state) {
- case 0:
- if (line[0] == ':')
- /* :server.name 001 jcs :Hi -> msg.source=server.name */
- strlcpy(msg.source, line + 1, sizeof(msg.source));
- else
- /* PING :server.name -> msg.cmd=PING */
- strlcpy(msg.cmd, line, sizeof(msg.cmd));
- state++;
- break;
- case 1:
- if (isdigit(line[lastbreak]) && isdigit(line[lastbreak + 1]) &&
- isdigit(line[lastbreak + 2]))
- /* :server.name 001 jcs :Hi -> msg.code=1 */
- msg.code = atoi(line + lastbreak);
- else if (msg.cmd[0] != '\0') {
- /* PING :server.name -> msg.msg=server.name */
- if (line[lastbreak + 1] == ':')
- lastbreak++;
- strlcpy(msg.msg, line + lastbreak + 1, sizeof(msg.msg));
- goto done_parsing;
- } else
- /* :jcs!~jcs@jcs JOIN #wallops -> msg.cmd=JOIN */
- strlcpy(msg.cmd, line + lastbreak, sizeof(msg.cmd));
- state++;
- break;
- case 2:
- if (line[lastbreak] == ':') {
- /* :jcs!... QUIT :Ping timeout -> msg.msg=Ping timeout */
- strlcpy(msg.msg, line + lastbreak + 1,
- sizeof(msg.msg));
- goto done_parsing;
- }
-
- /* :server.name 251 jcs :Blah -> msg.dest=jcs */
- strlcpy(msg.dest, line + lastbreak, sizeof(msg.dest));
-
- if (line[n + 1] == ':') {
- strlcpy(msg.msg, line + n + 2, sizeof(msg.msg));
- goto done_parsing;
- }
- state++;
- break;
- case 3:
- if (line[lastbreak] != ':') {
- /* :server 480 jcs #wallops :Cannot... -> msg.channel=#wallops */
- strlcpy(msg.channel, line + lastbreak, sizeof(msg.channel));
- lastbreak = n + 1;
- }
-
- strlcpy(msg.msg, line + lastbreak +
- (line[lastbreak] == ':' ? 1 : 0), sizeof(msg.msg));
- goto done_parsing;
- }
-
- lastbreak = n + 1;
+ /* extract source before command */
+ if (word[0] == ':') {
+ /* ":server.name 001 jcs :Hi" -> msg.source=server.name */
+ strlcpy(msg.source, word + 1, sizeof(msg.source));
+ word = strsep(&line, " ");
}
-done_parsing:
- if (strcmp(msg.cmd, "PING") == 0) {
- irc_printf(chatter, "PONG :%s\r\n", msg.source);
- return;
+ /* code or a command name */
+ if (isdigit(word[0]) && isdigit(word[1]) && isdigit(word[2]))
+ /* ":server.name 001 jcs :Hi" -> msg.code=1 */
+ msg.code = atoi(word);
+ else
+ /* "PING :server.name" -> msg.cmd=PING */
+ /* ":jcs!~jcs@jcs JOIN #wallops" -> msg.cmd=JOIN */
+ strlcpy(msg.cmd, word, sizeof(msg.cmd));
+
+ /* parameters */
+
+ if (line[0] == ':') {
+ strlcpy(msg.msg, line + 1, sizeof(msg.msg));
+ goto done_parsing;
}
- if (strcmp(msg.cmd, "NOTICE") == 0) {
- chatter_printf(chatter, "%s", msg.msg);
- return;
+ word = strsep(&line, " ");
+ if (word == NULL) {
+ strlcpy(msg.msg, line + 1, sizeof(msg.msg));
+ goto done_parsing;
}
- if (strcmp(msg.cmd, "MODE") == 0) {
- chatter_printf(chatter, "$B***$0 Mode change ($B%s$0) for $B%s$0",
- msg.msg, msg.dest);
- return;
+ strlcpy(msg.dest, word, sizeof(msg.dest));
+
+ if (line[0] == ':') {
+ strlcpy(msg.msg, line + 1, sizeof(msg.msg));
+ goto done_parsing;
}
- if (strcmp(msg.cmd, "PRIVMSG") == 0) {
- user = irc_parse_user(msg.source);
- if (strcmp(msg.dest, chatter->irc_nick) == 0)
- chatter_printf(chatter, "$B[$0%s$B($0%s@%s$B)]$0$/ %s",
- user->nick, user->username, user->hostname, msg.msg);
- else if (chatter->irc_channel &&
- strcmp(msg.dest, chatter->irc_channel->name) == 0) {
- size = strlen(chatter->irc_nick);
- if (strncmp(msg.msg, chatter->irc_nick, size) == 0 &&
- (msg.msg[size] == ' ' || msg.msg[size] == ':' ||
- msg.msg[size] == ',' || msg.msg[size] == '\0'))
- chatter_printf(chatter, "<$U%s$0>$/ %s", user->nick,
+ word = strsep(&line, " ");
+ if (word == NULL) {
+ strlcpy(msg.msg, line + 1, sizeof(msg.msg));
+ goto done_parsing;
+ }
+ strlcpy(msg.channel, word, sizeof(msg.channel));
+
+ if (line[0] == ':') {
+ strlcpy(msg.msg, line + 1, sizeof(msg.msg));
+ goto done_parsing;
+ }
+ strlcpy(msg.msg, line, sizeof(msg.msg));
+
+done_parsing:
+ if (msg.cmd[0]) {
+ if (strcmp(msg.cmd, "PING") == 0) {
+ irc_printf(chatter, "PONG :%s\r\n", msg.source);
+ return;
+ }
+ if (strcmp(msg.cmd, "PRIVMSG") == 0) {
+ user = irc_parse_user(msg.source);
+ if (strcmp(msg.dest, chatter->irc_nick) == 0)
+ chatter_printf(chatter, "$B[$0%s$B($0%s@%s$B)]$0$/ %s",
+ user->nick, user->username, user->hostname, msg.msg);
+ else if (chatter->irc_channel &&
+ strcmp(msg.dest, chatter->irc_channel->name) == 0) {
+ size = strlen(chatter->irc_nick);
+ if (strncmp(msg.msg, chatter->irc_nick, size) == 0 &&
+ (msg.msg[size] == ' ' || msg.msg[size] == ':' ||
+ msg.msg[size] == ',' || msg.msg[size] == '\0')) {
+ /* highlight message */
+ chatter_printf(chatter, "<$U%s$0>$/ %s", user->nick,
+ msg.msg);
+ if (!chatter->focusable->visible)
+ notify();
+ } else
+ chatter_printf(chatter, "$/<%s> %s", user->nick,
+ msg.msg);
+ } else
+ chatter_printf(chatter, "$B[%s($0%s@%s$B):%s]$0$/ %s",
+ user->nick, user->username, user->hostname, msg.dest,
msg.msg);
+ return;
+ }
+ if (strcmp(msg.cmd, "JOIN") == 0) {
+ user = irc_parse_user(msg.source);
+ chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has joined "
+ "$B%s$0", user->nick, user->username, user->hostname,
+ msg.dest);
+ if (strcmp(user->nick, chatter->irc_nick) == 0)
+ irc_set_active_channel(chatter, msg.dest);
else
- chatter_printf(chatter, "$/<%s> %s", user->nick,
+ irc_add_user_to_channel(chatter, user);
+ return;
+ }
+ if (strcmp(msg.cmd, "PART") == 0) {
+ user = irc_parse_user(msg.source);
+ chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has left "
+ "$B%s$0", user->nick, user->username, user->hostname,
+ msg.dest);
+ if (strcmp(user->nick, chatter->irc_nick) == 0)
+ irc_set_active_channel(chatter, NULL);
+ else
+ irc_remove_nick_from_channel(chatter, user->nick);
+ return;
+ }
+ if (strcmp(msg.cmd, "QUIT") == 0) {
+ user = irc_parse_user(msg.source);
+ chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s",
+ user->nick, user->username, user->hostname, msg.msg);
+ if (strcmp(user->nick, chatter->irc_nick) == 0)
+ irc_set_active_channel(chatter, NULL);
+ else
+ irc_remove_nick_from_channel(chatter, user->nick);
+ return;
+ }
+ if (strcmp(msg.cmd, "NICK") == 0) {
+ user = irc_parse_user(msg.source);
+ chatter_printf(chatter, "$B*** %s$0 is now known as $B%s$0",
+ user->nick, msg.msg);
+ irc_remove_nick_from_channel(chatter, user->nick);
+ strlcpy(user->nick, msg.msg, sizeof(user->nick));
+ irc_add_user_to_channel(chatter, user);
+ return;
+ }
+ if (strcmp(msg.cmd, "MODE") == 0) {
+ if (strcmp(msg.dest, chatter->irc_nick) == 0)
+ chatter_printf(chatter, "$B***$0 Mode change ($B%s$0) "
+ "for user $B%s$0", msg.msg, msg.dest);
+ else {
+ /* msg.channel is mode */
+ user = irc_parse_user(msg.source);
+ chatter_printf(chatter, "$B***$0 Mode change ($B%s %s$0) "
+ "on $B%s$0 by $B%s%0", msg.channel, msg.msg, msg.dest,
+ user->nick);
+ irc_parse_channel_mode_change(chatter, msg.channel,
msg.msg);
- } else
- chatter_printf(chatter, "$B[%s($0%s@%s$B):%s]$0$/ %s",
- user->nick, user->username, user->hostname, msg.dest,
- msg.msg);
-
- return;
+ }
+ return;
+ }
+ if (strcmp(msg.cmd, "KICK") == 0) {
+ /* dest and channel are swapped */
+ user = irc_parse_user(msg.source);
+ chatter_printf(chatter, "$B*** %s ($0%s$B)$0 was kicked "
+ "from $B%s$0 by $B%s%0:$/ %s", msg.channel, msg.dest,
+ user->nick, msg.msg);
+ if (strcmp(msg.channel, chatter->irc_nick) == 0)
+ irc_set_active_channel(chatter, NULL);
+ else
+ irc_remove_nick_from_channel(chatter, msg.channel);
+ return;
+ }
+ if (strcmp(msg.cmd, "NOTICE") == 0) {
+ chatter_printf(chatter, "%s", msg.msg);
+ return;
+ }
+ if (strcmp(msg.cmd, "ERROR") == 0) {
+ chatter_printf(chatter, "$B*!* Disconnected from %s:$0$/ %s",
+ chatter->irc_hostname, msg.msg);
+ irc_abort(chatter);
+ return;
+ }
+
+ goto unknown;
}
- if (strcmp(msg.cmd, "JOIN") == 0) {
- user = irc_parse_user(msg.source);
- chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has joined $B%s$0",
- user->nick, user->username, user->hostname, msg.dest);
- if (strcmp(user->nick, chatter->irc_nick) == 0)
- irc_set_active_channel(chatter, msg.dest);
- return;
- }
- if (strcmp(msg.cmd, "PART") == 0) {
- user = irc_parse_user(msg.source);
- chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has left $B%s$0",
- user->nick, user->username, user->hostname, msg.dest);
- if (strcmp(user->nick, chatter->irc_nick) == 0)
- irc_set_active_channel(chatter, NULL);
- return;
- }
+
switch (msg.code) {
case 0:
goto unknown;
@@ -524,14 +572,17 @@ done_parsing:
/* end of WHO */
goto unknown;
case 353:
- /* NAMES output */
- if (chatter->irc_channel) /* TODO: match arg to channel name */
+ /* NAMES output, channel is first part of msg.msg, not channel */
+ if (chatter->irc_channel &&
+ strncmp(msg.msg, chatter->irc_channel->name,
+ strlen(chatter->irc_channel->name)) == 0)
irc_parse_names(chatter, msg.msg);
return;
case 366:
/* end of NAMES output */
- if (chatter->irc_channel)
- chatter_sync_nick_list(chatter);
+ if (chatter->irc_channel &&
+ strcmp(msg.channel, chatter->irc_channel->name) == 0)
+ chatter_sync_nick_list(chatter, true);
return;
case 372:
case 375:
@@ -616,7 +667,7 @@ irc_set_active_channel(struct chatter *chatter, char *
}
if (channame == NULL)
- chatter_sync_nick_list(chatter);
+ chatter_sync_nick_list(chatter, false);
else {
channel = xmalloczero(sizeof(struct irc_channel));
strlcpy(channel->name, channame, sizeof(channel->name));
@@ -672,4 +723,98 @@ irc_parse_names(struct chatter *chatter, char *line)
if (line == NULL)
break;
}
+}
+
+void
+irc_add_user_to_channel(struct chatter *chatter, struct irc_user *user)
+{
+ chatter->irc_channel->nusers++;
+ chatter->irc_channel->users =
+ xreallocarray(chatter->irc_channel->users,
+ sizeof(struct irc_channel_nick), chatter->irc_channel->nusers);
+ strlcpy(chatter->irc_channel->users[
+ chatter->irc_channel->nusers - 1].nick, user->nick,
+ sizeof(chatter->irc_channel->users[0].nick));
+ chatter_sync_nick_list(chatter, false);
+}
+
+void
+irc_remove_nick_from_channel(struct chatter *chatter, char *nick)
+{
+ size_t n;
+
+ for (n = 0; n < chatter->irc_channel->nusers; n++) {
+ if (strcmp(chatter->irc_channel->users[n].nick, nick) == 0) {
+ LDelRow(1, n, chatter->nick_list);
+
+ /* move everyone else up */
+ for (; n < chatter->irc_channel->nusers - 1; n++)
+ chatter->irc_channel->users[n] =
+ chatter->irc_channel->users[n + 1];
+ break;
+ }
+ }
+
+ chatter->irc_channel->nusers--;
+ chatter->irc_channel->users =
+ xreallocarray(chatter->irc_channel->users,
+ sizeof(struct irc_channel_nick), chatter->irc_channel->nusers);
+}
+
+void
+irc_parse_channel_mode_change(struct chatter *chatter, char *mode,
+ char *args)
+{
+ size_t len;
+ struct irc_channel_nick *cnick;
+ char *user;
+ short n, j;
+ bool add = false, need_resync = false;
+
+ len = strlen(mode);
+
+ /* mode:"+mvo-vo" args:"nick1 nick2 nick3 nick4" */
+ for (n = 0; n < len; n++) {
+ switch (mode[n]) {
+ case '+':
+ add = true;
+ break;
+ case '-':
+ add = false;
+ break;
+ case 'v':
+ case 'o':
+ user = strsep(&args, " ");
+ if (user == NULL)
+ user = args;
+ for (j = 0; j < chatter->irc_channel->nusers; j++) {
+ cnick = &chatter->irc_channel->users[j];
+ if (strcmp(cnick->nick, user) != 0)
+ continue;
+
+ if (mode[n] == 'o') {
+ if (add)
+ cnick->flags |= IRC_NICK_FLAG_OP;
+ else
+ cnick->flags &= ~IRC_NICK_FLAG_OP;
+ } else if (mode[n] == 'v') {
+ if (add)
+ cnick->flags |= IRC_NICK_FLAG_VOICE;
+ else
+ cnick->flags &= ~IRC_NICK_FLAG_VOICE;
+ }
+
+ need_resync = true;
+ break;
+ }
+
+ break;
+ default:
+ /* some other channel mode */
+ break;
+ }
+ }
+
+ if (need_resync)
+ chatter_sync_nick_list(chatter, false);
}