zynk/cmd_gitlog.h
hanez feccad5853 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.
2026-07-24 04:03:04 +02:00

154 lines
5.5 KiB
C

#ifndef CMD_GITLOG_H
#define CMD_GITLOG_H
#include "zynk.h"
/*
* cmd_gitlog - Handle the "!gitlog [count]" command.
*
* Displays recent git commits of the zynk project by forking a child
* process that runs "git log --oneline -n <count>". Each commit line
* is sent as a separate IRC PRIVMSG to the reply target. The commit
* count defaults to GITLOG_COUNT (5) when no argument is provided,
* and is clamped to GITLOG_MAX (20) if the user requests more.
* Rate-limited per-nick. If git is not installed or the working
* directory is not a git repository, an appropriate error message is
* sent. If no commits exist (e.g. fresh repo), reports that no commits
* were found.
*
* Parameters:
* s - The IRC session.
* msg - The raw IRC message text.
* reply_target - Channel or nick to send the response to.
* src_nick - The IRC nick of the user who issued the command.
*
* Returns: 1 if the command was matched, 0 otherwise.
*/
int cmd_gitlog(Session *s, const char *msg, const char *reply_target, const char *src_nick) {
if (strncmp(msg, "!gitlog", 7) != 0) return 0;
if (msg[7] != ' ' && msg[7] != '\0') return 0;
if (rate_limit_check(src_nick, reply_target) < 0) return 1;
int count = GITLOG_COUNT;
const char *rest = msg[7] == ' ' ? msg + 8 : "";
while (*rest == ' ') rest++;
if (*rest) {
if (isdigit((unsigned char)*rest)) {
count = atoi(rest);
if (count <= 0) count = GITLOG_COUNT;
if (count > GITLOG_MAX) count = GITLOG_MAX;
while (*rest && isdigit((unsigned char)*rest)) rest++;
while (*rest == ' ') rest++;
}
}
char remote_url[512] = {0};
FILE *fp = popen("git remote get-url origin 2>/dev/null", "r");
if (fp) {
if (fgets(remote_url, sizeof remote_url, fp)) {
size_t rlen = strlen(remote_url);
while (rlen > 0 && (remote_url[rlen-1] == '\n' || remote_url[rlen-1] == '\r')) rlen--;
remote_url[rlen] = 0;
if (strncmp(remote_url, "ssh://git@", 10) == 0) {
char *host = remote_url + 10;
char *slash = strchr(host, '/');
if (slash) {
char tmp[512];
snprintf(tmp, sizeof tmp, "https://%.*s%s", (int)(slash - host), host, slash);
strncpy(remote_url, tmp, sizeof remote_url - 1);
remote_url[sizeof remote_url - 1] = 0;
}
} else if (strncmp(remote_url, "git@", 4) == 0) {
char *host = remote_url + 4;
char *path = strchr(host, ':');
if (path) {
char tmp[512];
snprintf(tmp, sizeof tmp, "https://%.*s/%s", (int)(path - host), host, path + 1);
strncpy(remote_url, tmp, sizeof remote_url - 1);
remote_url[sizeof remote_url - 1] = 0;
}
}
size_t ulen = strlen(remote_url);
if (ulen > 0 && remote_url[ulen-1] == '/') remote_url[--ulen] = 0;
if (ulen > 4 && strcmp(remote_url + ulen - 4, ".git") == 0) { ulen -= 4; remote_url[ulen] = 0; }
}
pclose(fp);
}
int out_pipe[2];
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_reply(s, reply_target, src_nick, "gitlog: fork failed"); return 1; }
if (pid == 0) {
close(out_pipe[0]);
dup2(out_pipe[1], STDOUT_FILENO);
close(out_pipe[1]);
int dn = open("/dev/null", O_WRONLY);
if (dn >= 0) { dup2(dn, STDERR_FILENO); close(dn); }
char cnt[8];
snprintf(cnt, sizeof cnt, "%d", count);
execlp("git", "git", "log", "--format=%ci %H %s", "-n", cnt, (char *)NULL);
_exit(127);
}
close(out_pipe[1]);
char buf[8192], tmp[512];
size_t len = 0;
ssize_t n;
while ((n = read(out_pipe[0], tmp, sizeof tmp - 1)) > 0) {
size_t avail = sizeof buf - 1 - len;
if (avail > 0) {
size_t copy = (size_t)n < avail ? (size_t)n : avail;
memcpy(buf + len, tmp, copy);
len += copy;
}
}
close(out_pipe[0]);
int status;
waitpid(pid, &status, 0);
while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
buf[len] = 0;
if (len == 0) {
irc_reply(s, reply_target, src_nick, "gitlog: no commits found");
return 1;
}
char *line = strtok(buf, "\n");
while (line) {
while (*line == ' ') line++;
if (strlen(line) > 25 && line[4] == '-' && line[7] == '-' && line[10] == ' ' &&
line[13] == ':' && line[16] == ':' && line[19] == ' ') {
char datetime[21];
memcpy(datetime, line, 19);
datetime[19] = 0;
char *rest = line + 25;
while (*rest == ' ') rest++;
char *sp = strchr(rest, ' ');
if (sp) {
*sp = 0;
char *hash = rest;
char *msg = sp + 1;
while (*msg == ' ') msg++;
char short_hash[8];
strncpy(short_hash, hash, 7);
short_hash[7] = 0;
char display[512];
snprintf(display, sizeof display, "[%s] %s %s", datetime, short_hash, msg);
collapse_spaces(display);
if (*remote_url) {
char link[4096];
snprintf(link, sizeof link, "%s - %s/commit/%s", display, remote_url, hash);
irc_reply(s, reply_target, src_nick, link);
} else {
irc_reply(s, reply_target, src_nick, display);
}
} else {
collapse_spaces(rest);
if (*rest) irc_reply(s, reply_target, src_nick, rest);
}
} else {
collapse_spaces(line);
if (*line) irc_reply(s, reply_target, src_nick, line);
}
line = strtok(NULL, "\n");
}
log_stamp(); fprintf(stderr, CLR_YELLOW "GITLOG %s from %s (count=%d)" CLR_RESET "\n", reply_target, src_nick, count);
return 1;
}
#endif