fix: swallow expired discord slash interactions

This commit is contained in:
Peter Steinberger
2026-04-05 16:50:05 +01:00
parent df38bc2271
commit a6d0ab1482
3 changed files with 35 additions and 1 deletions

View File

@@ -121,6 +121,7 @@ describe("Discord native slash commands with commands.allowFrom", () => {
it("authorizes guild slash commands when commands.allowFrom.discord matches the sender", async () => {
const { dispatchSpy, interaction } = await runGuildSlashCommand();
expect(interaction.defer).toHaveBeenCalledTimes(1);
expect(dispatchSpy).toHaveBeenCalledTimes(1);
expectNotUnauthorizedReply(interaction);
});
@@ -240,4 +241,29 @@ describe("Discord native slash commands with commands.allowFrom", () => {
expect(interaction.reply).toHaveBeenCalledWith(expect.objectContaining({ content: longReply }));
expect(interaction.followUp).not.toHaveBeenCalled();
});
it("swallows expired slash interactions before dispatch when defer returns Unknown interaction", async () => {
const cfg = createConfig();
const command = createCommand(cfg);
const interaction = createInteraction();
interaction.defer.mockRejectedValue({
status: 404,
discordCode: 10062,
rawBody: {
message: "Unknown interaction",
code: 10062,
},
});
vi.spyOn(pluginCommandsModule, "matchPluginCommand").mockReturnValue(null);
const dispatchSpy = createDispatchSpy();
await expect(
(command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown),
).resolves.toBeUndefined();
expect(interaction.defer).toHaveBeenCalledTimes(1);
expect(dispatchSpy).not.toHaveBeenCalled();
expect(interaction.reply).not.toHaveBeenCalled();
expect(interaction.followUp).not.toHaveBeenCalled();
});
});

View File

@@ -11,6 +11,7 @@ export type MockCommandInteraction = {
getNumber: ReturnType<typeof vi.fn>;
getBoolean: ReturnType<typeof vi.fn>;
};
defer: ReturnType<typeof vi.fn>;
reply: ReturnType<typeof vi.fn>;
followUp: ReturnType<typeof vi.fn>;
client: object;
@@ -55,6 +56,7 @@ export function createMockCommandInteraction(
getNumber: vi.fn().mockReturnValue(null),
getBoolean: vi.fn().mockReturnValue(null),
},
defer: vi.fn().mockResolvedValue(undefined),
reply: vi.fn().mockResolvedValue({ ok: true }),
followUp: vi.fn().mockResolvedValue({ ok: true }),
client: {},

View File

@@ -637,11 +637,17 @@ export function createDiscordNativeCommand(params: {
value: command.description,
label: `command:${command.name}`,
});
defer = true;
defer = false;
ephemeral = ephemeralDefault;
options = options;
async run(interaction: CommandInteraction) {
const deferred = await safeDiscordInteractionCall("interaction defer", () =>
interaction.defer(),
);
if (deferred === null) {
return;
}
const commandArgs = argDefinitions?.length
? readDiscordCommandArgs(interaction, argDefinitions)
: command.acceptsArgs