diff options
Diffstat (limited to '')
-rw-r--r-- | bin/.gitignore | 1 | ||||
-rw-r--r-- | bin/Makefile | 3 | ||||
-rw-r--r-- | bin/man1/typer.1 | 56 | ||||
-rw-r--r-- | bin/typer.c | 160 |
4 files changed, 220 insertions, 0 deletions
diff --git a/bin/.gitignore b/bin/.gitignore index 024588e6..24802157 100644 --- a/bin/.gitignore +++ b/bin/.gitignore @@ -31,6 +31,7 @@ shotty sup tags title +typer up when xx diff --git a/bin/Makefile b/bin/Makefile index d37aeb1b..e4ead85c 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -6,6 +6,7 @@ CFLAGS += -I${LIBS_PREFIX}/include LDFLAGS += -L${LIBS_PREFIX}/lib CFLAGS += -Wall -Wextra -Wpedantic -Wno-gnu-case-range + LDLIBS.dtch = -lutil LDLIBS.fbclock = -lz LDLIBS.glitch = -lz @@ -16,6 +17,7 @@ LDLIBS.ptee = -lutil LDLIBS.relay = -ltls LDLIBS.scheme = -lm LDLIBS.title = -lcurl +LDLIBS.typer = -ltls -include config.mk @@ -50,6 +52,7 @@ BINS_LINUX += fbatt BINS_LINUX += fbclock BINS_LINUX += psfed BINS_TLS += relay +BINS_TLS += typer BINS_ALL = ${BINS} ${BINS_BSD} ${BINS_LINUX} ${BINS_TLS} diff --git a/bin/man1/typer.1 b/bin/man1/typer.1 new file mode 100644 index 00000000..cb00d618 --- /dev/null +++ b/bin/man1/typer.1 @@ -0,0 +1,56 @@ +.Dd March 23, 2021 +.Dt TYPER 1 +.Os +. +.Sh NAME +.Nm typer +.Nd type all day +. +.Sh SYNOPSIS +.Nm +.Op Fl v +.Op Fl n Ar nick +.Op Fl p Ar port +.Op Fl u Ar user +.Ar host +.Ar chan +. +.Sh DESCRIPTION +.Nm +is an IRC bot +that types all day long. +The arguments are as follows: +.Bl -tag -width Ds +.It Fl n Ar nick +Set the nickname. +The default is +.Nm . +.It Fl p Ar port +Connect to +.Ar port . +The default is 6697. +.It Fl u Ar user +Set the username. +The default is +.Nm . +.It Fl v +Log IRC protocol to standard error. +.It Ar host +Connect to +.Ar host . +.It Ar chan +Type in the channel +.Ar chan . +.El +. +.Sh STANDARDS +.Bl -item +.It +.Rs +.%A MuffinMedic +.%A James Wheare +.%T IRCv3 typing client tag +.%I IRCv3 Working Group +.%U https://ircv3.net/specs/client-tags/typing +.Re +.El diff --git a/bin/typer.c b/bin/typer.c new file mode 100644 index 00000000..4842bb95 --- /dev/null +++ b/bin/typer.c @@ -0,0 +1,160 @@ +/* Copyright (C) 2021 June McEnroe <june@causal.agency> + * + * 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 <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this Program, or any covered work, by linking or + * combining it with LibreSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of LibreSSL used as well as that of the + * covered work. + */ + +#include <err.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <sysexits.h> +#include <tls.h> +#include <unistd.h> + +static bool verbose; +static struct tls *client; +static const char *chan; + +static void clientWrite(const char *ptr, size_t len) { + if (verbose) printf("%.*s", (int)len, ptr); + while (len) { + ssize_t ret = tls_write(client, ptr, len); + if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue; + if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client)); + ptr += ret; + len -= ret; + } +} + +static void format(const char *format, ...) { + char buf[1024]; + va_list ap; + va_start(ap, format); + int len = vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + if ((size_t)len > sizeof(buf) - 1) errx(EX_DATAERR, "message too large"); + clientWrite(buf, len); +} + +static bool joined; + +static void handle(char *line) { + if (line && line[0] == '@') strsep(&line, " "); + if (line && line[0] == ':') strsep(&line, " "); + char *cmd = strsep(&line, " "); + if (!cmd) return; + if (!strcmp(cmd, "CAP")) { + char *param = strsep(&cmd, " "); + if (!param) errx(EX_PROTOCOL, "CAP missing parameter"); + if (!strcmp(param, "NAK")) { + errx(EX_CONFIG, "server does not support message-tags"); + } + format("CAP END\r\n"); + } else if (!strcmp(cmd, "001")) { + format("JOIN %s\r\n", chan); + joined = true; + } else if (!strcmp(cmd, "PING")) { + format("PONG %s\r\n", line); + } +} + +static void timer(int sig) { + (void)sig; + if (!joined) return; + const char *status = (arc4random_uniform(4) ? "active" : "done"); + format( + "@+typing=%s;+draft/typing=%s TAGMSG %s\r\n", + status, status, chan + ); +} + +int main(int argc, char *argv[]) { + const char *host = NULL; + const char *port = "6697"; + const char *nick = "typer"; + const char *user = "typer"; + + for (int opt; 0 < (opt = getopt(argc, argv, "n:p:u:v"));) { + switch (opt) { + break; case 'n': nick = optarg; + break; case 'p': port = optarg; + break; case 'u': user = optarg; + break; case 'v': verbose = true; + break; default: return EX_USAGE; + } + } + if (argc - optind < 2) errx(EX_USAGE, "host and chan required"); + host = argv[optind]; + chan = argv[optind + 1]; + + client = tls_client(); + if (!client) errx(EX_SOFTWARE, "tls_client"); + + struct tls_config *config = tls_config_new(); + if (!config) errx(EX_SOFTWARE, "tls_config_new"); + + int error = tls_configure(client, config); + if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client)); + tls_config_free(config); + + error = tls_connect(client, host, port); + if (error) errx(EX_UNAVAILABLE, "tls_connect: %s", tls_error(client)); + + format("CAP REQ message-tags\r\n"); + format("NICK %s\r\n", nick); + format("USER %s 0 * :typer\r\n", user); + + signal(SIGALRM, timer); + struct itimerval itimer = { .it_interval.tv_sec = 5, .it_value.tv_sec = 5 }; + error = setitimer(ITIMER_REAL, &itimer, NULL); + if (error) err(EX_OSERR, "setitimer"); + + size_t len = 0; + char buf[4096]; + for (;;) { + ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len); + if (read == TLS_WANT_POLLIN || read == TLS_WANT_POLLOUT) continue; + if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client)); + if (!read) errx(EX_UNAVAILABLE, "server disconnected"); + len += read; + + char *crlf; + char *line = buf; + for (;;) { + crlf = memmem(line, &buf[len] - line, "\r\n", 2); + if (!crlf) break; + crlf[0] = '\0'; + if (verbose) printf("%s\n", line); + handle(line); + line = &crlf[2]; + } + len -= line - buf; + memmove(buf, line, len); + } +} |