Nick-prefixed channel replies for all non-admin commands. (0.40.8)

- Added irc_reply(): 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.
- Converted AI async responses (ai_check_completion()) to use irc_reply(),
  so AI answers delivered after the original query also show the 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, !calc,
  !time, !uptime, and greeting/chat responses.
- 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.
This commit is contained in:
Johannes Findeisen 2026-07-24 04:03:04 +02:00
commit feccad5853
21 changed files with 95 additions and 78 deletions

View file

@ -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.

View file

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

14
ai.h
View file

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

View file

@ -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 <question>");
irc_reply(s, reply_target, src_nick, "Usage: !ai <question>");
return 1;
}
char question[2048];

View file

@ -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 <expression> (e.g. !calc 2+2, !calc (3*7)+1)");
irc_reply(s, reply_target, src_nick, "Usage: !calc <expression> (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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 <nick>");
irc_reply(s, reply_target, src_nick, "Usage: !seen <nick>");
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;

View file

@ -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>[,SYMBOL...] (max 3, e.g. !stock AAPL or !stock AAPL,MSFT,GOOGL)");
irc_reply(s, reply_target, src_nick, "Usage: !stock <SYMBOL>[,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;
}

View file

@ -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 <nick> <message>");
irc_reply(s, reply_target, src_nick, "Usage: !tell <nick> <message>");
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 <nick> <message>");
irc_reply(s, reply_target, src_nick, "Usage: !tell <nick> <message>");
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;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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! <key> = <value>");
irc_reply(s, reply_target, src_nick, "Usage: !zynk! <key> = <value>");
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 <key> [= <value>]");
irc_reply(s, reply_target, src_nick, "Usage: !zynk <key> [= <value>]");
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;
}

12
irc.h
View file

@ -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...");
}
/*

1
zynk.h
View file

@ -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, ...);