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()}`); });