feat: add /me actions, :nick, :info commands, and @mention highlighting

New chat features:
- /me <action>: send emote-style action messages (displayed in italic)
- :nick <name>: 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.
This commit is contained in:
m1ngsama 2026-04-19 14:17:15 +08:00
parent c5d4a41fb4
commit cc7a3b9942
2 changed files with 88 additions and 4 deletions

View file

@ -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 <name>\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 <name> - 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 <action> 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';

View file

@ -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);
}