/* * Copyright (C) 2017 Andy Spencer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "util.h" #include "conf.h" #include "chat.h" #include "view.h" /* Global data */ server_t *servers; channel_t *channels; user_t *users; message_t *messages; int history; /* Local data */ static char *log_dir; static buf_t msg_buf; static const char *proto_map[] = { [IRC] "irc", [XMPP] "xmpp", }; /* Local functions */ void strdcat(char *dst, const char *src, size_t n) { int di = 0, si = 0; while (di < (n-1) && dst[di]) di++; while (di < (n-1) && src[si]) { if (src[si] == '/') dst[di++] = (si++,'_'); else dst[di++] = src[si++]; } dst[di] = '\0'; } static void init_logs() { if (!log_dir) return; wordexp_t wexp; wordexp(log_dir, &wexp, WRDE_NOCMD); if (wexp.we_wordc > 1) error("Found multiple log dirs: %s\n %s\n %s\n ...", log_dir, wexp.we_wordv[0], wexp.we_wordv[1]); strset(&log_dir, wexp.we_wordv[0]); if (mkdir(log_dir, 0755) && (errno != EEXIST)) error("Failed to create log dir: %s", log_dir); wordfree(&wexp); } static void write_log(message_t *msg) { static char log_path[4096]; if (!log_dir) return; channel_t *chan = msg->channel; server_t *srv = chan->server; if (!chan->log) { /* Create server directory */ strncpy(log_path, log_dir, sizeof(log_path)); strncat(log_path, "/", sizeof(log_path)); strdcat(log_path, srv->name, sizeof(log_path)); if (!mkdir(log_path, 0755)) { debug("Failed to create server log dir\n"); return; } /* Open log file directory */ strncat(log_path, "/", sizeof(log_path)); strdcat(log_path, chan->name, sizeof(log_path)); strncat(log_path, ".log", sizeof(log_path)); if (!(chan->log = fopen(log_path, "a+"))) { debug("Cannot open log file: %s\n", log_path); return; } } time_t timep = msg->when; struct tm *tm = gmtime(&timep); fprintf(chan->log, "(%04d-%02d-%02d %02d:%02d:%02d) %s%s %s\n", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, msg->from ? msg->from->name : "***", msg->from ? ":" : "", msg->text); fflush(chan->log); } /* Chat init */ void chat_init(void) { messages = (message_t*)msg_buf.data; history = 0; init_logs(); irc_init(); xmpp_init(); } void proto_config(const char *pname, const char *sname, const char *cname, const char *group, const char *name, const char *key, const char *value) { protocol_t proto = -1; server_t *srv = NULL; channel_t *chan = NULL; for (srv = servers; srv; srv = srv->next) if (match(sname, srv->name)) break; for (chan = channels; chan; chan = chan->next) if (match(cname, chan->name)) break; if (pname) proto = get_map(pname, proto_map); else if (srv) proto = srv->protocol; else if (chan) proto = chan->server->protocol; debug("chat_config: [%s-%s \"%s\"] %s = %s", proto>=0 ? proto_map[proto] : "none", group, name, key, value); switch (proto) { case IRC: irc_config(srv, chan, group, name, key, value); break; case XMPP: xmpp_config(srv, chan, group, name, key, value); break; } } void chat_config(const char *group, const char *name, const char *key, const char *value) { if (match(group, "general")) { if (match(key, "logdir")) log_dir = strcopy(get_string(value)); } if (match(group, "server")) { if (match(key, "")) get_name(name); else if (match(key, "protocol")) proto_config(value, NULL, NULL, group, name, key, value); else proto_config(NULL, name, NULL, group, name, key, value); } if (match(group, "channel")) { if (match(key, "")) get_name(name); else if (match(key, "server")) proto_config(NULL, value, NULL, group, name, key, value); else proto_config(NULL, NULL, name, group, name, key, value); } if (match(group, "autojoin")) { if (match(key, "")) get_name(name); else proto_config(NULL, name, key, group, name, key, value); } } void chat_recv(channel_t *channel, user_t *from, const char *text) { append(&msg_buf, NULL, sizeof(message_t)); messages = (message_t*)msg_buf.data; message_t *msg = &messages[history]; msg->channel = channel; msg->when = time(NULL); msg->from = from; msg->text = strcopy(text); history++; write_log(msg); view_draw(); } void chat_complete(channel_t *channel, const char *text) { switch (channel->server->protocol) { case IRC: irc_complete(channel, text); break; case XMPP: xmpp_complete(channel, text); break; } } void chat_send(channel_t *channel, const char *text) { switch (channel->server->protocol) { case IRC: irc_send(channel, text); break; case XMPP: xmpp_send(channel, text); break; } } void chat_update(void) { view_draw(); } void chat_exit(void) { irc_exit(); xmpp_exit(); for (int i = 0; i < history; i++) free((void*)messages[i].text); release(&msg_buf); } /* Server and channel function */ void add_server(server_t *server) { protocol_t proto = server->protocol; debug("adding %s server \"%s\"", proto_map[proto], server->name); server_t **last = &servers; while (*last) last = &(*last)->next; *last = server; } void add_channel(channel_t *channel) { debug("adding %s channel \"%s\"", channel->server->name, channel->name); channel_t **last = &channels; while (*last) last = &(*last)->next; *last = channel; } void add_user(user_t *user) { debug("adding %s user \"%s\"", user->server->name, user->name); user_t **last = &users; while (*last) last = &(*last)->next; *last = user; }