feat: enhance role handling by updating reply structure and adding role assignment functionality

This commit is contained in:
MythEclipse
2026-05-16 00:32:29 +07:00
parent c6ee7bb8c6
commit 41c6b567a6
6 changed files with 38 additions and 18 deletions
+7 -3
View File
@@ -37,7 +37,11 @@ export function attachBotHandlers(client: Client, config: AppConfig): void {
});
}
function resolveAnchorPosition(guild: { roles: { cache: { get(id: string): { position: number } | undefined } } }, anchorRoleId: string | null): number {
if (!anchorRoleId) return 1;
return guild.roles.cache.get(anchorRoleId)?.position ?? 1;
function resolveAnchorPosition(guild: { members: { me: { roles: { highest: { position: number } } } | null }; roles: { cache: { get(id: string): { position: number } | undefined } } }, anchorRoleId: string | null): number {
if (!anchorRoleId) return botRolePosition(guild);
return guild.roles.cache.get(anchorRoleId)?.position ?? botRolePosition(guild);
}
function botRolePosition(guild: { members: { me: { roles: { highest: { position: number } } } | null } }): number {
return guild.members.me?.roles.highest.position ?? 1;
}
+5 -4
View File
@@ -1,7 +1,8 @@
import { MessageFlags } from "discord.js";
import { describe, expect, test } from "bun:test";
import { handleInteraction, type BoosterRoleCommandService, type ChatInputInteractionLike } from "./interactionHandler";
type Reply = { content: string; ephemeral: boolean };
type Reply = { content: string; flags: MessageFlags.Ephemeral };
class FakeInteraction implements ChatInputInteractionLike {
commandName = "booster-role";
@@ -59,7 +60,7 @@ describe("handleInteraction", () => {
await handleInteraction(interaction, service, { isBoosting: async () => true });
expect(service.calls).toEqual(["claim"]);
expect(interaction.replies[0]).toEqual({ content: "Booster role created: <@&role-1>", ephemeral: true });
expect(interaction.replies[0]).toEqual({ content: "Booster role created: <@&role-1>", flags: MessageFlags.Ephemeral });
});
test("routes update subcommands and replies", async () => {
@@ -70,7 +71,7 @@ describe("handleInteraction", () => {
await handleInteraction(interaction, service, { isBoosting: async () => true });
expect(service.calls).toEqual([subcommand]);
expect(interaction.replies[0]?.ephemeral).toBe(true);
expect(interaction.replies[0]?.flags).toBe(MessageFlags.Ephemeral);
}
});
@@ -83,6 +84,6 @@ describe("handleInteraction", () => {
await handleInteraction(interaction, service, { isBoosting: async () => true });
expect(interaction.replies[0]).toEqual({ content: "Role name is already used", ephemeral: true });
expect(interaction.replies[0]).toEqual({ content: "Role name is already used", flags: MessageFlags.Ephemeral });
});
});
+8 -7
View File
@@ -1,3 +1,4 @@
import { MessageFlags } from "discord.js";
import type { BoosterRoleRecord, RoleIcon } from "../services/boosterRoleService";
export type ChatInputInteractionLike = {
@@ -5,7 +6,7 @@ export type ChatInputInteractionLike = {
guildId: string | null;
user: { id: string };
isChatInputCommand(): boolean;
reply(input: { content: string; ephemeral: boolean }): Promise<unknown>;
reply(input: { content: string; flags: MessageFlags.Ephemeral }): Promise<unknown>;
options: {
getSubcommand(): string;
getString(name: string): string | null;
@@ -46,37 +47,37 @@ export async function handleInteraction(
icon: optionalIcon(interaction, "icon"),
isBoosting: await deps.isBoosting(guildId, userId)
});
await interaction.reply({ content: `Booster role created: <@&${role.roleId}>`, ephemeral: true });
await interaction.reply({ content: `Booster role created: <@&${role.roleId}>`, flags: MessageFlags.Ephemeral });
return;
}
if (subcommand === "rename") {
await service.renameRole({ guildId, userId, name: requireString(interaction, "name") });
await interaction.reply({ content: "Booster role renamed.", ephemeral: true });
await interaction.reply({ content: "Booster role renamed.", flags: MessageFlags.Ephemeral });
return;
}
if (subcommand === "recolor") {
await service.recolorRole({ guildId, userId, color: requireString(interaction, "color") });
await interaction.reply({ content: "Booster role color updated.", ephemeral: true });
await interaction.reply({ content: "Booster role color updated.", flags: MessageFlags.Ephemeral });
return;
}
if (subcommand === "icon") {
await service.setRoleIcon({ guildId, userId, icon: requireIcon(interaction, "image") });
await interaction.reply({ content: "Booster role icon updated.", ephemeral: true });
await interaction.reply({ content: "Booster role icon updated.", flags: MessageFlags.Ephemeral });
return;
}
if (subcommand === "delete") {
await service.deleteRole({ guildId, userId });
await interaction.reply({ content: "Booster role deleted.", ephemeral: true });
await interaction.reply({ content: "Booster role deleted.", flags: MessageFlags.Ephemeral });
return;
}
throw new Error("Unknown booster-role subcommand");
} catch (error) {
await interaction.reply({ content: error instanceof Error ? error.message : "Command failed", ephemeral: true });
await interaction.reply({ content: error instanceof Error ? error.message : "Command failed", flags: MessageFlags.Ephemeral });
}
}