Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
Erin | 6243a7d80d | ||
Erin | cb5778377e | ||
Erin | 81a472f6e4 | ||
Erin | d62a32e427 | ||
Erin | 124b3cb365 | ||
Erin | f2f51b28fe |
16
README.md
16
README.md
|
@ -2,9 +2,19 @@
|
||||||
|
|
||||||
## run it
|
## run it
|
||||||
|
|
||||||
npm i
|
no external dependencies! no need to `npm i`! woo!
|
||||||
cp webring.txt.sample webring.txt && $EDITOR webring.txt
|
|
||||||
npm run start
|
cp webring.txt.sample webring.txt && $EDITOR webring.txt
|
||||||
|
npm run start
|
||||||
|
|
||||||
|
## configuration
|
||||||
|
|
||||||
|
the server listens on port `$PORT` (default 80) and loads the list of
|
||||||
|
participating sites from the file `$SITES_FILE` (default `webring.txt`).
|
||||||
|
|
||||||
|
the sites file is a newline-separated list of URLs; leading and trailing
|
||||||
|
whitespace, empty lines, and lines starting with `#` are ignored. you can test
|
||||||
|
out the format using [this regexr link](https://regexr.com/?expression=/^\s*([^%23\s].*)\s*$/gm&text=%23%20sample%20SITES_FILE%0A%0A%23%20my%20friend%20alice%20who%20is%20cool%0Ahttps%3A%2F%2Falices.awesome.website%0A%0A%23%20my%20other%20friend%20bob%20who%20is%20neat%20as%20well%0Ahttp%3A%2F%2Fsome.shared.domain%2F~bob%0A&tool=list&input=your.webring.server/next?from=$1\n).
|
||||||
|
|
||||||
## link to webring sites
|
## link to webring sites
|
||||||
|
|
||||||
|
|
29
dprint.json
Normal file
29
dprint.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"typescript": {
|
||||||
|
"lineWidth": 80,
|
||||||
|
"useTabs": true,
|
||||||
|
"quoteStyle": "alwaysSingle",
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"useBraces": "always",
|
||||||
|
"arrowFunction.useParentheses": "preferNone",
|
||||||
|
"enumDeclaration.memberSpacing": "newLine",
|
||||||
|
"spaceSurroundingProperties": false,
|
||||||
|
"exportDeclaration.spaceSurroundingNamedExports": false,
|
||||||
|
"importDeclaration.spaceSurroundingNamedImports": false,
|
||||||
|
"constructor.spaceBeforeParentheses": true,
|
||||||
|
"functionDeclaration.spaceBeforeParentheses": true,
|
||||||
|
"functionExpression.spaceBeforeParentheses": true,
|
||||||
|
"getAccessor.spaceBeforeParentheses": true,
|
||||||
|
"setAccessor.spaceBeforeParentheses": true,
|
||||||
|
"method.spaceBeforeParentheses": true
|
||||||
|
},
|
||||||
|
"markdown": {},
|
||||||
|
"excludes": [
|
||||||
|
"**/node_modules",
|
||||||
|
"**/*-lock.json"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"https://plugins.dprint.dev/typescript-0.86.1.wasm",
|
||||||
|
"https://plugins.dprint.dev/markdown-0.15.3.wasm"
|
||||||
|
]
|
||||||
|
}
|
62
index.mjs
62
index.mjs
|
@ -1,44 +1,30 @@
|
||||||
import * as fs from 'node:fs';
|
import {readFileSync} from 'node:fs';
|
||||||
import http from 'node:http';
|
import {createServer} from 'node:http';
|
||||||
|
import {URL} from 'node:url';
|
||||||
|
|
||||||
const mod = (n, m) => ((n % m) + m) % m;
|
const data = readFileSync(process.env.SITES_FILE || 'webring.txt', 'utf-8');
|
||||||
const stripURL = s => s.replace(/(^https?:\/\/|\/$)/g, '')
|
const sites = [...data.matchAll(/^\s*([^#\s].*)\s*$/gm)].map(match => match[1]);
|
||||||
|
|
||||||
const sitesArray = fs.readFileSync('./webring.txt', 'utf-8')
|
const text = (response, body, code = 200) => {
|
||||||
.match(/(?<!#.*)https?:\/\/[^\s]+(?=\s*$)/gmi)
|
response.writeHead(code, {'Content-Type': 'text/plain'}).write(body);
|
||||||
|
response.end();
|
||||||
|
};
|
||||||
|
const redirect = (res, url) => res.writeHead(302, {Location: url}).end();
|
||||||
|
|
||||||
const sitesLookup = sitesArray.map(stripURL).reduce((acc, current, index) => ({
|
const toSite = (response, from, offset) => {
|
||||||
...acc, [current]: {
|
const index = sites.findIndex(site => site === from);
|
||||||
'/next': sitesArray[mod(index + 1, sitesArray.length)],
|
if (!from || index === -1) {
|
||||||
'/prev': sitesArray[mod(index - 1, sitesArray.length)],
|
return text(response, 'the provided site is not in the webring', 400);
|
||||||
}
|
}
|
||||||
}), {})
|
redirect(response, sites[(index + offset + sites.length) % sites.length]);
|
||||||
|
};
|
||||||
|
|
||||||
const redirect = (response, url) => {
|
const pathHandlers = {
|
||||||
response.setHeader('Location', url)
|
'/prev': (response, query) => toSite(response, query.get('from'), -1),
|
||||||
response.statusCode = 301
|
'/next': (response, query) => toSite(response, query.get('from'), +1),
|
||||||
response.end()
|
'/list': response => text(response, sites.join('\n')),
|
||||||
}
|
};
|
||||||
|
createServer((request, response) => {
|
||||||
const handle = (direction, from, response) => {
|
|
||||||
const destination = sitesLookup[stripURL(from)]?.[direction]
|
|
||||||
|
|
||||||
if (destination)
|
|
||||||
return redirect(response, destination)
|
|
||||||
|
|
||||||
response.statusCode = 400
|
|
||||||
response.end('the provided site is not in the webring')
|
|
||||||
}
|
|
||||||
|
|
||||||
http.createServer((request, response) => {
|
|
||||||
const url = new URL(request.url, `http://${request.headers.host}`);
|
const url = new URL(request.url, `http://${request.headers.host}`);
|
||||||
|
pathHandlers[url.pathname]?.(response, url.searchParams);
|
||||||
if (url.pathname === '/prev' || url.pathname === '/next') {
|
}).listen(process.env.PORT || 80);
|
||||||
return handle(url.pathname, url.searchParams.get('from'), response)
|
|
||||||
} else if (url.pathname === '/list') {
|
|
||||||
return response.setHeader('Content-Type', 'text/plain').end(sitesArray.join('\n'))
|
|
||||||
}
|
|
||||||
|
|
||||||
response.statusCode = 404
|
|
||||||
response.end('not found')
|
|
||||||
}).listen(process.env.PORT || 8080)
|
|
||||||
|
|
1158
package-lock.json
generated
1158
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -4,12 +4,13 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.mjs"
|
"start": "node index.mjs",
|
||||||
|
"fmt": "dprint fmt"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "WTFPL",
|
||||||
"dependencies": {
|
"devDependencies": {
|
||||||
"express": "^4.18.2"
|
"dprint": "^0.40.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue