aboutsummaryrefslogtreecommitdiffstats
path: root/server/main.ts
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2022-01-13 17:28:04 +0000
committers-ol <s+removethis@s-ol.nu>2022-01-13 17:28:04 +0000
commitad6f77fcadd3968d9a9edb840aacad1e7efec8ea (patch)
treeea17d08d54ce88135fe35e637e947db8434ea666 /server/main.ts
parentbetter auth, list (diff)
downloadfedidag-ad6f77fcadd3968d9a9edb840aacad1e7efec8ea.tar.gz
fedidag-ad6f77fcadd3968d9a9edb840aacad1e7efec8ea.zip
move everything into server/
Diffstat (limited to 'server/main.ts')
-rw-r--r--server/main.ts242
1 files changed, 242 insertions, 0 deletions
diff --git a/server/main.ts b/server/main.ts
new file mode 100644
index 0000000..43ab64a
--- /dev/null
+++ b/server/main.ts
@@ -0,0 +1,242 @@
+import { URLSearchParams } from "url";
+import express from "express";
+import cors from "cors";
+import fetch from "node-fetch";
+import cookieParser from "cookie-parser";
+import * as cheerio from "cheerio";
+
+import { wrap } from "./async";
+
+const PORT = process.env.PORT ? +process.env.PORT : 3000;
+const DISCDAG_URL = process.env.DISCDAG_URL ?? 'http://solipsys.co.uk/cgi-bin/DiscDAG.py';
+const PUBLIC_URL = process.env.PUBLIC_URL ?? 'http://localhost:3000';
+const DEBUG = process.env.NODE_ENV !== 'production' || PUBLIC_URL.indexOf('localhost') > -1;
+
+const app = express();
+
+if (DEBUG) {
+ app.use(cors({
+ origin: 'http://localhost:8080',
+ credentials: true,
+ }));
+}
+app.use(express.json());
+app.use(cookieParser());
+
+const discdag = express()
+ .post('/login', wrap(async ($req, $res) => {
+ const { username, password } = $req.body;
+ if (!username || !password)
+ throw new Error("no credentials");
+
+ const res = await fetch(DISCDAG_URL, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: new URLSearchParams({
+ Action: 'Login',
+ Username: username,
+ Password: password,
+ }).toString(),
+ });
+
+ if (res.status !== 200)
+ throw new Error("bad response");
+
+ const cookiestr = res.headers.get('set-cookie') ?? '';
+ const match = cookiestr.match(/digest=([^ ]+);/);
+ if (!match || !match[1].length) throw new Error("wrong credentials");
+
+ const doc = cheerio.load(await res.text());
+ const name = doc("font b").text().match(/browsing as (.*)$/)![1];
+ const user = `${PUBLIC_URL}/discdag/user/${name}`;
+
+ return $res
+ .cookie('digest', match[1])
+ .json({
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ id: user,
+ type: 'User',
+ name,
+ });
+ }))
+
+ .get('/list', wrap(async ($req, $res) => {
+ const { digest } = $req.cookies;
+
+ const res = await fetch(DISCDAG_URL + '?Action=ListDiscussions', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Cookie': `digest=${digest}`,
+ },
+ });
+
+ const doc = cheerio.load(await res.text());
+ const name = doc("font b").text().match(/browsing as (.*)$/)![1];
+ const user = `${PUBLIC_URL}/discdag/user/${name}`;
+
+ const discussions = doc("ul > li").map(function(this: any) {
+ const writeAccess = this.children[0].data.indexOf('(W)') > -1;
+ const href = doc("a", this).attr("href")!;
+ const id = href.match(/cgi-bin\/DiscDAG.py\?DiscussionID=(.+)/)![1];
+
+ return {
+ id: `${PUBLIC_URL}/discdag/${id}`,
+ type: 'Document',
+ name: id,
+ url: {
+ type: 'Link',
+ href,
+ },
+ attributedTo: writeAccess ? user : null,
+ audience: user,
+ };
+ }).toArray();
+
+ return $res.json({
+ discussions,
+ user: {
+ id: user,
+ type: 'User',
+ name,
+ },
+ });
+ }))
+
+ .get('/user/:name', wrap(async ($req, $res) => {
+ const { name } = $req.params;
+
+ return $res.json({
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ id: `${PUBLIC_URL}/discdag/user/${name}`,
+ type: 'User',
+ name,
+ });
+ }))
+
+ .get('/:discussion', wrap(async ($req, $res) => {
+ const { discussion } = $req.params;
+ const { digest } = $req.cookies;
+
+ const res = await fetch(DISCDAG_URL, {
+ headers: {
+ 'Cookie': `DiscussionID=${discussion}; digest=${digest}`,
+ }
+ });
+
+ if (res.status !== 200)
+ throw new Error("bad response");
+
+ const doc = cheerio.load(await res.text());
+
+ type Note = {
+ type: 'Note';
+ published: string;
+ attributedTo: string | {
+ id: string;
+ type: 'Person',
+ name: string;
+ };
+ inReplyTo: string[];
+ replies: string[];
+ content: string;
+ };
+
+ let first = null;
+ const items: Record<string, Note> = {};
+ for (const node of doc('g.node', 'svg')) {
+ const nodeId = doc('title', node).text();
+ const [_, time, author] = nodeId.split('_');
+
+ const id = `${PUBLIC_URL}/discdag/${discussion}/${nodeId}`;
+ first = first ?? id;
+
+ const content = doc('g:first text', node)
+ .slice(3)
+ .map(function(this: any) { return doc(this).text(); })
+ .toArray()
+ .join(' ').trim();
+
+ items[id] = {
+ type: 'Note',
+ published: time.replace(/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)[a-z]*/, '$1-$2-$3T$4:$5:$6Z'),
+ attributedTo: `${PUBLIC_URL}/discdag/user/${author}`,
+ inReplyTo: [],
+ replies: [],
+ content,
+ };
+ }
+
+ for (const node of doc('g.edge', 'svg')) {
+ let [frm, to] = doc('title', node).text().split('->');
+ frm = `${PUBLIC_URL}/discdag/${discussion}/${frm}`;
+ to = `${PUBLIC_URL}/discdag/${discussion}/${to}`;
+
+ items[frm].replies.push(to);
+ items[to].inReplyTo.push(frm);
+ }
+
+ return $res.json({
+ '@context': [
+ 'https://www.w3.org/ns/activitystreams',
+ {
+ items: {
+ '@id': 'as:items',
+ '@type': '@id',
+ '@container': '@id'
+ },
+ },
+ ],
+ id: `${PUBLIC_URL}/discdag/${discussion}`,
+ type: 'Document',
+ name: discussion,
+ first,
+ items,
+ });
+ }));
+
+app.use('/discdag', discdag);
+
+app.get(/\/remote\/.*/, wrap(async ($req, $res) => {
+ const { origin } = $req.headers;
+ const url = $req.url.match('^/remote/(.*)')![1];
+
+ if (!DEBUG && origin !== PUBLIC_URL) {
+ return $res.status(403).end();
+ }
+
+ const res = await fetch(url, {
+ headers: {
+ 'Accept': 'application/ld+json, application/json',
+ 'X-Forwarded-For': $req.ip,
+ },
+ });
+
+ $res.status(res.status);
+ for (const key of [
+ 'Content-Type',
+ 'Content-Size',
+ 'Location',
+ 'Age',
+ 'Cache-Control',
+ 'Expires',
+ 'Etag',
+ ]) {
+ const val = res.headers.get(key);
+ if (val) $res.setHeader(key, val);
+ }
+
+ res.body.pipe($res);
+
+ return $res;
+}));
+
+try {
+ app.listen(PORT, (): void => {
+ console.log(`Connected successfully on port ${PORT}`);
+ });
+} catch (error) {
+ console.error(`Error occured: ${error}`);
+}