sourceapphelpers.js

import hljs from "highlight.js";
import { format } from "prettier";

import Database from "better-sqlite3";

import { verifyComment } from "../shush/shush.js";

import grimm from "./grimm.js";
import md5 from "./md5.js";
import rubric from "./rubric.js";
import pugTheme from "./pug-highlight.js";

const db = new Database("./db/site.db");
db.pragma("journal_mode = DELETE");

hljs.registerLanguage("pug", pugTheme);

rubric.langs.html.els.heading = (node, opt, lang) => {
	const parsed = lang.parse(node.text, opt);
	const headerSlug = grimm.slugify(parsed, { html: true });
	return `\n<h${node.level}${
		opt.hyperlinkHeadings && node.level > 1 ? ` id="${headerSlug}"` : ""
	}>${
		opt.hyperlinkHeadings && node.level > 1 && !parsed.includes("<a ")
			? `<a href="#${headerSlug}">${parsed}</a>`
			: parsed
	}</h${node.level}>\n`;
};

rubric.langs.html.els.pre = (node, opt, lang) => {
	if (!opt.highlightCode) {
		return `\n<pre><code>${lang.escape(node.text)}</code></pre>\n`;
	}

	let highlit;
	if (node.language) {
		highlit = hljs.highlight(node.text, {
			language: node.language
		});
	} else {
		highlit = hljs.highlightAuto(node.text);
	}
	return `\n<pre><code class="hljs language-${highlit.language}">${highlit.value}</code></pre>\n`;
};

const getRubricFilters = rbc => ({
	rubric: text => {
		return rbc.parse(text);
	},
	"rubric-comment": (text, options = {}) => {
		let parsed;
		try {
			parsed = rbc.parse(text, "html", rbc.options.comments);
		} catch (e) {
			parsed = `<i>Formatting error</i>: <pre>${e}</pre>`;
		}
		return parsed;
	},
	"rubric-unsafe": (text, options = {}) => {
		let parsed;
		try {
			parsed = rbc.parse(text, "html", rbc.options.risky);
		} catch (e) {
			parsed = `<i>Formatting error</i>: <pre>${e}</pre>`;
		}
		return options.afterwards ? options.afterwards(parsed) : parsed;
	},
	"rubric-inline": (text, options = {}) => {
		let parsed;
		try {
			parsed = rbc.parse(text, "html", rbc.options.inlineRisky);
		} catch (e) {
			parsed = `<i>Formatting error</i>: <pre>${e}</pre>`;
		}
		return options.afterwards ? options.afterwards(parsed) : parsed;
	}
});

const highlightFilter = {
	highlight: (text, options) => {
		let highlit;
		if (options.lang) {
			highlit = hljs.highlight(text, {
				language: options.lang
			});
		} else {
			highlit = hljs.highlightAuto(text);
		}
		return `<code class="hljs language-${highlit.language}">${highlit.value}</code>`;
	}
};

const auth = permission => {
	if (permission) {
		return (req, res, next) => {
			if (req?.user) {
				req.user.prefs = db
					.prepare(`Select * From user_preferences Where userID = ?`)
					.get(req.user.userID);
			}
			if (req?.user?.permissions === permission) {
				return next();
			}
			return next(createError(403));
		};
	} else {
		return (req, res, next) => {
			if (req?.user) {
				req.user.prefs = db
					.prepare(`Select * From user_preferences Where userID = ?`)
					.get(req.user.userID);
			}
			if (req.isAuthenticated()) {
				return next();
			}
			return next(createError(403));
		};
	}
};

const base32 = int => {
	const digits = "0123456789abcdefghjkmnpqrstvwxyz";
	let thirtyTwo = "";
	let quotient = int;

	while (quotient > 0) {
		thirtyTwo = `${digits[quotient % 32]}${thirtyTwo}`;
		quotient = Math.floor(quotient / 32);
	}

	return thirtyTwo.padStart(8, "0");
};

const comparePages = (a, b) => {
	const isOriginal = x =>
		"slug" in x && "translates" in x && x.slug == x.translates;
	const localeSort = x => grimm.dict?.[x.lang]?.meta?.sort ?? x.lang;

	if (isOriginal(a) > isOriginal(b) || localeSort(a) < localeSort(b)) {
		return -1;
	}
	if (isOriginal(a) < isOriginal(b) || localeSort(a) > localeSort(b)) {
		return 1;
	}
	// a must be equal to b
	return 0;
};

const getComments = fullSlug =>
	db
		.prepare(
			`Select * From comments
			Where page_id = ? And planet = 'earth' And shown = 1`
		)
		.all(fullSlug)
		.map(cmt => ({
			id: cmt.id,
			pageId: fullSlug,
			name: cmt.name,
			hash: {
				cmt: cmt.hashcode,
				tripcode: cmt.hashcode
					? base32(parseInt(cmt.hashcode.slice(0, 10), 16))
					: null,
				avatar: cmt.hashcode ? cmt.hashcode[0] : md5(cmt.name)[0]
			},
			website: cmt.website
				? cmt.website.includes("//")
					? cmt.website
					: `//${cmt.website}`
				: null,
			content: cmt.content,
			submitDate: cmt["submit_date"].match(/[-+][0-9]{2}:|Z/)
				? cmt["submit_date"].replace(" ", "T")
				: `${cmt["submit_date"].replace(" ", "T")}Z`,
			country: cmt.country,
			lang: cmt.lang,
			planet: cmt.planet,
			verified: verifyComment(cmt)
		}));

const prettyRender = (res, next, url, data) =>
	res.render(url, data, (err, html) => {
		if (err) {
			return next(err);
		}

		let sent;
		try {
			sent = format(html, {
				useTabs: true,
				tabWidth: 4,
				bracketSameLine: true,
				trailingComma: "none",
				arrowParens: "avoid",
				parser: "html",
				printWidth: 128
			});
		} catch {
			sent = html;
		}

		return res.send(sent);
	});

const rootFolder = "/home/atossa/server/satyrsforest";

export {
	rubric,
	getRubricFilters,
	highlightFilter,
	auth,
	base32,
	comparePages,
	getComments,
	prettyRender,
	rootFolder,
	md5
};