From cc7a3b994277e8acd6a2cc0d88f400612e001559 Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Sun, 19 Apr 2026 14:17:15 +0800 Subject: [PATCH] feat: add /me actions, :nick, :info commands, and @mention highlighting New chat features: - /me : send emote-style action messages (displayed in italic) - :nick : change username mid-session with broadcast notification - :info: show connection details (IP, terminal size, server uptime, version) - @username mentions: messages containing @yourname are rendered in bold Updated :help command to document all new commands. --- src/ssh_server.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++-- src/tui.c | 16 +++++++++- 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/ssh_server.c b/src/ssh_server.c index 792b6ce..db54803 100644 --- a/src/ssh_server.c +++ b/src/ssh_server.c @@ -1131,15 +1131,79 @@ static void execute_command(client_t *client) { "========================================\n" "* = you / 你\n"); + } else if (strncmp(cmd, "nick ", 5) == 0) { + char *new_nick = cmd + 5; + while (*new_nick == ' ') new_nick++; + if (new_nick[0] == '\0') { + buffer_appendf(output, sizeof(output), &pos, + "Usage: nick \n"); + } else { + char trimmed[MAX_USERNAME_LEN]; + strncpy(trimmed, new_nick, sizeof(trimmed) - 1); + trimmed[sizeof(trimmed) - 1] = '\0'; + if (utf8_strlen(trimmed) > 20) { + utf8_truncate(trimmed, 20); + } + if (!is_valid_username(trimmed)) { + buffer_appendf(output, sizeof(output), &pos, + "Invalid username / 无效用户名\n"); + } else { + char old_name[MAX_USERNAME_LEN]; + strncpy(old_name, client->username, sizeof(old_name) - 1); + old_name[sizeof(old_name) - 1] = '\0'; + strncpy(client->username, trimmed, MAX_USERNAME_LEN - 1); + client->username[MAX_USERNAME_LEN - 1] = '\0'; + message_t nick_msg = { .timestamp = time(NULL) }; + strncpy(nick_msg.username, "system", MAX_USERNAME_LEN - 1); + snprintf(nick_msg.content, MAX_MESSAGE_LEN, + "%s -> %s", old_name, trimmed); + room_broadcast(g_room, &nick_msg); + buffer_appendf(output, sizeof(output), &pos, + "Username changed: %s -> %s\n", old_name, trimmed); + } + } + + } else if (strcmp(cmd, "info") == 0) { + time_t now = time(NULL); + time_t uptime = now - g_server_start_time; + int days = uptime / 86400; + int hours = (uptime % 86400) / 3600; + int mins = (uptime % 3600) / 60; + + buffer_appendf(output, sizeof(output), &pos, + "========================================\n" + " Connection Info\n" + "========================================\n" + "Username: %s\n" + "SSH login: %s\n" + "Client IP: %s\n" + "Terminal: %dx%d\n" + "Server uptime: %dd %dh %dm\n" + "Online: %d user(s)\n" + "Messages: %d\n" + "Version: TNT %s\n" + "========================================\n", + client->username, + client->ssh_login[0] ? client->ssh_login : "(none)", + client->client_ip[0] ? client->client_ip : "(unknown)", + client->width, client->height, + days, hours, mins, + room_get_client_count(g_room), + room_get_message_count(g_room), + TNT_VERSION); + } else if (strcmp(cmd, "help") == 0 || strcmp(cmd, "commands") == 0) { buffer_appendf(output, sizeof(output), &pos, "========================================\n" " Available Commands\n" "========================================\n" "list, users, who - Show online users\n" + "nick - Change your username\n" + "info - Show connection info\n" "help, commands - Show this help\n" "clear, cls - Clear command output\n" - "========================================\n"); + "========================================\n" + "In chat: /me for emotes\n"); } else if (strcmp(cmd, "clear") == 0 || strcmp(cmd, "cls") == 0) { buffer_appendf(output, sizeof(output), &pos, "Command output cleared\n"); @@ -1271,8 +1335,14 @@ static bool handle_key(client_t *client, unsigned char key, char *input) { message_t msg = { .timestamp = time(NULL), }; - snprintf(msg.username, sizeof(msg.username), "%s", client->username); - snprintf(msg.content, sizeof(msg.content), "%s", input); + if (strncmp(input, "/me ", 4) == 0) { + snprintf(msg.username, sizeof(msg.username), "*"); + snprintf(msg.content, sizeof(msg.content), "%s %s", + client->username, input + 4); + } else { + snprintf(msg.username, sizeof(msg.username), "%s", client->username); + snprintf(msg.content, sizeof(msg.content), "%s", input); + } room_broadcast(g_room, &msg); message_save(&msg); input[0] = '\0'; diff --git a/src/tui.c b/src/tui.c index 0af8d95..bbfc332 100644 --- a/src/tui.c +++ b/src/tui.c @@ -153,10 +153,24 @@ void tui_render_screen(client_t *client) { /* Render messages from snapshot */ if (msg_snapshot) { + char at_mention[MAX_USERNAME_LEN + 2]; + snprintf(at_mention, sizeof(at_mention), "@%s", client->username); + for (int i = 0; i < snapshot_count; i++) { char msg_line[1024]; message_format(&msg_snapshot[i], msg_line, sizeof(msg_line), client->width); - buffer_appendf(buffer, buf_size, &pos, "%s\033[K\r\n", msg_line); + bool mentioned = (strstr(msg_snapshot[i].content, at_mention) != NULL); + bool is_action = (msg_snapshot[i].username[0] == '*' && + msg_snapshot[i].username[1] == '\0'); + if (mentioned) { + buffer_appendf(buffer, buf_size, &pos, + ANSI_BOLD "%s" ANSI_RESET "\033[K\r\n", msg_line); + } else if (is_action) { + buffer_appendf(buffer, buf_size, &pos, + "\033[3m%s\033[0m\033[K\r\n", msg_line); + } else { + buffer_appendf(buffer, buf_size, &pos, "%s\033[K\r\n", msg_line); + } } free(msg_snapshot); }