




















@@ -1,4 +1,6 @@
11import { createHash } from "node:crypto";
2+import fs from "node:fs/promises";
3+import path from "node:path";
24import { ApplicationCommandType, type APIApplicationCommand } from "discord-api-types/v10";
35import {
46createApplicationCommand,
@@ -20,12 +22,14 @@ type SerializedCommand = ReturnType<BaseCommand["serialize"]>;
20222123export class DiscordCommandDeployer {
2224private readonly hashes = new Map<string, string>();
25+private hashesLoaded = false;
23262427constructor(
2528private readonly params: {
2629clientId: string;
2730commands: BaseCommand[];
2831devGuilds?: string[];
32+hashStorePath?: string;
2933rest: () => RequestClient;
3034},
3135) {}
@@ -124,11 +128,67 @@ export class DiscordCommandDeployer {
124128options: { force?: boolean },
125129): Promise<void> {
126130const hash = stableCommandSetHash(commands);
131+await this.loadPersistedHashes();
127132if (!options.force && this.hashes.get(key) === hash) {
128133return;
129134}
130135await deploy();
131136this.hashes.set(key, hash);
137+await this.persistHashes();
138+}
139+140+private async loadPersistedHashes(): Promise<void> {
141+if (this.hashesLoaded) {
142+return;
143+}
144+this.hashesLoaded = true;
145+const storePath = this.params.hashStorePath;
146+if (!storePath) {
147+return;
148+}
149+try {
150+const raw = await fs.readFile(storePath, "utf8");
151+const parsed = JSON.parse(raw) as { hashes?: unknown };
152+if (!parsed.hashes || typeof parsed.hashes !== "object") {
153+return;
154+}
155+for (const [key, value] of Object.entries(parsed.hashes)) {
156+if (typeof value === "string" && key.trim() && value.trim()) {
157+this.hashes.set(key, value);
158+}
159+}
160+} catch {
161+// Best-effort cache only. A corrupt or missing file should never block startup.
162+}
163+}
164+165+private async persistHashes(): Promise<void> {
166+const storePath = this.params.hashStorePath;
167+if (!storePath) {
168+return;
169+}
170+try {
171+await fs.mkdir(path.dirname(storePath), { recursive: true });
172+const tmpPath = `${storePath}.${process.pid}.${Date.now()}.tmp`;
173+await fs.writeFile(
174+tmpPath,
175+`${JSON.stringify(
176+ {
177+ version: 1,
178+ updatedAt: new Date().toISOString(),
179+ hashes: Object.fromEntries(
180+ [...this.hashes.entries()].toSorted(([left], [right]) => left.localeCompare(right)),
181+ ),
182+ },
183+ null,
184+ 2,
185+ )}\n`,
186+"utf8",
187+);
188+await fs.rename(tmpPath, storePath);
189+} catch {
190+// The cache is only an optimization to avoid redundant Discord writes.
191+}
132192}
133193134194private get rest(): RequestClient {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。