diff --git a/CHANGELOG.md b/CHANGELOG.md index 5543292..9d246d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ -2026.07.24 - Secure file ops, TLS hardening, input validation, security docs. (0.40.7) +26.07.24 - Nick-prefixed channel replies for all non-admin commands. (0.40.8) + +- Added `irc_reply()`: a new IRC response helper that automatically prepends the sender's nick to channel messages (e.g. `"nick: pong!"`), while leaving private-message responses unmodified. This makes it clear which user the bot is addressing in busy channels. +- Converted all non-admin command handlers to use `irc_reply()` instead of raw `irc_msg()`: `!ping`, `!zynk`, `!weather`, `!forecast`, `!stock`, `!calc`, `!time`, `!seen`, `!tell`, `!gitlog`, `!changelog`, `!ai`, `!uptime`, `!version`, `!help`, greeting/chat responses, and the `"Not implemented..."` fallback for unknown `!commands`. +- Converted AI async responses (`ai_check_completion()`) to use `irc_reply()`, so AI answers delivered after the original query also show the requesting user's nick. +- Converted AI error messages in `ai_ask()` (rate-limit busy, fork failure, start failure) to use `irc_reply()`. +- Removed redundant manual nick prefixes from commands that already embedded the sender's nick in their response text: `!ping` (`"nick: pong!"`), `!calc` (`"nick: result"`), `!time` (`"nick: 2026-07-24 ..."`), `!uptime` (`"nick: up 3d ..."`), and greeting/chat responses (`"Hi, nick!"` → `"nick: Hi!"`). +- Admin commands (`!quit`, `!reload`, `!restart`, `!rebuild`, `!code`) are intentionally excluded — these are privileged operations where the nick prefix adds no value. +- The `tell_deliver()` function retains its own existing format and was not modified. + +26.07.24 - Secure file ops, TLS hardening, input validation, security docs. (0.40.7) - Added `secure_state_open()`: hardened file open with O_CLOEXEC, O_NOFOLLOW, mode 0600, regular-file and ownership checks. Prevents symlink attacks, TOCTOU races, and world-writable temp files. - Replaced all `fopen()` calls for temp state files (`/tmp/zynk_reload`, `/tmp/zynk_code_restart`, `/tmp/zynk_make_err`, `/tmp/zynk_rebuild_err`) with `secure_state_open()` + `fdopen()` across hot reload, compile-restart, and rebuild paths. diff --git a/CMakeLists.txt b/CMakeLists.txt index 70ef547..548f376 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.13) -project(zynk VERSION 0.40.7 LANGUAGES C) +project(zynk VERSION 0.40.8 LANGUAGES C) # Prefer C99; the code is compatible with C99/C11 set(CMAKE_C_STANDARD 99) diff --git a/ai.h b/ai.h index 177e433..e635935 100644 --- a/ai.h +++ b/ai.h @@ -234,17 +234,17 @@ void ai_child_task(Session *s, long long id, const char *agent) { void ai_ask(Session *s, const char *question, const char *reply_target, const char *src_nick, const char *agent) { if (rate_limit_check(src_nick, reply_target) < 0) return; if (pending_ai_pid > 0) { - irc_msg(s, reply_target, "Already processing an AI query, please wait."); + irc_reply(s, reply_target, src_nick, "Already processing an AI query, please wait."); return; } db_update_lastq(question); long long id = 0; if (ai_start(question, reply_target, src_nick, &id) != 0) { - irc_msg(s, reply_target, "Error starting AI query"); + irc_reply(s, reply_target, src_nick, "Error starting AI query"); return; } pid_t pid = fork(); - if (pid == -1) { ai_reset(id); irc_msg(s, reply_target, "Error forking"); return; } + if (pid == -1) { ai_reset(id); irc_reply(s, reply_target, src_nick, "Error forking"); return; } if (pid == 0) { ai_child_task(s, id, agent); } pending_ai_pid = pid; pending_ai_id = id; @@ -294,7 +294,7 @@ void ai_check_completion(Session *s) { while (pos < alen) { size_t end = pos + AI_CHUNK_MAX; if (end >= alen) { - irc_msg(s, target, answer + pos); + irc_reply(s, target, nick, answer + pos); chunks++; break; } @@ -302,7 +302,7 @@ void ai_check_completion(Session *s) { if (end <= pos) end = pos + AI_CHUNK_MAX; char save = answer[end]; answer[end] = 0; - irc_msg(s, target, answer + pos); + irc_reply(s, target, nick, answer + pos); answer[end] = save; pos = end + 1; chunks++; @@ -327,11 +327,11 @@ void ai_check_completion(Session *s) { if (target) { char msg[96]; snprintf(msg, sizeof msg, "AI query timed out after %d seconds.", AI_TIMEOUT); - irc_msg(s, target, msg); + irc_reply(s, target, nick, msg); log_stamp(); fprintf(stderr, CLR_RED "AI done PID %d from %s: timed out for %s" CLR_RESET "\n", done_pid, nick ? nick : "?", target); } } else if (target) { - irc_msg(s, target, "AI query failed."); + irc_reply(s, target, nick, "AI query failed."); log_stamp(); fprintf(stderr, CLR_RED "AI done PID %d from %s: failed for %s" CLR_RESET "\n", done_pid, nick ? nick : "?", target); } free(target); diff --git a/cmd_ai.h b/cmd_ai.h index 1537fa5..0b9db81 100644 --- a/cmd_ai.h +++ b/cmd_ai.h @@ -24,7 +24,7 @@ int cmd_ai(Session *s, const char *msg, const char *reply_target, const char *sr const char *rest = msg[3] == ' ' ? msg + 4 : ""; while (*rest == ' ') rest++; if (!*rest) { - irc_msg(s, reply_target, "Usage: !ai "); + irc_reply(s, reply_target, src_nick, "Usage: !ai "); return 1; } char question[2048]; diff --git a/cmd_calc.h b/cmd_calc.h index ed5ed4b..5c47af4 100644 --- a/cmd_calc.h +++ b/cmd_calc.h @@ -26,24 +26,24 @@ int cmd_calc(Session *s, const char *msg, const char *reply_target, const char * const char *rest = msg[5] == ' ' ? msg + 6 : ""; while (*rest == ' ') rest++; if (!*rest) { - irc_msg(s, reply_target, "Usage: !calc (e.g. !calc 2+2, !calc (3*7)+1)"); + irc_reply(s, reply_target, src_nick, "Usage: !calc (e.g. !calc 2+2, !calc (3*7)+1)"); return 1; } size_t expr_len = strlen(rest); if (expr_len > 256) { - irc_msg(s, reply_target, "Calc: expression too long"); + irc_reply(s, reply_target, src_nick, "Calc: expression too long"); return 1; } for (const unsigned char *p = (const unsigned char *)rest; *p; p++) { if (!isdigit(*p) && !strchr(".()+-*/%^ \t", *p)) { - irc_msg(s, reply_target, "Calc: only numeric expressions are allowed"); + irc_reply(s, reply_target, src_nick, "Calc: only numeric expressions are allowed"); return 1; } } int in_pipe[2], out_pipe[2]; - if (pipe(in_pipe) < 0 || pipe(out_pipe) < 0) { irc_msg(s, reply_target, "Calc: internal error"); return 1; } + if (pipe(in_pipe) < 0 || pipe(out_pipe) < 0) { irc_reply(s, reply_target, src_nick, "Calc: internal error"); return 1; } pid_t pid = fork(); - if (pid < 0) { close(in_pipe[0]); close(in_pipe[1]); close(out_pipe[0]); close(out_pipe[1]); irc_msg(s, reply_target, "Calc: fork failed"); return 1; } + if (pid < 0) { close(in_pipe[0]); close(in_pipe[1]); close(out_pipe[0]); close(out_pipe[1]); irc_reply(s, reply_target, src_nick, "Calc: fork failed"); return 1; } if (pid == 0) { close(in_pipe[1]); close(out_pipe[0]); @@ -62,7 +62,7 @@ int cmd_calc(Session *s, const char *msg, const char *reply_target, const char * close(out_pipe[0]); kill(pid, SIGTERM); waitpid(pid, NULL, 0); - irc_msg(s, reply_target, "Calc: evaluation failed"); + irc_reply(s, reply_target, src_nick, "Calc: evaluation failed"); return 1; } close(in_pipe[1]); @@ -85,11 +85,9 @@ int cmd_calc(Session *s, const char *msg, const char *reply_target, const char * char *nl = strchr(buf, '\n'); if (nl) *nl = 0; if (len == 0) { - irc_msg(s, reply_target, "Calc: no result (check expression syntax)"); + irc_reply(s, reply_target, src_nick, "Calc: no result (check expression syntax)"); } else { - char resp[640]; - snprintf(resp, sizeof resp, "%s: %s", src_nick, buf); - irc_msg(s, reply_target, resp); + irc_reply(s, reply_target, src_nick, buf); } log_stamp(); fprintf(stderr, CLR_YELLOW "CALC %s from %s:" CLR_RESET " %s\n", reply_target, src_nick, rest); return 1; diff --git a/cmd_changelog.h b/cmd_changelog.h index 0c3dd57..f35fffd 100644 --- a/cmd_changelog.h +++ b/cmd_changelog.h @@ -68,7 +68,7 @@ int cmd_changelog(Session *s, const char *msg, const char *reply_target, const c pclose(fp); } FILE *f = fopen("CHANGELOG.md", "r"); - if (!f) { irc_msg(s, reply_target, "changelog: CHANGELOG.md not found"); return 1; } + if (!f) { irc_reply(s, reply_target, src_nick, "changelog: CHANGELOG.md not found"); return 1; } char *line = NULL; size_t cap = 0; const char *headers[256]; @@ -89,16 +89,16 @@ int cmd_changelog(Session *s, const char *msg, const char *reply_target, const c int showed = count < total ? count : total; struct timespec ts_msg = { .tv_sec = 0, .tv_nsec = 200000000 }; for (int i = 0; i < showed; i++) { - irc_msg(s, reply_target, headers[i]); + irc_reply(s, reply_target, src_nick, headers[i]); free((void *)headers[i]); if (i + 1 < showed) nanosleep(&ts_msg, NULL); } for (int i = showed; i < total; i++) free((void *)headers[i]); - if (total == 0) { irc_msg(s, reply_target, "changelog: no entries found"); return 1; } + if (total == 0) { irc_reply(s, reply_target, src_nick, "changelog: no entries found"); return 1; } if (*remote_url) { char link[1024]; snprintf(link, sizeof link, "Full changelog: %s/blob/master/CHANGELOG.md", remote_url); - irc_msg(s, reply_target, link); + irc_reply(s, reply_target, src_nick, link); } log_stamp(); fprintf(stderr, CLR_YELLOW "CHANGELOG %s from %s (count=%d, showed=%d)" CLR_RESET "\n", reply_target, src_nick, count, showed); return 1; diff --git a/cmd_forecast.h b/cmd_forecast.h index 5740ea4..8cef36e 100644 --- a/cmd_forecast.h +++ b/cmd_forecast.h @@ -347,7 +347,7 @@ int cmd_forecast(Session *s, const char *msg, const char *reply_target, const ch if (body) { char *line = strtok(body, "\n"); while (line) { - irc_msg(s, reply_target, line); + irc_reply(s, reply_target, src_nick, line); line = strtok(NULL, "\n"); if (line) { struct timespec ts_msg = { .tv_sec = 0, .tv_nsec = 200000000 }; @@ -356,7 +356,7 @@ int cmd_forecast(Session *s, const char *msg, const char *reply_target, const ch } free(body); } else { - irc_msg(s, reply_target, "Forecast: could not fetch data"); + irc_reply(s, reply_target, src_nick, "Forecast: could not fetch data"); } } else { char *body = fetch_forecast(city_url, city); @@ -367,7 +367,7 @@ int cmd_forecast(Session *s, const char *msg, const char *reply_target, const ch } else { snprintf(resp, sizeof resp, "Forecast: could not fetch data"); } - irc_msg(s, reply_target, resp); + irc_reply(s, reply_target, src_nick, resp); } log_stamp(); fprintf(stderr, CLR_MAGENTA "FORECAST %s from %s (full=%d):" CLR_RESET " %s\n", reply_target, src_nick, full, city); return 1; diff --git a/cmd_gitlog.h b/cmd_gitlog.h index 73ac93e..2768560 100644 --- a/cmd_gitlog.h +++ b/cmd_gitlog.h @@ -73,9 +73,9 @@ int cmd_gitlog(Session *s, const char *msg, const char *reply_target, const char pclose(fp); } int out_pipe[2]; - if (pipe(out_pipe) < 0) { irc_msg(s, reply_target, "gitlog: internal error"); return 1; } + if (pipe(out_pipe) < 0) { irc_reply(s, reply_target, src_nick, "gitlog: internal error"); return 1; } pid_t pid = fork(); - if (pid < 0) { close(out_pipe[0]); close(out_pipe[1]); irc_msg(s, reply_target, "gitlog: fork failed"); return 1; } + if (pid < 0) { close(out_pipe[0]); close(out_pipe[1]); irc_reply(s, reply_target, src_nick, "gitlog: fork failed"); return 1; } if (pid == 0) { close(out_pipe[0]); dup2(out_pipe[1], STDOUT_FILENO); @@ -105,7 +105,7 @@ int cmd_gitlog(Session *s, const char *msg, const char *reply_target, const char while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--; buf[len] = 0; if (len == 0) { - irc_msg(s, reply_target, "gitlog: no commits found"); + irc_reply(s, reply_target, src_nick, "gitlog: no commits found"); return 1; } char *line = strtok(buf, "\n"); @@ -133,17 +133,17 @@ int cmd_gitlog(Session *s, const char *msg, const char *reply_target, const char if (*remote_url) { char link[4096]; snprintf(link, sizeof link, "%s - %s/commit/%s", display, remote_url, hash); - irc_msg(s, reply_target, link); + irc_reply(s, reply_target, src_nick, link); } else { - irc_msg(s, reply_target, display); + irc_reply(s, reply_target, src_nick, display); } } else { collapse_spaces(rest); - if (*rest) irc_msg(s, reply_target, rest); + if (*rest) irc_reply(s, reply_target, src_nick, rest); } } else { collapse_spaces(line); - if (*line) irc_msg(s, reply_target, line); + if (*line) irc_reply(s, reply_target, src_nick, line); } line = strtok(NULL, "\n"); } diff --git a/cmd_greeting_or_chat.h b/cmd_greeting_or_chat.h index 5d6ad70..4cc7b86 100644 --- a/cmd_greeting_or_chat.h +++ b/cmd_greeting_or_chat.h @@ -32,24 +32,24 @@ void cmd_greeting_or_chat(Session *s, const char *msg, const char *reply_target, char resp[1024]; int matched = 0; if (strcasecmp(cmd, "hi") == 0 || strcasecmp(cmd, "hey") == 0 || strcasecmp(cmd, "hello") == 0) { - snprintf(resp, sizeof resp, "Hi, %s!", src_nick); matched = 1; + snprintf(resp, sizeof resp, "Hi!"); matched = 1; } else if (strcasecmp(cmd, "bye") == 0 || strcasecmp(cmd, "goodbye") == 0) { - snprintf(resp, sizeof resp, "Bye, %s!", src_nick); matched = 1; + snprintf(resp, sizeof resp, "Bye!"); matched = 1; } else if (strcasecmp(cmd, "jojo") == 0) { - snprintf(resp, sizeof resp, "Jo, %s!", src_nick); matched = 1; + snprintf(resp, sizeof resp, "Jo!"); matched = 1; } else if (strcasecmp(cmd, "whatsup") == 0) { - snprintf(resp, sizeof resp, "Not much, %s!", src_nick); matched = 1; + snprintf(resp, sizeof resp, "Not much!"); matched = 1; } else if (strcasecmp(cmd, "waddup") == 0) { - snprintf(resp, sizeof resp, "Yo %s!", src_nick); matched = 1; + snprintf(resp, sizeof resp, "Yo!"); matched = 1; } else if (strcasecmp(cmd, "waddup yo") == 0) { snprintf(resp, sizeof resp, "Yo!"); matched = 1; } else if (strcasecmp(cmd, "deine muddi") == 0 || strcasecmp(cmd, "deine mutter") == 0) { - snprintf(resp, sizeof resp, "Ey, %s, lass das!", src_nick); matched = 1; + snprintf(resp, sizeof resp, "Ey, lass das!"); matched = 1; } else if (strcasecmp(cmd, "penis") == 0) { snprintf(resp, sizeof resp, "..."); matched = 1; } if (matched) { - irc_msg(s, reply_target, resp); + irc_reply(s, reply_target, src_nick, resp); log_stamp(); fprintf(stderr, CLR_CYAN "CHAT %s from %s:" CLR_RESET " %s\n", reply_target, src_nick, resp); return; } diff --git a/cmd_help.h b/cmd_help.h index 9e3463a..6c568c7 100644 --- a/cmd_help.h +++ b/cmd_help.h @@ -105,7 +105,7 @@ int cmd_help(Session *s, const char *msg, const char *reply_target, const char * p += n; rem -= n; #endif if (p > buf && p[-1] == ',') p[-1] = '\0'; - irc_msg(s, reply_target, buf); + irc_reply(s, reply_target, src_nick, buf); log_stamp(); fprintf(stderr, CLR_CYAN "HELP from %s in %s" CLR_RESET "\n", src_nick, reply_target); return 1; } diff --git a/cmd_ping.h b/cmd_ping.h index dd13d5f..5f74cee 100644 --- a/cmd_ping.h +++ b/cmd_ping.h @@ -21,8 +21,7 @@ int cmd_ping(Session *s, const char *msg, const char *reply_target, const char * if (strncmp(msg, "!ping", 5) != 0) return 0; if (msg[5] != ' ' && msg[5] != '\0') return 0; if (rate_limit_check(src_nick, reply_target) < 0) return 1; - char pong_buf[96]; snprintf(pong_buf, sizeof pong_buf, "%s: pong!", src_nick); - irc_msg(s, reply_target, pong_buf); + irc_reply(s, reply_target, src_nick, "pong!"); log_stamp(); fprintf(stderr, CLR_YELLOW "PING from %s in %s" CLR_RESET "\n", src_nick, reply_target); return 1; } diff --git a/cmd_seen.h b/cmd_seen.h index f0e12fc..887561b 100644 --- a/cmd_seen.h +++ b/cmd_seen.h @@ -27,7 +27,7 @@ int cmd_seen(Session *s, const char *msg, const char *reply_target, const char * const char *rest = msg + 6; while (*rest == ' ') rest++; if (!*rest) { - irc_msg(s, reply_target, "Usage: !seen "); + irc_reply(s, reply_target, src_nick, "Usage: !seen "); return 1; } char target[32]; @@ -35,21 +35,21 @@ int cmd_seen(Session *s, const char *msg, const char *reply_target, const char * while (*rest && *rest != ' ' && i < 31) target[i++] = *rest++; target[i] = 0; if (strcasecmp(target, src_nick) == 0) { - irc_msg(s, reply_target, "You are right here, duh!"); + irc_reply(s, reply_target, src_nick, "You are right here, duh!"); return 1; } if (strcasecmp(target, s->nick) == 0) { - irc_msg(s, reply_target, "I am right here!"); + irc_reply(s, reply_target, src_nick, "I am right here!"); return 1; } char *result = seen_lookup(target); if (result) { - irc_msg(s, reply_target, result); + irc_reply(s, reply_target, src_nick, result); free(result); } else { char resp[128]; snprintf(resp, sizeof resp, "I have never seen %s", target); - irc_msg(s, reply_target, resp); + irc_reply(s, reply_target, src_nick, resp); } log_stamp(); fprintf(stderr, CLR_CYAN "SEEN %s from %s in %s" CLR_RESET "\n", target, src_nick, reply_target); return 1; diff --git a/cmd_stock.h b/cmd_stock.h index 25dc20f..1dd3b08 100644 --- a/cmd_stock.h +++ b/cmd_stock.h @@ -27,7 +27,7 @@ int cmd_stock(Session *s, const char *msg, const char *reply_target, const char const char *rest = msg[6] == ' ' ? msg + 7 : ""; while (*rest == ' ') rest++; if (!*rest) { - irc_msg(s, reply_target, "Usage: !stock [,SYMBOL...] (max 3, e.g. !stock AAPL or !stock AAPL,MSFT,GOOGL)"); + irc_reply(s, reply_target, src_nick, "Usage: !stock [,SYMBOL...] (max 3, e.g. !stock AAPL or !stock AAPL,MSFT,GOOGL)"); return 1; } char buf[2048]; @@ -103,7 +103,7 @@ int cmd_stock(Session *s, const char *msg, const char *reply_target, const char } if (!buf[0]) snprintf(buf, sizeof buf, "Stock: could not fetch data"); collapse_spaces(buf); - irc_msg(s, reply_target, buf); + irc_reply(s, reply_target, src_nick, buf); log_stamp(); fprintf(stderr, CLR_MAGENTA "STOCK %s from %s:" CLR_RESET " %s\n", reply_target, src_nick, buf); return 1; } diff --git a/cmd_tell.h b/cmd_tell.h index 9872414..9dd435c 100644 --- a/cmd_tell.h +++ b/cmd_tell.h @@ -26,7 +26,7 @@ int cmd_tell(Session *s, const char *msg, const char *reply_target, const char * const char *rest = msg + 6; while (*rest == ' ') rest++; if (!*rest) { - irc_msg(s, reply_target, "Usage: !tell "); + irc_reply(s, reply_target, src_nick, "Usage: !tell "); return 1; } char target[32]; @@ -35,17 +35,17 @@ int cmd_tell(Session *s, const char *msg, const char *reply_target, const char * target[i] = 0; while (*rest == ' ') rest++; if (!*rest) { - irc_msg(s, reply_target, "Usage: !tell "); + irc_reply(s, reply_target, src_nick, "Usage: !tell "); return 1; } if (strcasecmp(target, src_nick) == 0) { - irc_msg(s, reply_target, "You can't tell yourself something!"); + irc_reply(s, reply_target, src_nick, "You can't tell yourself something!"); return 1; } tell_add(src_nick, target, rest); char resp[256]; snprintf(resp, sizeof resp, "Ok, I'll tell %s when they're around.", target); - irc_msg(s, reply_target, resp); + irc_reply(s, reply_target, src_nick, resp); log_stamp(); fprintf(stderr, CLR_CYAN "TELL %s from %s in %s:" CLR_RESET " -> %s: %s\n", reply_target, src_nick, reply_target, target, rest); return 1; } diff --git a/cmd_time.h b/cmd_time.h index 5360631..d94b835 100644 --- a/cmd_time.h +++ b/cmd_time.h @@ -117,7 +117,6 @@ int cmd_time(Session *s, const char *msg, const char *reply_target, const char * if (strncmp(msg, "!time", 5) != 0) return 0; if (msg[5] != ' ' && msg[5] != '\0') return 0; if (rate_limit_check(src_nick, reply_target) < 0) return 1; - char resp[256]; const char *rest = msg[5] == ' ' ? msg + 6 : ""; while (*rest == ' ') rest++; if (*rest) { @@ -145,7 +144,7 @@ int cmd_time(Session *s, const char *msg, const char *reply_target, const char * if (system_works) { char buf[64]; strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", &local_tm); - snprintf(resp, sizeof resp, "%s: %s", src_nick, buf); + irc_reply(s, reply_target, src_nick, buf); } else { const TzEntry *tz = tz_lookup(tzname); if (tz) { @@ -159,9 +158,11 @@ int cmd_time(Session *s, const char *msg, const char *reply_target, const char * } char buf[128]; tz_format(now, off, tzname, off != tz->off, buf, sizeof buf); - snprintf(resp, sizeof resp, "%s: %s", src_nick, buf); + irc_reply(s, reply_target, src_nick, buf); } else { - snprintf(resp, sizeof resp, "%s: Unknown timezone: %s", src_nick, tzname); + char resp[256]; + snprintf(resp, sizeof resp, "Unknown timezone: %s", tzname); + irc_reply(s, reply_target, src_nick, resp); } } } else { @@ -169,9 +170,8 @@ int cmd_time(Session *s, const char *msg, const char *reply_target, const char * struct tm *tm = localtime(&now); char buf[64]; strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", tm); - snprintf(resp, sizeof resp, "%s: %s", src_nick, buf); + irc_reply(s, reply_target, src_nick, buf); } - irc_msg(s, reply_target, resp); log_stamp(); fprintf(stderr, CLR_YELLOW "TIME from %s in %s" CLR_RESET "\n", src_nick, reply_target); return 1; } diff --git a/cmd_uptime.h b/cmd_uptime.h index d011a5f..bffc10f 100644 --- a/cmd_uptime.h +++ b/cmd_uptime.h @@ -28,14 +28,14 @@ int cmd_uptime(Session *s, const char *msg, const char *reply_target, const char long secs = diff % 60; char buf[128]; if (days > 0) - snprintf(buf, sizeof buf, "%s: up %ldd %ldh %ldm %lds", src_nick, days, hours, mins, secs); + snprintf(buf, sizeof buf, "up %ldd %ldh %ldm %lds", days, hours, mins, secs); else if (hours > 0) - snprintf(buf, sizeof buf, "%s: up %ldh %ldm %lds", src_nick, hours, mins, secs); + snprintf(buf, sizeof buf, "up %ldh %ldm %lds", hours, mins, secs); else if (mins > 0) - snprintf(buf, sizeof buf, "%s: up %ldm %lds", src_nick, mins, secs); + snprintf(buf, sizeof buf, "up %ldm %lds", mins, secs); else - snprintf(buf, sizeof buf, "%s: up %lds", src_nick, secs); - irc_msg(s, reply_target, buf); + snprintf(buf, sizeof buf, "up %lds", secs); + irc_reply(s, reply_target, src_nick, buf); log_stamp(); fprintf(stderr, CLR_YELLOW "UPTIME from %s in %s" CLR_RESET "\n", src_nick, reply_target); return 1; } diff --git a/cmd_version.h b/cmd_version.h index 55ca572..8448882 100644 --- a/cmd_version.h +++ b/cmd_version.h @@ -22,7 +22,7 @@ int cmd_version(Session *s, const char *msg, const char *reply_target, const cha if (msg[8] != ' ' && msg[8] != '\0') return 0; if (rate_limit_check(src_nick, reply_target) < 0) return 1; char buf[96]; snprintf(buf, sizeof buf, "zynk %s", VERSION); - irc_msg(s, reply_target, buf); + irc_reply(s, reply_target, src_nick, buf); log_stamp(); fprintf(stderr, CLR_BLUE "VERSION from %s in %s" CLR_RESET "\n", src_nick, reply_target); return 1; } diff --git a/cmd_weather.h b/cmd_weather.h index 814168f..997d4b8 100644 --- a/cmd_weather.h +++ b/cmd_weather.h @@ -38,7 +38,7 @@ int cmd_weather(Session *s, const char *msg, const char *reply_target, const cha } else { snprintf(resp, sizeof resp, "Weather: could not fetch data"); } - irc_msg(s, reply_target, resp); + irc_reply(s, reply_target, src_nick, resp); log_stamp(); fprintf(stderr, CLR_MAGENTA "WEATHER %s from %s:" CLR_RESET " %s\n", reply_target, src_nick, resp); return 1; } diff --git a/cmd_zynk.h b/cmd_zynk.h index aab267c..a86a8f7 100644 --- a/cmd_zynk.h +++ b/cmd_zynk.h @@ -31,16 +31,16 @@ int cmd_zynk(Session *s, const char *msg, const char *reply_target, const char * if (msg[5] == '\0') { char *rand = db_get_random(); if (rand) { - irc_msg(s, reply_target, rand); + irc_reply(s, reply_target, src_nick, rand); log_stamp(); fprintf(stderr, CLR_MAGENTA "ZYNK %s from %s:" CLR_RESET " random %s\n", reply_target, src_nick, rand); free(rand); } else { - irc_msg(s, reply_target, "No zynkss saved yet."); + irc_reply(s, reply_target, src_nick, "No zynkss saved yet."); } return 1; } else if (msg[5] == '!') { if (msg[6] == '\0') { - irc_msg(s, reply_target, "Usage: !zynk! = "); + irc_reply(s, reply_target, src_nick, "Usage: !zynk! = "); return 1; } force = 1; @@ -50,7 +50,7 @@ int cmd_zynk(Session *s, const char *msg, const char *reply_target, const char * } while (*rest == ' ') rest++; if (!*rest) { - irc_msg(s, reply_target, "Usage: !zynk [= ]"); + irc_reply(s, reply_target, src_nick, "Usage: !zynk [= ]"); return 1; } const char *eq = strstr(rest, " = "); @@ -58,7 +58,7 @@ int cmd_zynk(Session *s, const char *msg, const char *reply_target, const char * if (eq) { char key[256]; size_t kl = eq - rest; - if (kl == 0) { irc_msg(s, reply_target, "Key cannot be empty"); return 1; } + if (kl == 0) { irc_reply(s, reply_target, src_nick, "Key cannot be empty"); return 1; } if (kl >= sizeof key) kl = sizeof key - 1; memcpy(key, rest, kl); key[kl] = 0; for (char *p = key; *p; p++) *p = tolower((unsigned char)*p); @@ -66,14 +66,13 @@ int cmd_zynk(Session *s, const char *msg, const char *reply_target, const char * char *existing = db_get_raw(key); if (existing) { free(existing); - snprintf(resp, sizeof resp, "zynk already exists (use !zynk! to overwrite)"); - irc_msg(s, reply_target, resp); + irc_reply(s, reply_target, src_nick, "zynk already exists (use !zynk! to overwrite)"); log_stamp(); fprintf(stderr, CLR_MAGENTA "ZYNK %s from %s:" CLR_RESET " blocked re-add of %s\n", reply_target, src_nick, key); return 1; } } else { if (tgt[0] == '#' && !chan_is_op(tgt, src_nick)) { - irc_msg(s, reply_target, "You need op to overwrite a zynk"); + irc_reply(s, reply_target, src_nick, "You need op to overwrite a zynk"); return 1; } } @@ -98,7 +97,7 @@ int cmd_zynk(Session *s, const char *msg, const char *reply_target, const char * snprintf(resp, sizeof resp, "%s: not found", key); } } - irc_msg(s, reply_target, resp); + irc_reply(s, reply_target, src_nick, resp); log_stamp(); fprintf(stderr, CLR_MAGENTA "ZYNK %s from %s:" CLR_RESET " %s\n", reply_target, src_nick, resp); return 1; } diff --git a/irc.h b/irc.h index f80d2e8..b2a2035 100644 --- a/irc.h +++ b/irc.h @@ -242,6 +242,16 @@ void irc_msg(Session *s, const char *t, const char *txt) { free(clean); } +void irc_reply(Session *s, const char *target, const char *nick, const char *txt) { + if (target && target[0] == '#' && nick && *nick) { + char prefixed[2048]; + snprintf(prefixed, sizeof prefixed, "%s: %s", nick, txt); + irc_msg(s, target, prefixed); + } else { + irc_msg(s, target, txt ? txt : ""); + } +} + /* * irc_nick - Send a NICK command to change the bot's nickname. * @@ -391,7 +401,7 @@ static void handle_privmsg(Session *s, const char *params, const char *src_nick) cmd_greeting_or_chat(s, msg, reply_target, src_nick); #endif if (msg[0] == '!' && msg[1] && isalpha((unsigned char)msg[1])) - irc_msg(s, reply_target, "Not implemented..."); + irc_reply(s, reply_target, src_nick, "Not implemented..."); } /* diff --git a/zynk.h b/zynk.h index 23872dd..a907515 100644 --- a/zynk.h +++ b/zynk.h @@ -171,6 +171,7 @@ extern time_t start_time; /* IRC protocol helpers */ void irc_msg(Session *s, const char *t, const char *txt); +void irc_reply(Session *s, const char *target, const char *nick, const char *txt); void irc_join(Session *s, const char *chan); void irc_nick(Session *s, const char *nn); int net_send(Session *s, const char *fmt, ...);