feat: worst-scan fansub web — initial release
Posts engine (SQLite + auto-publisher via poller), public feed with clickable tags, reader, admin panel, submit/search/queue tools. BFF proxy to pipeline API. Clean dark design. Docker-ready.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
|
||||
const COVERS_DIR = process.env.COVERS_DIR || path.join(process.cwd(), "data", "covers")
|
||||
|
||||
function ensureDir() {
|
||||
fs.mkdirSync(COVERS_DIR, { recursive: true })
|
||||
}
|
||||
|
||||
function extFromContentType(ct: string | null): string {
|
||||
if (!ct) return ".jpg"
|
||||
const m = ct.match(/image\/(\w+)/)
|
||||
if (!m) return ".jpg"
|
||||
const exts: Record<string, string> = {
|
||||
jpeg: ".jpg",
|
||||
png: ".png",
|
||||
webp: ".webp",
|
||||
gif: ".gif",
|
||||
}
|
||||
return exts[m[1]] || ".jpg"
|
||||
}
|
||||
|
||||
export async function cacheCover(gid: string, proxyUrl: string): Promise<string | null> {
|
||||
ensureDir()
|
||||
try {
|
||||
const res = await fetch(proxyUrl, { signal: AbortSignal.timeout(15000) })
|
||||
if (!res.ok) return null
|
||||
|
||||
const buffer = Buffer.from(await res.arrayBuffer())
|
||||
const ext = extFromContentType(res.headers.get("content-type"))
|
||||
const filePath = path.join(COVERS_DIR, `${gid}${ext}`)
|
||||
|
||||
fs.writeFileSync(filePath, buffer)
|
||||
return filePath
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function getCoverPath(gid: string): string | null {
|
||||
ensureDir()
|
||||
const files = fs.readdirSync(COVERS_DIR).filter((f) => f.startsWith(gid))
|
||||
if (files.length === 0) return null
|
||||
return path.join(COVERS_DIR, files[0])
|
||||
}
|
||||
|
||||
export function getCoverContentType(gid: string): string {
|
||||
const fp = getCoverPath(gid)
|
||||
if (!fp) return "image/jpeg"
|
||||
const ext = path.extname(fp).toLowerCase()
|
||||
const m: Record<string, string> = {
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".webp": "image/webp",
|
||||
".gif": "image/gif",
|
||||
}
|
||||
return m[ext] || "image/jpeg"
|
||||
}
|
||||
|
||||
export function deleteCover(gid: string): void {
|
||||
const fp = getCoverPath(gid)
|
||||
if (fp) fs.unlinkSync(fp)
|
||||
}
|
||||
Reference in New Issue
Block a user