181 lines
5.8 KiB
JavaScript
181 lines
5.8 KiB
JavaScript
import cfg from "../config.mjs";
|
|
import fs from "fs/promises";
|
|
import path from "path";
|
|
import db from "../sql.mjs";
|
|
import queue from "../queue.mjs";
|
|
import { isHiddenItem } from "../hidden_items.mjs";
|
|
|
|
export default (router, tpl) => {
|
|
router.static({
|
|
dir: cfg.paths.b,
|
|
route: /^\/b\//
|
|
});
|
|
|
|
router.static({
|
|
dir: cfg.paths.c,
|
|
route: /^\/c\//
|
|
});
|
|
|
|
router.static({
|
|
dir: cfg.paths.emojis,
|
|
route: /^\/s\/emojis\//
|
|
});
|
|
|
|
router.static({
|
|
dir: cfg.paths.koepfe,
|
|
route: /^\/s\/koepfe\//
|
|
});
|
|
|
|
router.static({
|
|
dir: cfg.paths.fonts,
|
|
route: /^\/s\/fonts\//
|
|
});
|
|
|
|
router.static({
|
|
dir: path.join(path.resolve(), 'node_modules/@ruffle-rs/ruffle'),
|
|
route: /^\/s\/ruffle\//
|
|
});
|
|
|
|
router.static({
|
|
dir: cfg.paths.s,
|
|
route: /^\/s\//
|
|
});
|
|
|
|
const getImgMime = (filename) => {
|
|
if (filename.endsWith('.webp')) return 'image/webp';
|
|
if (filename.endsWith('.png')) return 'image/png';
|
|
if (filename.endsWith('.gif')) return 'image/gif';
|
|
if (filename.endsWith('.jpg') || filename.endsWith('.jpeg')) return 'image/jpeg';
|
|
return 'application/octet-stream';
|
|
};
|
|
|
|
// <id>.webp / <id>_blur.webp belonging to an item that isn't live (pending, deleted) is never public,
|
|
// even if a stale file with that name exists. Uploaders use /pending/t/, staff /mod/pending/t/.
|
|
const isHiddenMedia = async (file) => {
|
|
const m = /^(\d+)(?:_blur)?\.webp$/.exec(file);
|
|
return !!m && await isHiddenItem(m[1]);
|
|
};
|
|
|
|
const notFound = (res) => {
|
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
return res.end('404 - file not found.');
|
|
};
|
|
|
|
router.get(/^\/t\/(?<file>.+)$/, async (req, res) => {
|
|
const file = req.params.file;
|
|
if (await isHiddenMedia(file)) return notFound(res);
|
|
const filePath = path.join(cfg.paths.t, file);
|
|
try {
|
|
const stat = await fs.stat(filePath);
|
|
if (stat.isFile()) {
|
|
const content = await fs.readFile(filePath);
|
|
res.writeHead(200, {
|
|
'Content-Length': stat.size,
|
|
'Content-Type': getImgMime(file),
|
|
'Cache-Control': 'public, max-age=3600'
|
|
});
|
|
return res.end(content);
|
|
}
|
|
} catch (_) {}
|
|
|
|
// Dynamic generation or fallback for subf0cks / albums
|
|
const base = file.replace(/\.[^.]+$/, '').replace(/_blur$/, '');
|
|
try {
|
|
const subRow = await db`SELECT id, item_id, dest, mime FROM album_items WHERE dest LIKE ${base + '.%'} LIMIT 1`;
|
|
if (subRow.length > 0) {
|
|
const sub = subRow[0];
|
|
const srcFile = path.join(cfg.paths.b, sub.dest);
|
|
const thumbDest = path.join(cfg.paths.t, `${base}.webp`);
|
|
const srcStat = await fs.stat(srcFile).catch(() => null);
|
|
if (srcStat && srcStat.size > 0) {
|
|
if (sub.mime.startsWith('image/')) {
|
|
await queue.spawn('magick', [srcFile + '[0]', '-resize', '256x256^', '-gravity', 'center', '-crop', '256x256+0+0', '+repage', thumbDest]);
|
|
} else if (sub.mime.startsWith('video/')) {
|
|
await queue.spawn('ffmpegthumbnailer', ['-i', srcFile, '-s', '256', '-o', thumbDest]).catch(async () => {
|
|
await queue.spawn('ffmpeg', ['-y', '-ss', '00:00:01', '-i', srcFile, '-vframes', '1', '-vf', 'scale=256:256:force_original_aspect_ratio=increase,crop=256:256', thumbDest]);
|
|
});
|
|
}
|
|
const genStat = await fs.stat(thumbDest).catch(() => null);
|
|
if (genStat && genStat.size > 0) {
|
|
const content = await fs.readFile(thumbDest);
|
|
res.writeHead(200, {
|
|
'Content-Length': genStat.size,
|
|
'Content-Type': 'image/webp',
|
|
'Cache-Control': 'public, max-age=3600'
|
|
});
|
|
return res.end(content);
|
|
}
|
|
}
|
|
// Fallback to parent album thumbnail if subf0ck media file is missing
|
|
const parentThumb = path.join(cfg.paths.t, `${sub.item_id}.webp`);
|
|
const pStat = await fs.stat(parentThumb).catch(() => null);
|
|
if (pStat && pStat.size > 0) {
|
|
const content = await fs.readFile(parentThumb);
|
|
res.writeHead(200, {
|
|
'Content-Length': pStat.size,
|
|
'Content-Type': 'image/webp',
|
|
'Cache-Control': 'public, max-age=3600'
|
|
});
|
|
return res.end(content);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('[STATIC /t/] Dynamic subf0ck thumbnail generation error:', e.message);
|
|
}
|
|
|
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
return res.end('404 - file not found.');
|
|
});
|
|
|
|
router.get(/^\/ca\/(?<file>.+)$/, async (req, res) => {
|
|
const file = req.params.file;
|
|
if (await isHiddenMedia(file)) return notFound(res);
|
|
const filePath = path.join(cfg.paths.ca, file);
|
|
try {
|
|
const stat = await fs.stat(filePath);
|
|
if (stat.isFile()) {
|
|
const content = await fs.readFile(filePath);
|
|
res.writeHead(200, {
|
|
'Content-Length': stat.size,
|
|
'Content-Type': getImgMime(file),
|
|
'Cache-Control': 'public, max-age=3600'
|
|
});
|
|
return res.end(content);
|
|
}
|
|
} catch (_) {}
|
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
return res.end('404 - file not found.');
|
|
});
|
|
|
|
router.static({
|
|
dir: cfg.paths.memes,
|
|
route: /^\/memes\//
|
|
});
|
|
|
|
router.static({
|
|
dir: cfg.paths.a,
|
|
route: /^\/a\//
|
|
});
|
|
|
|
router.get(/^\/robots\.txt$/, async (req, res) => {
|
|
res.reply({
|
|
type: "text/plain",
|
|
body: await fs.readFile(path.join(cfg.paths.s, "../robots.txt"), "utf-8")
|
|
});
|
|
});
|
|
|
|
router.get(/^\/manifest\.json$/, async (req, res) => {
|
|
res.reply({
|
|
type: "application/json",
|
|
body: await fs.readFile(path.join(cfg.paths.s, "../manifest.json"), "utf-8")
|
|
});
|
|
});
|
|
|
|
router.get(/^\/sw\.js$/, async (req, res) => {
|
|
res.reply({
|
|
type: "application/javascript",
|
|
body: await fs.readFile(path.join(cfg.paths.s, "../sw.js"), "utf-8")
|
|
});
|
|
});
|
|
};
|