diff options
| author | s-ol <s+removethis@s-ol.nu> | 2022-01-13 17:28:04 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2022-01-13 17:28:04 +0000 |
| commit | ad6f77fcadd3968d9a9edb840aacad1e7efec8ea (patch) | |
| tree | ea17d08d54ce88135fe35e637e947db8434ea666 /server/main.ts | |
| parent | better auth, list (diff) | |
| download | fedidag-ad6f77fcadd3968d9a9edb840aacad1e7efec8ea.tar.gz fedidag-ad6f77fcadd3968d9a9edb840aacad1e7efec8ea.zip | |
move everything into server/
Diffstat (limited to 'server/main.ts')
| -rw-r--r-- | server/main.ts | 242 |
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}`); +} |
