import path from "node:path"; import { listFiles, downloadFile } from "@huggingface/hub"; import { sha256 } from "../utils/sha256"; import { Mod } from "./mod"; import { Server } from "../server/server"; import { Logger } from "../logger/logger"; export class ModManager { private server: Server; private logger: Logger; private bucketId = "buckets/NSMP/NSMP-Server-Storage"; private bucketFolder = "mods"; private modCache: Mod[] = []; private refreshIntervalMs = 60_000; // 1 minute constructor(server: Server, logger: Logger) { this.server = server; this.logger = logger; } public async createModEndpoints(): Promise { await this.refreshModCache(); setInterval(() => { this.refreshModCache().catch(err => { this.logger.error("Failed to refresh mod cache:", err); }); }, this.refreshIntervalMs); this.server.get("/mods/", async (_req, res) => { try { res.send(JSON.stringify(this.modCache)); } catch (err) { this.logger.error("Failed to list mods:", err); res.status(500).send("Failed to list mods"); } }); this.server.get("/files/mods/:filename", async (req, res) => { try { const filename = req.params.filename; const mod = this.modCache.find( m => m.filename === filename ); if (!mod) { res.status(404).send("Mod not found"); return; } this.logger.time(`download:${filename}`); const blob = await downloadFile({ repo: this.bucketId, path: mod.path, accessToken: process.env.HF_TOKEN }); this.logger.timeEnd(`download:${filename}`); if (!blob) { res.status(404).send("Mod not found"); return; } const buffer = Buffer.from( await blob.arrayBuffer() ); res.setHeader( "Content-Disposition", `attachment; filename="${filename}"` ); res.setHeader( "Content-Type", "application/java-archive" ); res.send(buffer); } catch (err) { this.logger.error(err as string); res.status(500).send("Failed to fetch mod from bucket"); } }); } private async refreshModCache(): Promise { this.logger.time("refreshModCache"); const existing = new Map( this.modCache.map(mod => [mod.path, mod]) ); const updated: Mod[] = []; for await (const item of listFiles({ repo: this.bucketId, path: this.bucketFolder, recursive: true, accessToken: process.env.HF_TOKEN })) { if ( item.type !== "file" || !item.path.toLowerCase().endsWith(".jar") ) { continue; } const cached = existing.get(item.path); if (cached) { updated.push(cached); continue; } this.logger.info(`New mod detected: ${item.path}`); const jarBlob = await downloadFile({ repo: this.bucketId, path: item.path, accessToken: process.env.HF_TOKEN }); if (!jarBlob) { continue; } const jarBuffer = Buffer.from( await jarBlob.arrayBuffer() ); const filename = path.basename(item.path); updated.push({ name: filename.replace(/\.jar$/i, ""), filename, path: item.path, sha256: sha256(jarBuffer), url: `https://NSMP-NSMP-Server.hf.space/files/mods/${encodeURIComponent( filename )}` }); } this.modCache = updated; this.logger.timeEnd("refreshModCache"); this.logger.info(`Cached ${this.modCache.length} mods`); } }