- 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.
154 lines
5.5 KiB
C
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
|