summary refs log tree commit diff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rw-r--r--bin/.gitignore1
-rw-r--r--bin/Makefile3
-rw-r--r--bin/man1/typer.156
-rw-r--r--bin/typer.c160
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);
+	}
+}