shithub: 9volt

Download patch

ref: ef2a51a97c36d1f279993eb17314fa4b080fe488
author: glenda <glenda@cirno>
date: Thu Feb 20 14:30:55 EST 2025

initial

--- /dev/null
+++ b/9volt
@@ -1,0 +1,113 @@
+#!/bin/rc
+# Made by Rosa Green ("rosie"), licensed under BSD
+# LONG LIVE 9FRONT
+APIVER=0.8
+
+fn sendtextmsg{
+ 	awk 'BEGIN{printf "t %0.20d%s", length(ARGV[1]), ARGV[1]}' $1 >[1=0]
+}
+fn readheader{
+ 	header=`{read -c 22}
+ 	length=`{echo $header(2) p | dc}
+ 	echo $header(1) $length
+}
+fn handlemsg{
+ 	echo Handling message:
+ 	echo '	TYPE  :' $1
+ 	echo '	LENGTH:' $2
+ 	echo '	BODY  :' $3
+}
+
+fn render {
+	message=$1
+	author=''
+	for(user in `{ls /mnt/json/users/}) {
+		if(~ `{cat $user/_id} `{cat $message/author}) {
+			author=`{cat $user/username}
+			if(test -e $user/display_name)
+				author=`{cat $user/display_name}
+
+			for(member in `{ls /mnt/json/members/}) {
+				if(~ `{cat $member/_id/user} `{cat $message/author} \
+					&& test -e $member/nickname)
+					author=`{cat $member/nickname}
+			}
+		}
+	}
+	if(test -e $message/replies){
+		for(reply in `{ls $message/replies}) {
+			for(messagetest in `{ls /mnt/json/messages/}) {
+				if(~ `{cat $messagetest/_id} `{cat $reply}) {
+					content='<No message content>'
+					if(test -e $message/content)
+						content=`{cat $messagetest/content}
+					echo '>' $content
+				}
+			}
+		}
+	}
+	content='<No message content>'
+	if(test -e $message/content)
+		content=`{cat $message/content}
+	echo $author: $content
+}
+
+fn load {
+	channelid=$1
+	token=$2
+	hget -r 'x-session-token: '$token https://api.revolt.chat/$APIVER/channels/$channelid'/messages?include_users' | jsonfs
+	for(message in `{seq 49 -1 0}) {
+		render /mnt/json/messages/$message
+	}
+}
+
+fn websocket {
+	<>/mnt/web/clone {
+		d=/mnt/web/^`{sed 1q}
+		echo url wss://echo.websocket.org >[1=0]
+		<>$d/body {
+			read -n 1
+			sendtextmsg 'ping'
+			while(header=`{readheader}){
+				echo Header: $header
+				body=`{read -c $header(2)}
+				handlemsg $header(1) $header(2) $"body
+				sleep 2
+				sendtextmsg 'ping'
+			}
+		}
+	}
+}
+fn connect {
+	token=`{auth/userpasswd 'service=retaped-cli'}
+	token=$token(2)
+	load $1 $token
+	websocket&
+	while() {
+		input=`{read}
+		hget -m POST -r 'x-session-token: '$token -p '{"content":"'$"input'"}' https://api.revolt.chat/$APIVER/channels/$1/messages > /dev/null
+	}
+}
+
+fn login {
+	hget -m POST -p '{"email":"'$1'","password":"'$2'"}' https://api.revolt.chat/$APIVER/auth/session/login | jsonfs
+	echo 'key proto=pass service=retaped-cli user=idk !password='`{cat /mnt/json/token} > /mnt/factotum/ctl
+}
+
+fn help {
+	echo '9volt: CLI Revolt client for Plan9(front)'
+	echo 'Usage: 9volt [-lch] [serverID|email] [channelID|password]'
+	echo '-l email password - Login using provided credentials'
+	echo '-c serverID channelID - Connect to Revolt and filter for the specified server and channel; token is loaded from Factotum'
+}
+
+switch ($1) {
+	case -l
+		login $2 $3
+	case -c
+		connect $2
+	case -h
+		help
+	case *
+		help
+}
--- /dev/null
+++ b/jsonfs.c
@@ -1,0 +1,184 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <json.h>
+#include <bio.h>
+#include <String.h>
+
+static char *delim = nil;
+static char *uid = "json";
+static char *mtpt = nil;
+
+void
+fsread(Req *r)
+{
+	if(r->fid->file->aux)
+		readstr(r, r->fid->file->aux);
+	respond(r, nil);
+}
+
+Srv fs = {
+	.read = fsread,
+};
+
+char*
+esmprint(char *fmt, ...)
+{
+	char *s;
+	va_list arg;
+	
+	va_start(arg, fmt);
+	s = vsmprint(fmt, arg);
+	va_end(arg);
+	if(s == nil)
+		sysfatal("out of memory");
+	return s;
+}
+
+char*
+jprint(char *fmt, ...)
+{
+	char *s, *p;
+	va_list arg;
+
+	p = nil;
+	if(delim != nil){
+		if((p = smprint("%s%s", fmt, delim)) == nil)
+			sysfatal("out of memory");
+		fmt = p;
+	}
+
+	va_start(arg, fmt);
+	s = vsmprint(fmt, arg);
+	va_end(arg);
+	if(s == nil)
+		sysfatal("out of memory");
+
+	if(p)
+		free(p);
+
+	return s;
+}
+
+void
+json2files(File *dir, JSON *j)
+{
+	File *d, *f;
+	JSONEl *tok;
+	char *p;
+	void *s;
+	int anum = 0;
+	int bnum = 0;
+
+	for(tok = j->first; tok != nil; tok = tok->next){
+		s = nil;
+
+		switch(tok->val->t){
+		case JSONNull:
+			s = jprint("null");
+			break;
+		case JSONBool:
+			if(tok->val->n)
+				s = jprint("true");
+			else
+				s = jprint("false");
+			break;
+		case JSONNumber:
+			s = jprint("%0.f", tok->val->n);
+			break;
+		case JSONString:
+			s = jprint("%s", tok->val->s);
+			break;
+		case JSONArray:
+		case JSONObject:
+			if((s = tok->name) == nil || strlen(s) < 1)
+				s = esmprint("%d", anum++);
+
+			if((d = createfile(dir, s, uid, DMDIR|0777, nil)) == nil)
+				sysfatal("createfile: JSONObject: %r");
+			d->qid.type = QTDIR;
+			json2files(d, tok->val);
+			continue;
+		default:
+			sysfatal("wut: %r");
+		}
+		if((p = tok->name) == nil)
+			p = esmprint("%d", bnum++);
+		if((f = createfile(dir, p, uid, 0666, s)) != nil)
+			f->length = strlen(s);
+	}
+}
+
+void
+populatetree(File *root, int fd)
+{
+	Biobuf in;
+	String *s;
+	JSON *j;
+
+	Binit(&in, fd, OREAD);
+	s = s_new();
+
+	while(s_read(&in, s, 8192))
+		;
+
+	if((j = jsonparse(s_to_c(s))) == nil)
+		sysfatal("jsonparse: %r");
+
+	json2files(root, j);
+
+	s_free(s);
+	Bterm(&in);
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-D] [-s srv] [-m mtpt] [-n] [file]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *srv;
+	int fd;
+
+	fd = 0;
+	srv = nil;
+	mtpt = "/mnt/json";
+	ARGBEGIN{
+	case 'D':
+		chatty9p++;
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 'n':
+		delim = "\n";
+		break;
+	default:
+		usage();
+	}ARGEND;
+	if(argc > 1)
+		usage();
+	else if(argc == 1){
+		if((fd = open(argv[0], OREAD)) < 0)
+			sysfatal("open: %r");
+	}
+
+	if(chatty9p)
+		fprint(2, "srvname %s mtpt %s\n", srv, mtpt);
+
+	fs.tree = alloctree(nil, nil, DMDIR|0777, nil);
+	populatetree(fs.tree->root, fd);
+	postmountsrv(&fs, srv, mtpt, MREPL|MCREATE);
+	exits(nil);
+}
+