Spaces:
Sleeping
Sleeping
| 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<void> { | |
| 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<void> { | |
| 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`); | |
| } | |
| } |