abtmtr-v13/index.js

300 lines
No EOL
7.9 KiB
JavaScript

import express from "express";
import { config as dotenvConfig } from "dotenv";
import path from "path";
import SQLite from 'better-sqlite3';
import { Kysely, SqliteDialect } from 'kysely';
import { nanoid } from "nanoid";
import { JSDOM } from "jsdom";
import Parser from "rss-parser";
dotenvConfig();
const __dirname = import.meta.dirname;
const rawDB = new SQLite('abtmtr.db');
const db = new Kysely({
dialect: new SqliteDialect({
database: rawDB,
})
});
const rssParser = new Parser();
rawDB.exec(`CREATE TABLE IF NOT EXISTS blurbs( 'id' TEXT, 'site' TEXT, 'blurb' TEXT, 'verified' INTEGER, 'time' INTEGER );`);
rawDB.exec(`CREATE TABLE IF NOT EXISTS blacklist( 'domain' TEXT );`);
const app = express();
app.set('view engine', 'ejs');
app.use('/assets', express.static('assets'));
app.set('views', path.join(__dirname, "views", "pages"));
app.locals.siteMap = [
{
link: "blurbs",
description: "what are other sites saying about abtmtr.link?"
},
{
link: "updates",
description: "what's going on with abtmtr.link?"
},
{
link: "servers",
description: "about the services on abtmtr.link and the machines that serve them."
},
{
link: "sites",
description: "a collection of other sites in the form of 88x31s."
},
{
link: "about",
description: "who runs abtmtr.link?"
}
];
app.locals.curCommit = "0000000000";
async function getCurCommit() {
const curCommit = await fetch("https://git.abtmtr.link/api/v1/repos/MeowcaTheoRange/abtmtr-v13/branches/main")
.catch(() => res.status(500).send())
.then((res) => res.json());
app.locals.curCommit = curCommit.commit.id.substr(0, 10);
}
getCurCommit();
app.get('/', async (req, res) => {
const statusesJson = await fetch("https://cdn.abtmtr.link/site_content/v13/statuses.json")
.catch(() => res.status(500).send())
.then((res) => res.json());
res.render('index', {
statuses: statusesJson
});
})
app.get('/servers', async (req, res) => {
const servicesJson = await fetch("https://cdn.abtmtr.link/site_content/v13/services.json")
.catch(() => res.status(500).send())
.then((res) => res.json());
const computersJson = await fetch("https://cdn.abtmtr.link/site_content/v13/computers.json")
.catch(() => res.status(500).send())
.then((res) => res.json());
res.render('servers', {
services: servicesJson,
computers: computersJson,
visibility: [
"for domain performance",
"for personal use",
"for use by friends of abtmtr.link's webmaster(s)",
"for restricted public use",
"for public use"
]
});
});
var units = {
year: 24 * 60 * 60 * 1000 * 365,
month: 24 * 60 * 60 * 1000 * 365 / 12,
day: 24 * 60 * 60 * 1000,
hour: 60 * 60 * 1000,
minute: 60 * 1000,
second: 1000
}
var rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
var getRelativeTime = (d1, d2 = new Date()) => {
var elapsed = d1 - d2
// "Math.abs" accounts for both "past" & "future" scenarios
for (var u in units)
if (Math.abs(elapsed) > units[u] || u == 'second')
return rtf.format(Math.round(elapsed / units[u]), u)
}
app.get('/about', async (req, res) => {
const linksJson = await fetch("https://cdn.abtmtr.link/site_content/v13/links.json")
.catch(() => res.status(500).send())
.then((res) => res.json());
const currentlyListeningJson = await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=MeowcaTheoRange&api_key=${process.env.LFM_API_KEY}&format=json&limit=2&extended=1`)
.catch(() => res.status(500).send())
.then((res) => res.json());
res.render('about', {
links: linksJson,
cl: currentlyListeningJson.recenttracks.track[0],
getRelativeTime
});
});
app.get('/updates', async (req, res) => {
const rssXML = await fetch("https://cdn.abtmtr.link/site_content/rss.xml")
.catch(() => res.status(500).send())
.then((res) => res.text());
const rssParsed = await rssParser.parseString(rssXML);
res.render('updates', {
rss: rssParsed
});
});
app.get('/sites', async (req, res) => {
const buttonsJson = await fetch("https://cdn.abtmtr.link/site_content/buttons.json")
.catch(() => res.status(500).send())
.then((res) => res.json())
.catch(() => res.status(500).send());
const followingJson = await fetch("https://cdn.abtmtr.link/site_content/following.json")
.catch(() => res.status(500).send())
.then((res) => res.json())
.catch(() => res.status(500).send());
res.render('sites', {
b: buttonsJson, f: followingJson
});
});
app.get('/blurbs', async (req, res) => {
const data = await db
.selectFrom('blurbs')
.selectAll()
.where('verified', '=', 1)
.orderBy('time', "desc")
.execute();
res.render('blurbs', { data });
});
// brbrleibbghbelsbbsbuuebbuubsubss
async function cleanupBlurbles() {
const timeNow = Date.now() - 86400000;
await db
.deleteFrom('blurbs')
.where('time', '<', timeNow)
.where('verified', '=', 0)
.execute()
}
app.get('/blurbs/testsend', async (req, res) => {
res.render('blurbsent', { id: "THISISATESTLOL" });
});
app.get('/blurbs/send', async (req, res) => {
const body = req.query;
const errors = [];
if (body.site == null) errors.push("Site domain required");
else {
const status = await fetch(body.site).then(x => x.status).catch(_ => _);
if (status != 200) errors.push("Site must be online");
}
if (body.text == null) errors.push("Blurb text required");
else {
if (body.text.length < 1) errors.push("Blurb text must not be blank");
if (body.text.length > 140) errors.push("Blurb text must not exceed 140 characters");
}
if (errors.length > 0) return res.status(400).json({
error: "Bad Request",
message: errors.join(", ")
});
try {
const domain = new URL(body.site).hostname;
const isBadSite = await db
.selectFrom('blacklist')
.selectAll()
.where('domain', '=', domain)
.executeTakeFirst();
if (isBadSite != null)
return res.status(403).json({
error: "Forbidden",
message: "\u{1f595}"
});
} catch (err) {
return res.status(500).json({
error: "Internal Server Error",
message: "URL CONSTRUCTOR FAILED???? i think this might be your fault...."
});
}
const postId = nanoid(32);
try {
await db.insertInto('blurbs')
.values({
id: postId,
site: body.site,
blurb: body.text,
verified: 0,
time: Date.now()
})
.executeTakeFirstOrThrow();
} catch (err) {
console.log(err)
return res.status(500).send('Internal Server Error');
}
cleanupBlurbles();
res.render('blurbsent', { id: postId });
});
app.get('/blurbs/check', async (req, res) => {
const body = req.query;
const blurbFromId = await db
.selectFrom('blurbs')
.selectAll()
.where('id', '=', body.id)
.executeTakeFirst();
if (blurbFromId == null) return res.redirect("/blurbs/");
const site = await fetch(blurbFromId.site).then(x => x.text()).catch(_ => _);
const dom = new JSDOM(site);
const relationLink = dom.window.document.querySelector(
`[rel=me][href="https://abtmtr.link/blurbs/#${blurbFromId.id}"]`
);
if (relationLink != null) await db
.updateTable('blurbs')
.set({
verified: 1
})
.where('id', '=', body.id)
.executeTakeFirst();
else {
await db
.updateTable('blurbs')
.set({
verified: 0
})
.where('id', '=', body.id)
.executeTakeFirst();
}
cleanupBlurbles();
res.redirect("/blurbs/");
});
app.get("/favicon.ico", (req, res) => {
res.redirect("https://cdn.abtmtr.link/site_content/favicon.ico")
})
app.get(["/rss", "/rss.xml"], (req, res) => {
res.redirect("https://cdn.abtmtr.link/site_content/rss.xml")
})
app.all('*', (req, res) => {
res.status(404).render('404');
})
app.listen(process.env.PORT, () => {
const url = new URL("http://localhost/");
url.port = process.env.PORT;
console.log(`Example app listening on ${url.toString()}`);
});