File size: 4,457 Bytes
33f687c
 
 
a86efc3
 
33f687c
a484614
a86efc3
 
 
a484614
2fa53e9
f62bcf0
b2a321b
a86efc3
2fa53e9
 
 
a484614
a86efc3
a484614
a86efc3
 
 
2fa53e9
 
 
 
a484614
2fa53e9
 
 
2b2a93b
 
2fa53e9
2b2a93b
a484614
2b2a93b
 
33f687c
a86efc3
144f7f2
9ad973a
 
 
2fa53e9
 
 
9f32484
 
2fa53e9
9f32484
 
9ad973a
a484614
2fa53e9
9ad973a
 
9f32484
9ad973a
 
 
a484614
2fa53e9
9ad973a
 
 
222e54f
9ad973a
2fa53e9
 
 
9ad973a
 
 
 
 
2fa53e9
 
 
 
 
9ad973a
 
 
a484614
9ad973a
60c1a52
9ad973a
a86efc3
 
2fa53e9
a484614
2fa53e9
 
 
 
 
 
a86efc3
33f687c
 
 
 
e502e8d
33f687c
2fa53e9
 
 
 
289ca2d
 
 
2fa53e9
 
 
 
 
 
 
a484614
a86efc3
e6a8780
 
2fa53e9
e6a8780
 
a86efc3
e6a8780
33f687c
 
a86efc3
2fa53e9
 
 
a86efc3
2fa53e9
9ad973a
2fa53e9
 
9ad973a
2fa53e9
 
 
 
 
33f687c
a86efc3
 
2fa53e9
 
a484614
2fa53e9
a484614
a86efc3
9ad973a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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`);
    }
}