/* Copyright (C) 2020 C. McEnroe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include "server.h" const char *htmlStylesheet = "stylesheet.css"; enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) { return khtml_elem(html, KELEM_DOCTYPE) || khtml_attr(html, KELEM_META, KATTR_CHARSET, "utf-8", KATTR__MAX) || khtml_elem(html, KELEM_TITLE) || khtml_puts(html, title) || khtml_closeelem(html, 1) || khtml_attr( html, KELEM_META, KATTR_NAME, "viewport", KATTR_CONTENT, "width=device-width, initial-scale=1.0", KATTR__MAX ) || khtml_attr( html, KELEM_LINK, KATTR_REL, "stylesheet", KATTR_HREF, htmlStylesheet, KATTR__MAX ) || khtml_elem(html, KELEM_H1) || khtml_puts(html, title) || khtml_closeelem(html, 1); } enum kcgi_err htmlNav(struct khtmlreq *html, const char *network, const char *context) { enum kcgi_err error = 0 || khtml_elem(html, KELEM_NAV) || khtml_elem(html, KELEM_OL) || khtml_elem(html, KELEM_LI) || khtml_attr(html, KELEM_A, KATTR_HREF, Pages[Networks], KATTR__MAX) || khtml_puts(html, "Networks") || khtml_closeelem(html, 2); if (error) return error; if (network) { char *href = khttp_urlpart( NULL, NULL, Pages[Contexts], Keys[Network].name, network, NULL ); if (!href) err(EX_OSERR, "khttp_urlpart"); error = 0 || khtml_elem(html, KELEM_LI) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) || khtml_puts(html, network) || khtml_closeelem(html, 2); if (error) return error; } if (network && context) { char *href = khttp_urlpart( NULL, NULL, Pages[Events], Keys[Network].name, network, Keys[Context].name, context, NULL ); if (!href) err(EX_OSERR, "khttp_urlpart"); error = 0 || khtml_elem(html, KELEM_LI) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) || khtml_puts(html, context) || khtml_closeelem(html, 2); if (error) return error; } char label[256]; snprintf( label, sizeof(label), "Search%s%s", (network ? " " : ""), (context ? context : network ? network : "") ); error = 0 || khtml_closeelem(html, 1) || khtml_attr( html, KELEM_FORM, KATTR_METHOD, "get", KATTR_ACTION, Pages[Search], KATTR__MAX ) || khtml_attr( html, KELEM_INPUT, KATTR_TYPE, "search", KATTR_NAME, Keys[Query].name, KATTR__MAX ) || khtml_attr( html, KELEM_INPUT, KATTR_TYPE, "submit", KATTR_VALUE, label, KATTR__MAX ) || khtml_closeelem(html, 1); if (error) return error; if (network) { error = khtml_attr( html, KELEM_INPUT, KATTR_TYPE, "hidden", KATTR_NAME, Keys[Network].name, KATTR_VALUE, network, KATTR__MAX ); if (error) return error; } if (context) { error = khtml_attr( html, KELEM_INPUT, KATTR_TYPE, "hidden", KATTR_NAME, Keys[Context].name, KATTR_VALUE, context, KATTR__MAX ); if (error) return error; } return khtml_closeelem(html, 2); } static const char *SourceURL = "https://git.causal.agency/scooper"; static const char *SyntaxURL = { "https://www.sqlite.org/fts5.html#full_text_query_syntax" }; static const char *Columns = { "network, channel, query, nick, user, target, message" }; enum kcgi_err htmlFooter(struct khtmlreq *html) { return 0 || khtml_closeto(html, 0) || khtml_elem(html, KELEM_FOOTER) || khtml_elem(html, KELEM_SPAN) || khtml_attr(html, KELEM_A, KATTR_HREF, SourceURL, KATTR__MAX) || khtml_puts(html, "scooper is AGPLv3+") || khtml_closeelem(html, 2) || khtml_putc(html, ' ') || khtml_elem(html, KELEM_SPAN) || khtml_attr(html, KELEM_A, KATTR_HREF, SyntaxURL, KATTR__MAX) || khtml_puts(html, "Search syntax") || khtml_closeelem(html, 1) || khtml_putc(html, ' ') || khtml_attr(html, KELEM_SPAN, KATTR_TITLE, Columns, KATTR__MAX) || khtml_puts(html, "Columns") || khtml_closeto(html, 0); } static enum kcgi_err eventTime(struct khtmlreq *html, struct Event event) { char time[sizeof("0000-00-00 00:00:00")]; strftime(time, sizeof(time), "%F %T", gmtime(&event.time)); char *base = NULL; if (event.network && event.context) { base = khttp_urlpart( NULL, NULL, Pages[Events], Keys[Network].name, event.network, Keys[Context].name, event.context, Keys[After].name, time, NULL ); if (!base) err(EX_OSERR, "khttp_urlpart"); } char *href = NULL; asprintf(&href, "%s#%" PRId64, (base ? base : ""), event.event); if (!href) err(EX_OSERR, "asprintf"); if (base) free(base); enum kcgi_err error = 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "time", KATTR__MAX) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) || khtml_attr(html, KELEM_TIME, KATTR_DATETIME, time, KATTR__MAX) || khtml_puts(html, time) || khtml_closeelem(html, 3); free(href); return error; } static enum kcgi_err eventNetwork(struct khtmlreq *html, struct Event event) { if (!event.network) return KCGI_OK; char *href = khttp_urlpart( NULL, NULL, Pages[Contexts], Keys[Network].name, event.network, NULL ); if (!href) err(EX_OSERR, "khttp_urlpart"); enum kcgi_err error = 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "network", KATTR__MAX) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) || khtml_puts(html, event.network) || khtml_closeelem(html, 2); free(href); return error; } static enum kcgi_err eventContext(struct khtmlreq *html, struct Event event) { if (!event.network || !event.context) return KCGI_OK; char *href = khttp_urlpart( NULL, NULL, Pages[Events], Keys[Network].name, event.network, Keys[Context].name, event.context, NULL ); if (!href) err(EX_OSERR, "khttp_urlpart"); enum kcgi_err error = 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "context", KATTR__MAX) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) || khtml_puts(html, event.context) || khtml_closeelem(html, 2); free(href); return error; } static int hash(const char *str) { if (*str == '~') str++; uint32_t hash = 0; for (; *str; ++str) { hash = (hash << 5) | (hash >> 27); hash ^= *str; hash *= 0x27220A95; } return 2 + hash % 74; } static enum kcgi_err eventNick(struct khtmlreq *html, struct Event event) { char color[sizeof("fg99")]; snprintf(color, sizeof(color), "fg%02d", hash(event.user)); char *mask = NULL; asprintf(&mask, "%s!%s@%s", event.nick, event.user, event.host); if (!mask) err(EX_OSERR, "asprintf"); enum kcgi_err error = 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "nick", KATTR__MAX) || khtml_attr( html, KELEM_SPAN, KATTR_CLASS, color, KATTR_TITLE, mask, KATTR__MAX ) || khtml_puts(html, event.nick) || khtml_closeelem(html, 2); free(mask); return error; } static enum kcgi_err eventMessage(struct khtmlreq *html, struct Event event) { if (!event.message) return KCGI_OK; return 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "message", KATTR__MAX) || khtml_puts(html, event.message) || khtml_closeelem(html, 1); } static const char *Types[TypesLen] = { #define X(id, name) [id] = name, ENUM_TYPE #undef X }; enum kcgi_err htmlEvent(struct khtmlreq *html, struct Event event) { const char *type = (event.type < TypesLen ? Types[event.type] : "unknown"); return 0 || khtml_attrx( html, KELEM_TR, KATTR_ID, KATTRX_INT, event.event, KATTR_CLASS, KATTRX_STRING, type, KATTR__MAX ) || eventTime(html, event) || eventNetwork(html, event) || eventContext(html, event) || eventNick(html, event) || eventMessage(html, event) || khtml_closeelem(html, 1); }