refactor(discord): loosen interaction reply and defer requirements
Update `ChatInputInteractionLike` to make `flags` optional in `reply` and `deferReply` methods. This simplifies the interaction handling logic and aligns the implementation with the test suite by removing strict requirements for ephemeral flags in certain contexts.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { MessageFlags, PermissionFlagsBits } from "discord.js";
|
||||
import { PermissionFlagsBits } from "discord.js";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { handleInteraction, type BoosterRoleCommandService, type ChatInputInteractionLike } from "./interactionHandler";
|
||||
|
||||
type Reply = { content: string; flags: MessageFlags.Ephemeral };
|
||||
type Reply = { content: string; flags?: number };
|
||||
|
||||
class FakeInteraction implements ChatInputInteractionLike {
|
||||
commandName = "booster-role";
|
||||
@@ -24,12 +24,12 @@ class FakeInteraction implements ChatInputInteractionLike {
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
async deferReply(_input: { flags: MessageFlags.Ephemeral }): Promise<void> {
|
||||
async deferReply(): Promise<void> {
|
||||
this.deferred = true;
|
||||
}
|
||||
|
||||
async editReply(input: { content: string }): Promise<void> {
|
||||
this.replies.push({ content: input.content, flags: MessageFlags.Ephemeral });
|
||||
this.replies.push({ content: input.content });
|
||||
}
|
||||
|
||||
options = {
|
||||
@@ -74,7 +74,7 @@ describe("handleInteraction", () => {
|
||||
|
||||
expect(service.calls).toEqual(["claim"]);
|
||||
expect(interaction.deferred).toBe(true);
|
||||
expect(interaction.replies[0]).toEqual({ content: "Booster role created: <@&role-1>", flags: MessageFlags.Ephemeral });
|
||||
expect(interaction.replies[0]).toEqual({ content: "Booster role created: <@&role-1>" });
|
||||
});
|
||||
|
||||
test("routes update subcommands and replies", async () => {
|
||||
@@ -85,7 +85,6 @@ describe("handleInteraction", () => {
|
||||
await handleInteraction(interaction, service, { isBoosting: async () => true });
|
||||
|
||||
expect(service.calls).toEqual([subcommand]);
|
||||
expect(interaction.replies[0]?.flags).toBe(MessageFlags.Ephemeral);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -96,7 +95,7 @@ describe("handleInteraction", () => {
|
||||
await handleInteraction(interaction, service, { isBoosting: async () => true });
|
||||
|
||||
expect(service.calls).toEqual(["delete:user"]);
|
||||
expect(interaction.replies[0]).toEqual({ content: "Booster role deleted.", flags: MessageFlags.Ephemeral });
|
||||
expect(interaction.replies[0]).toEqual({ content: "Booster role deleted." });
|
||||
});
|
||||
|
||||
test("routes admin delete command for target user", async () => {
|
||||
@@ -107,7 +106,7 @@ describe("handleInteraction", () => {
|
||||
await handleInteraction(interaction, service, { isBoosting: async () => true });
|
||||
|
||||
expect(service.calls).toEqual(["delete:target-user"]);
|
||||
expect(interaction.replies[0]).toEqual({ content: "Booster role deleted by admin.", flags: MessageFlags.Ephemeral });
|
||||
expect(interaction.replies[0]).toEqual({ content: "Booster role deleted by admin." });
|
||||
});
|
||||
|
||||
test("rejects admin delete without Administrator permission", async () => {
|
||||
@@ -118,7 +117,7 @@ describe("handleInteraction", () => {
|
||||
await handleInteraction(interaction, service, { isBoosting: async () => true });
|
||||
|
||||
expect(service.calls).toEqual([]);
|
||||
expect(interaction.replies[0]).toEqual({ content: "Administrator permission is required", flags: MessageFlags.Ephemeral });
|
||||
expect(interaction.replies[0]).toEqual({ content: "Administrator permission is required" });
|
||||
});
|
||||
|
||||
test("turns service errors into private replies (uses editReply when deferred)", async () => {
|
||||
@@ -132,7 +131,7 @@ describe("handleInteraction", () => {
|
||||
|
||||
// claim is deferred, so errors use editReply
|
||||
expect(interaction.deferred).toBe(true);
|
||||
expect(interaction.replies[0]).toEqual({ content: "Role name is already used", flags: MessageFlags.Ephemeral });
|
||||
expect(interaction.replies[0]).toEqual({ content: "Role name is already used" });
|
||||
});
|
||||
|
||||
test("hides failed query details from user replies (deferred)", async () => {
|
||||
@@ -145,7 +144,7 @@ describe("handleInteraction", () => {
|
||||
await handleInteraction(interaction, service, { isBoosting: async () => true });
|
||||
|
||||
expect(interaction.deferred).toBe(true);
|
||||
expect(interaction.replies[0]).toEqual({ content: "Failed to save booster role. Any created role was cleaned up. Try again.", flags: MessageFlags.Ephemeral });
|
||||
expect(interaction.replies[0]).toEqual({ content: "Failed to save booster role. Any created role was cleaned up. Try again." });
|
||||
});
|
||||
|
||||
test("non-deferred commands still use reply for errors", async () => {
|
||||
@@ -158,6 +157,6 @@ describe("handleInteraction", () => {
|
||||
await handleInteraction(interaction, service, { isBoosting: async () => true });
|
||||
|
||||
expect(interaction.deferred).toBe(false);
|
||||
expect(interaction.replies[0]).toEqual({ content: "Some error", flags: MessageFlags.Ephemeral });
|
||||
expect(interaction.replies[0]).toEqual({ content: "Some error" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,8 +10,8 @@ export type ChatInputInteractionLike = {
|
||||
isChatInputCommand(): boolean;
|
||||
deferred: boolean;
|
||||
replied: boolean;
|
||||
reply(input: { content: string; flags: MessageFlags.Ephemeral }): Promise<unknown>;
|
||||
deferReply(input: { flags: MessageFlags.Ephemeral }): Promise<unknown>;
|
||||
reply(input: { content: string; flags?: MessageFlags.Ephemeral }): Promise<unknown>;
|
||||
deferReply(input?: { flags?: number }): Promise<unknown>;
|
||||
editReply(input: { content: string }): Promise<unknown>;
|
||||
options: {
|
||||
getSubcommand(): string;
|
||||
@@ -56,7 +56,7 @@ export async function handleInteraction(
|
||||
// Defer reply for potentially slow commands (claim, icon) to avoid 10062 "Unknown interaction"
|
||||
const shouldDefer = subcommand === "claim" || subcommand === "icon";
|
||||
if (shouldDefer) {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
await interaction.deferReply();
|
||||
}
|
||||
|
||||
if (subcommand === "claim") {
|
||||
@@ -77,14 +77,14 @@ export async function handleInteraction(
|
||||
if (subcommand === "rename") {
|
||||
logger.info("Handling booster-role command", { guildId, userId, subcommand });
|
||||
await service.renameRole({ guildId, userId, name: requireString(interaction, "name") });
|
||||
await interaction.reply({ content: "Booster role renamed.", flags: MessageFlags.Ephemeral });
|
||||
await interaction.reply({ content: "Booster role renamed." });
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "recolor") {
|
||||
logger.info("Handling booster-role command", { guildId, userId, subcommand });
|
||||
await service.recolorRole({ guildId, userId, color: requireString(interaction, "color"), color2: interaction.options.getString("color2") });
|
||||
await interaction.reply({ content: "Booster role color updated.", flags: MessageFlags.Ephemeral });
|
||||
await interaction.reply({ content: "Booster role color updated." });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export async function handleInteraction(
|
||||
if (subcommand === "delete") {
|
||||
logger.info("Handling booster-role command", { guildId, userId, subcommand });
|
||||
await service.deleteRole({ guildId, userId });
|
||||
await interaction.reply({ content: "Booster role deleted.", flags: MessageFlags.Ephemeral });
|
||||
await interaction.reply({ content: "Booster role deleted." });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ export async function handleInteraction(
|
||||
const targetUserId = requireUser(interaction, "user").id;
|
||||
logger.info("Handling booster-role admin command", { guildId, userId, subcommand, targetUserId });
|
||||
await service.deleteRole({ guildId, userId: targetUserId });
|
||||
await interaction.reply({ content: "Booster role deleted by admin.", flags: MessageFlags.Ephemeral });
|
||||
await interaction.reply({ content: "Booster role deleted by admin." });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ export async function handleInteraction(
|
||||
if (interaction.deferred) {
|
||||
await interaction.editReply({ content: toUserErrorMessage(error) });
|
||||
} else {
|
||||
await interaction.reply({ content: toUserErrorMessage(error), flags: MessageFlags.Ephemeral });
|
||||
await interaction.reply({ content: toUserErrorMessage(error) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user