From 3c256ba0b2dc23f48c94fa2df2afbab0e43f1b6f Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Thu, 2 Apr 2026 11:36:35 +0200 Subject: [PATCH] refactor: centralize custom interaction IDs into constants Replace all hardcoded custom ID strings with module-level constants. Each module now has *_CUSTOM_IDS in its types file, using functions for dynamic IDs and PREFIX for startsWith matching. Co-Authored-By: Claude Opus 4.6 (1M context) --- bot/commands/admin/prune.ts | 3 +- bot/commands/quest/quests.ts | 19 +++--- bot/lib/interaction.routes.ts | 23 +++---- bot/modules/admin/item_wizard.ts | 60 +++++++++---------- bot/modules/admin/item_wizard.types.ts | 31 ++++++++++ bot/modules/admin/item_wizard.view.ts | 56 ++++++++--------- bot/modules/economy/economy.types.ts | 10 ++++ bot/modules/economy/lootdrop.interaction.ts | 3 +- bot/modules/economy/lootdrop.view.ts | 5 +- bot/modules/economy/shop.interaction.ts | 5 +- bot/modules/economy/shop.view.ts | 3 +- bot/modules/feedback/feedback.interaction.ts | 2 +- bot/modules/feedback/feedback.types.ts | 4 +- bot/modules/feedback/feedback.view.ts | 2 +- .../inventory/inventory.interaction.ts | 3 +- bot/modules/inventory/inventory.types.ts | 13 ++++ bot/modules/inventory/inventory.view.ts | 21 +++---- bot/modules/moderation/prune.types.ts | 5 ++ bot/modules/moderation/prune.view.ts | 6 +- bot/modules/quest/quest.types.ts | 8 +++ bot/modules/quest/quest.view.ts | 11 ++-- bot/modules/trade/trade.interaction.ts | 25 ++++---- bot/modules/trade/trade.types.ts | 13 ++++ bot/modules/trade/trade.view.ts | 16 ++--- bot/modules/trivia/trivia.types.ts | 7 +++ bot/modules/trivia/trivia.view.ts | 13 ++-- bot/modules/user/user.types.ts | 3 + 27 files changed, 238 insertions(+), 132 deletions(-) create mode 100644 bot/modules/economy/economy.types.ts create mode 100644 bot/modules/inventory/inventory.types.ts create mode 100644 bot/modules/quest/quest.types.ts create mode 100644 bot/modules/trivia/trivia.types.ts create mode 100644 bot/modules/user/user.types.ts diff --git a/bot/commands/admin/prune.ts b/bot/commands/admin/prune.ts index bf09e4f..68e2522 100644 --- a/bot/commands/admin/prune.ts +++ b/bot/commands/admin/prune.ts @@ -11,6 +11,7 @@ import { getCancelledEmbed } from "@/modules/moderation/prune.view"; import { withCommandErrorHandling } from "@lib/commandUtils"; +import { PRUNE_CUSTOM_IDS } from "@modules/moderation/prune.types"; export const prune = createCommand({ data: new SlashCommandBuilder() @@ -83,7 +84,7 @@ export const prune = createCommand({ time: 30000 }); - if (confirmation.customId === "cancel_prune") { + if (confirmation.customId === PRUNE_CUSTOM_IDS.CANCEL) { await confirmation.update({ embeds: [getCancelledEmbed()], components: [] diff --git a/bot/commands/quest/quests.ts b/bot/commands/quest/quests.ts index f5e0b0a..f8b8c4a 100644 --- a/bot/commands/quest/quests.ts +++ b/bot/commands/quest/quests.ts @@ -2,11 +2,12 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, MessageFlags } from "discord.js"; import { questService } from "@shared/modules/quest/quest.service"; import { createSuccessEmbed } from "@lib/embeds"; -import { - getQuestListComponents, - getAvailableQuestsComponents, - getQuestActionRows +import { + getQuestListComponents, + getAvailableQuestsComponents, + getQuestActionRows } from "@/modules/quest/quest.view"; +import { QUEST_CUSTOM_IDS } from "@modules/quest/quest.types"; export const quests = createCommand({ data: new SlashCommandBuilder() @@ -56,19 +57,19 @@ export const quests = createCommand({ if (i.user.id !== interaction.user.id) return; try { - if (i.customId === "quest_view_active") { + if (i.customId === QUEST_CUSTOM_IDS.VIEW_ACTIVE) { await i.deferUpdate(); await updateView('active', 0); - } else if (i.customId === "quest_view_available") { + } else if (i.customId === QUEST_CUSTOM_IDS.VIEW_AVAILABLE) { await i.deferUpdate(); await updateView('available', 0); - } else if (i.customId === "quest_page_prev") { + } else if (i.customId === QUEST_CUSTOM_IDS.PAGE_PREV) { await i.deferUpdate(); await updateView(currentView, Math.max(0, currentPage - 1)); - } else if (i.customId === "quest_page_next") { + } else if (i.customId === QUEST_CUSTOM_IDS.PAGE_NEXT) { await i.deferUpdate(); await updateView(currentView, currentPage + 1); - } else if (i.customId.startsWith("quest_accept:")) { + } else if (i.customId.startsWith(QUEST_CUSTOM_IDS.ACCEPT_PREFIX)) { const questIdStr = i.customId.split(":")[1]; if (!questIdStr) return; const questId = parseInt(questIdStr); diff --git a/bot/lib/interaction.routes.ts b/bot/lib/interaction.routes.ts index 577cb7f..3639147 100644 --- a/bot/lib/interaction.routes.ts +++ b/bot/lib/interaction.routes.ts @@ -1,11 +1,14 @@ import { ButtonInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from "discord.js"; +import { TRADE_CUSTOM_IDS } from "@modules/trade/trade.types"; +import { SHOP_CUSTOM_IDS, LOOTDROP_CUSTOM_IDS } from "@modules/economy/economy.types"; +import { ITEM_WIZARD_CUSTOM_IDS } from "@modules/admin/item_wizard.types"; +import { TRIVIA_CUSTOM_IDS } from "@modules/trivia/trivia.types"; +import { ENROLLMENT_CUSTOM_IDS } from "@modules/user/user.types"; +import { FEEDBACK_CUSTOM_IDS } from "@modules/feedback/feedback.types"; // Union type for all component interactions type ComponentInteraction = ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction; -// Type for the handler function that modules export -type InteractionHandler = (interaction: ComponentInteraction) => Promise; - // Type for the dynamically imported module containing the handler interface InteractionModule { [key: string]: (...args: any[]) => Promise | any; @@ -21,45 +24,45 @@ interface InteractionRoute { export const interactionRoutes: InteractionRoute[] = [ // --- TRADE MODULE --- { - predicate: (i) => i.customId.startsWith("trade_") || i.customId === "amount", + predicate: (i) => i.customId.startsWith(TRADE_CUSTOM_IDS.PREFIX) || i.customId === TRADE_CUSTOM_IDS.MONEY_AMOUNT_FIELD, handler: () => import("@/modules/trade/trade.interaction"), method: 'handleTradeInteraction' }, // --- ECONOMY MODULE --- { - predicate: (i) => i.isButton() && i.customId.startsWith("shop_buy_"), + predicate: (i) => i.isButton() && i.customId.startsWith(SHOP_CUSTOM_IDS.BUY_PREFIX), handler: () => import("@/modules/economy/shop.interaction"), method: 'handleShopInteraction' }, { - predicate: (i) => i.isButton() && i.customId.startsWith("lootdrop_"), + predicate: (i) => i.isButton() && i.customId.startsWith(LOOTDROP_CUSTOM_IDS.PREFIX), handler: () => import("@/modules/economy/lootdrop.interaction"), method: 'handleLootdropInteraction' }, { - predicate: (i) => i.isButton() && i.customId.startsWith("trivia_"), + predicate: (i) => i.isButton() && i.customId.startsWith(TRIVIA_CUSTOM_IDS.PREFIX), handler: () => import("@/modules/trivia/trivia.interaction"), method: 'handleTriviaInteraction' }, // --- ADMIN MODULE --- { - predicate: (i) => i.customId.startsWith("createitem_"), + predicate: (i) => i.customId.startsWith(ITEM_WIZARD_CUSTOM_IDS.PREFIX), handler: () => import("@/modules/admin/item_wizard"), method: 'handleItemWizardInteraction' }, // --- USER MODULE --- { - predicate: (i) => i.isButton() && i.customId === "enrollment", + predicate: (i) => i.isButton() && i.customId === ENROLLMENT_CUSTOM_IDS.ENROLL, handler: () => import("@/modules/user/enrollment.interaction"), method: 'handleEnrollmentInteraction' }, // --- FEEDBACK MODULE --- { - predicate: (i) => i.customId.startsWith("feedback_"), + predicate: (i) => i.customId.startsWith(FEEDBACK_CUSTOM_IDS.PREFIX), handler: () => import("@/modules/feedback/feedback.interaction"), method: 'handleFeedbackInteraction' } diff --git a/bot/modules/admin/item_wizard.ts b/bot/modules/admin/item_wizard.ts index 864e618..543fab3 100644 --- a/bot/modules/admin/item_wizard.ts +++ b/bot/modules/admin/item_wizard.ts @@ -3,7 +3,7 @@ import { items } from "@db/schema"; import { DrizzleClient } from "@shared/db/DrizzleClient"; import type { ItemUsageData, ItemEffect } from "@shared/lib/types"; import { getItemWizardEmbed, getItemTypeSelection, getEffectTypeSelection, getDetailsModal, getEconomyModal, getVisualsModal, getEffectConfigModal } from "./item_wizard.view"; -import type { DraftItem } from "./item_wizard.types"; +import { ITEM_WIZARD_CUSTOM_IDS, type DraftItem } from "./item_wizard.types"; import { ItemType, EffectType } from "@shared/lib/constants"; // --- Types --- @@ -41,13 +41,13 @@ export const renderWizard = (userId: string, isDraft = true) => { export const handleItemWizardInteraction = async (interaction: Interaction) => { // Only handle createitem interactions if (!interaction.isButton() && !interaction.isStringSelectMenu() && !interaction.isModalSubmit()) return; - if (!interaction.customId.startsWith("createitem_")) return; + if (!interaction.customId.startsWith(ITEM_WIZARD_CUSTOM_IDS.PREFIX)) return; const userId = interaction.user.id; let draft = draftSession.get(userId); // Special case for Cancel - doesn't need draft checks usually, but we want to clear it - if (interaction.customId === "createitem_cancel") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.CANCEL) { draftSession.delete(userId); if (interaction.isMessageComponent()) { await interaction.update({ content: "❌ Item creation cancelled.", embeds: [], components: [] }); @@ -59,7 +59,7 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { if (!draft) { if (interaction.isMessageComponent()) { // Create one implicitly to prevent crashes, or warn user - if (interaction.customId === "createitem_start") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.START) { // Allow start } else { await interaction.reply({ content: "⚠️ Session expired. Please run `/createitem` again.", ephemeral: true }); @@ -81,7 +81,7 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { // --- Routing --- // 1. Details Modal - if (interaction.customId === "createitem_details") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.DETAILS) { if (!interaction.isButton()) return; const modal = getDetailsModal(draft); await interaction.showModal(modal); @@ -89,7 +89,7 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { } // 2. Economy Modal - if (interaction.customId === "createitem_economy") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.ECONOMY) { if (!interaction.isButton()) return; const modal = getEconomyModal(draft); await interaction.showModal(modal); @@ -97,7 +97,7 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { } // 3. Visuals Modal - if (interaction.customId === "createitem_visuals") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.VISUALS) { if (!interaction.isButton()) return; const modal = getVisualsModal(draft); await interaction.showModal(modal); @@ -105,14 +105,14 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { } // 4. Type Toggle (Start Select Menu) - if (interaction.customId === "createitem_type_toggle") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.TYPE_TOGGLE) { if (!interaction.isButton()) return; const { components } = getItemTypeSelection(); await interaction.update({ components }); // Temporary view return; } - if (interaction.customId === "createitem_select_type") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.SELECT_TYPE) { if (!interaction.isStringSelectMenu()) return; const selected = interaction.values[0]; if (selected) { @@ -125,14 +125,14 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { } // 5. Add Effect Flow - if (interaction.customId === "createitem_addeffect_start") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.ADD_EFFECT_START) { if (!interaction.isButton()) return; const { components } = getEffectTypeSelection(); await interaction.update({ components }); return; } - if (interaction.customId === "createitem_select_effect_type") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.SELECT_EFFECT_TYPE) { if (!interaction.isStringSelectMenu()) return; const effectType = interaction.values[0]; if (!effectType) return; @@ -149,7 +149,7 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { } // Toggle Consume - if (interaction.customId === "createitem_toggle_consume") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.TOGGLE_CONSUME) { if (!interaction.isButton()) return; draft.usageData.consume = !draft.usageData.consume; const payload = renderWizard(userId); @@ -159,43 +159,43 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { // 6. Handle Modal Submits if (interaction.isModalSubmit()) { - if (interaction.customId === "createitem_modal_details") { - draft.name = interaction.fields.getTextInputValue("name"); - draft.description = interaction.fields.getTextInputValue("desc"); - draft.rarity = interaction.fields.getTextInputValue("rarity"); + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.MODAL_DETAILS) { + draft.name = interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_NAME); + draft.description = interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_DESC); + draft.rarity = interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_RARITY); } - else if (interaction.customId === "createitem_modal_economy") { - const price = parseInt(interaction.fields.getTextInputValue("price")); + else if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.MODAL_ECONOMY) { + const price = parseInt(interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_PRICE)); draft.price = isNaN(price) || price === 0 ? null : price; } - else if (interaction.customId === "createitem_modal_visuals") { - draft.iconUrl = interaction.fields.getTextInputValue("icon"); - draft.imageUrl = interaction.fields.getTextInputValue("image"); + else if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.MODAL_VISUALS) { + draft.iconUrl = interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_ICON); + draft.imageUrl = interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_IMAGE); } - else if (interaction.customId === "createitem_modal_effect") { + else if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.MODAL_EFFECT) { const type = draft.pendingEffectType; if (type) { let effect: ItemEffect | null = null; if (type === EffectType.ADD_XP || type === EffectType.ADD_BALANCE) { - const amount = parseInt(interaction.fields.getTextInputValue("amount")); + const amount = parseInt(interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_AMOUNT)); if (!isNaN(amount)) effect = { type: type as any, amount }; } else if (type === EffectType.REPLY_MESSAGE) { - effect = { type: EffectType.REPLY_MESSAGE, message: interaction.fields.getTextInputValue("message") }; + effect = { type: EffectType.REPLY_MESSAGE, message: interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_MESSAGE) }; } else if (type === EffectType.XP_BOOST) { - const multiplier = parseFloat(interaction.fields.getTextInputValue("multiplier")); - const duration = parseInt(interaction.fields.getTextInputValue("duration")); + const multiplier = parseFloat(interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_MULTIPLIER)); + const duration = parseInt(interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_DURATION)); if (!isNaN(multiplier) && !isNaN(duration)) effect = { type: EffectType.XP_BOOST, multiplier, durationSeconds: duration }; } else if (type === EffectType.TEMP_ROLE) { - const roleId = interaction.fields.getTextInputValue("role_id"); - const duration = parseInt(interaction.fields.getTextInputValue("duration")); + const roleId = interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_ROLE_ID); + const duration = parseInt(interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_DURATION)); if (roleId && !isNaN(duration)) effect = { type: EffectType.TEMP_ROLE, roleId: roleId, durationSeconds: duration }; } else if (type === EffectType.COLOR_ROLE) { - const roleId = interaction.fields.getTextInputValue("role_id"); + const roleId = interaction.fields.getTextInputValue(ITEM_WIZARD_CUSTOM_IDS.FIELD_ROLE_ID); if (roleId) effect = { type: EffectType.COLOR_ROLE, roleId: roleId }; } @@ -214,7 +214,7 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => { } // 7. Save - if (interaction.customId === "createitem_save") { + if (interaction.customId === ITEM_WIZARD_CUSTOM_IDS.SAVE) { if (!interaction.isButton()) return; await interaction.deferUpdate(); // Prepare to save diff --git a/bot/modules/admin/item_wizard.types.ts b/bot/modules/admin/item_wizard.types.ts index 5f08dcd..2f11ec7 100644 --- a/bot/modules/admin/item_wizard.types.ts +++ b/bot/modules/admin/item_wizard.types.ts @@ -1,5 +1,36 @@ import type { ItemUsageData } from "@shared/lib/types"; +export const ITEM_WIZARD_CUSTOM_IDS = { + PREFIX: "createitem_", + START: "createitem_start", + DETAILS: "createitem_details", + ECONOMY: "createitem_economy", + VISUALS: "createitem_visuals", + TYPE_TOGGLE: "createitem_type_toggle", + SELECT_TYPE: "createitem_select_type", + ADD_EFFECT_START: "createitem_addeffect_start", + SELECT_EFFECT_TYPE: "createitem_select_effect_type", + TOGGLE_CONSUME: "createitem_toggle_consume", + SAVE: "createitem_save", + CANCEL: "createitem_cancel", + MODAL_DETAILS: "createitem_modal_details", + MODAL_ECONOMY: "createitem_modal_economy", + MODAL_VISUALS: "createitem_modal_visuals", + MODAL_EFFECT: "createitem_modal_effect", + // Modal field IDs + FIELD_NAME: "name", + FIELD_DESC: "desc", + FIELD_RARITY: "rarity", + FIELD_PRICE: "price", + FIELD_ICON: "icon", + FIELD_IMAGE: "image", + FIELD_AMOUNT: "amount", + FIELD_MESSAGE: "message", + FIELD_MULTIPLIER: "multiplier", + FIELD_DURATION: "duration", + FIELD_ROLE_ID: "role_id", +} as const; + export interface DraftItem { name: string; description: string; diff --git a/bot/modules/admin/item_wizard.view.ts b/bot/modules/admin/item_wizard.view.ts index 268858c..03e4717 100644 --- a/bot/modules/admin/item_wizard.view.ts +++ b/bot/modules/admin/item_wizard.view.ts @@ -9,7 +9,7 @@ import { type MessageActionRowComponentBuilder } from "discord.js"; import { createBaseEmbed } from "@lib/embeds"; -import type { DraftItem } from "./item_wizard.types"; +import { ITEM_WIZARD_CUSTOM_IDS, type DraftItem } from "./item_wizard.types"; import { ItemType } from "@shared/lib/constants"; const getItemTypeOptions = () => [ @@ -51,18 +51,18 @@ export const getItemWizardEmbed = (draft: DraftItem) => { // Components const row1 = new ActionRowBuilder() .addComponents( - new ButtonBuilder().setCustomId("createitem_details").setLabel("Edit Details").setStyle(ButtonStyle.Secondary).setEmoji("πŸ“"), - new ButtonBuilder().setCustomId("createitem_economy").setLabel("Edit Economy").setStyle(ButtonStyle.Secondary).setEmoji("πŸ’°"), - new ButtonBuilder().setCustomId("createitem_visuals").setLabel("Edit Visuals").setStyle(ButtonStyle.Secondary).setEmoji("πŸ–ΌοΈ"), - new ButtonBuilder().setCustomId("createitem_type_toggle").setLabel("Change Type").setStyle(ButtonStyle.Secondary).setEmoji("πŸ”„"), + new ButtonBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.DETAILS).setLabel("Edit Details").setStyle(ButtonStyle.Secondary).setEmoji("πŸ“"), + new ButtonBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.ECONOMY).setLabel("Edit Economy").setStyle(ButtonStyle.Secondary).setEmoji("πŸ’°"), + new ButtonBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.VISUALS).setLabel("Edit Visuals").setStyle(ButtonStyle.Secondary).setEmoji("πŸ–ΌοΈ"), + new ButtonBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.TYPE_TOGGLE).setLabel("Change Type").setStyle(ButtonStyle.Secondary).setEmoji("πŸ”„"), ); const row2 = new ActionRowBuilder() .addComponents( - new ButtonBuilder().setCustomId("createitem_addeffect_start").setLabel("Add Effect").setStyle(ButtonStyle.Primary).setEmoji("✨"), - new ButtonBuilder().setCustomId("createitem_toggle_consume").setLabel(`Consume: ${draft.usageData.consume ? "ON" : "OFF"}`).setStyle(ButtonStyle.Secondary).setEmoji("πŸ”„"), - new ButtonBuilder().setCustomId("createitem_save").setLabel("Save Item").setStyle(ButtonStyle.Success).setEmoji("πŸ’Ύ"), - new ButtonBuilder().setCustomId("createitem_cancel").setLabel("Cancel").setStyle(ButtonStyle.Danger).setEmoji("βœ–οΈ") + new ButtonBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.ADD_EFFECT_START).setLabel("Add Effect").setStyle(ButtonStyle.Primary).setEmoji("✨"), + new ButtonBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.TOGGLE_CONSUME).setLabel(`Consume: ${draft.usageData.consume ? "ON" : "OFF"}`).setStyle(ButtonStyle.Secondary).setEmoji("πŸ”„"), + new ButtonBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.SAVE).setLabel("Save Item").setStyle(ButtonStyle.Success).setEmoji("πŸ’Ύ"), + new ButtonBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.CANCEL).setLabel("Cancel").setStyle(ButtonStyle.Danger).setEmoji("βœ–οΈ") ); return { embeds: [embed], components: [row1, row2] }; @@ -70,65 +70,65 @@ export const getItemWizardEmbed = (draft: DraftItem) => { export const getItemTypeSelection = () => { const row = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder().setCustomId("createitem_select_type").setPlaceholder("Select Item Type").addOptions(getItemTypeOptions()) + new StringSelectMenuBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.SELECT_TYPE).setPlaceholder("Select Item Type").addOptions(getItemTypeOptions()) ); return { components: [row] }; }; export const getEffectTypeSelection = () => { const row = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder().setCustomId("createitem_select_effect_type").setPlaceholder("Select Effect Type").addOptions(getEffectTypeOptions()) + new StringSelectMenuBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.SELECT_EFFECT_TYPE).setPlaceholder("Select Effect Type").addOptions(getEffectTypeOptions()) ); return { components: [row] }; }; export const getDetailsModal = (current: DraftItem) => { - const modal = new ModalBuilder().setCustomId("createitem_modal_details").setTitle("Edit Details"); + const modal = new ModalBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.MODAL_DETAILS).setTitle("Edit Details"); modal.addComponents( - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("name").setLabel("Name").setValue(current.name).setStyle(TextInputStyle.Short).setRequired(true)), - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("desc").setLabel("Description").setValue(current.description).setStyle(TextInputStyle.Paragraph).setRequired(false)), - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("rarity").setLabel("Rarity").setValue(current.rarity).setStyle(TextInputStyle.Short).setPlaceholder("C, R, SR, SSR").setRequired(true)) + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_NAME).setLabel("Name").setValue(current.name).setStyle(TextInputStyle.Short).setRequired(true)), + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_DESC).setLabel("Description").setValue(current.description).setStyle(TextInputStyle.Paragraph).setRequired(false)), + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_RARITY).setLabel("Rarity").setValue(current.rarity).setStyle(TextInputStyle.Short).setPlaceholder("C, R, SR, SSR").setRequired(true)) ); return modal; }; export const getEconomyModal = (current: DraftItem) => { - const modal = new ModalBuilder().setCustomId("createitem_modal_economy").setTitle("Edit Economy"); + const modal = new ModalBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.MODAL_ECONOMY).setTitle("Edit Economy"); modal.addComponents( - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("price").setLabel("Price (0 for not for sale)").setValue(current.price?.toString() || "0").setStyle(TextInputStyle.Short).setRequired(true)) + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_PRICE).setLabel("Price (0 for not for sale)").setValue(current.price?.toString() || "0").setStyle(TextInputStyle.Short).setRequired(true)) ); return modal; }; export const getVisualsModal = (current: DraftItem) => { - const modal = new ModalBuilder().setCustomId("createitem_modal_visuals").setTitle("Edit Visuals"); + const modal = new ModalBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.MODAL_VISUALS).setTitle("Edit Visuals"); modal.addComponents( - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("icon").setLabel("Icon URL (Emoji or Link)").setValue(current.iconUrl).setStyle(TextInputStyle.Short).setRequired(false)), - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("image").setLabel("Image URL").setValue(current.imageUrl).setStyle(TextInputStyle.Short).setRequired(false)) + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_ICON).setLabel("Icon URL (Emoji or Link)").setValue(current.iconUrl).setStyle(TextInputStyle.Short).setRequired(false)), + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_IMAGE).setLabel("Image URL").setValue(current.imageUrl).setStyle(TextInputStyle.Short).setRequired(false)) ); return modal; }; export const getEffectConfigModal = (effectType: string) => { - let modal = new ModalBuilder().setCustomId("createitem_modal_effect").setTitle(`Config ${effectType}`); + let modal = new ModalBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.MODAL_EFFECT).setTitle(`Config ${effectType}`); if (effectType === "ADD_XP" || effectType === "ADD_BALANCE") { - modal.addComponents(new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("amount").setLabel("Amount").setStyle(TextInputStyle.Short).setRequired(true).setPlaceholder("100"))); + modal.addComponents(new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_AMOUNT).setLabel("Amount").setStyle(TextInputStyle.Short).setRequired(true).setPlaceholder("100"))); } else if (effectType === "REPLY_MESSAGE") { - modal.addComponents(new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("message").setLabel("Message").setStyle(TextInputStyle.Paragraph).setRequired(true))); + modal.addComponents(new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_MESSAGE).setLabel("Message").setStyle(TextInputStyle.Paragraph).setRequired(true))); } else if (effectType === "XP_BOOST") { modal.addComponents( - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("multiplier").setLabel("Multiplier (e.g. 1.5)").setStyle(TextInputStyle.Short).setRequired(true)), - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("duration").setLabel("Duration (Seconds)").setStyle(TextInputStyle.Short).setRequired(true).setValue("3600")) + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_MULTIPLIER).setLabel("Multiplier (e.g. 1.5)").setStyle(TextInputStyle.Short).setRequired(true)), + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_DURATION).setLabel("Duration (Seconds)").setStyle(TextInputStyle.Short).setRequired(true).setValue("3600")) ); } else if (effectType === "TEMP_ROLE") { modal.addComponents( - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("role_id").setLabel("Role ID").setStyle(TextInputStyle.Short).setRequired(true)), - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("duration").setLabel("Duration (Seconds)").setStyle(TextInputStyle.Short).setRequired(true).setValue("3600")) + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_ROLE_ID).setLabel("Role ID").setStyle(TextInputStyle.Short).setRequired(true)), + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_DURATION).setLabel("Duration (Seconds)").setStyle(TextInputStyle.Short).setRequired(true).setValue("3600")) ); } else if (effectType === "COLOR_ROLE") { modal.addComponents( - new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId("role_id").setLabel("Role ID").setStyle(TextInputStyle.Short).setRequired(true)) + new ActionRowBuilder().addComponents(new TextInputBuilder().setCustomId(ITEM_WIZARD_CUSTOM_IDS.FIELD_ROLE_ID).setLabel("Role ID").setStyle(TextInputStyle.Short).setRequired(true)) ); } return modal; diff --git a/bot/modules/economy/economy.types.ts b/bot/modules/economy/economy.types.ts new file mode 100644 index 0000000..dee8be8 --- /dev/null +++ b/bot/modules/economy/economy.types.ts @@ -0,0 +1,10 @@ +export const LOOTDROP_CUSTOM_IDS = { + PREFIX: "lootdrop_", + CLAIM: "lootdrop_claim", + CLAIM_DISABLED: "lootdrop_claim_disabled", +} as const; + +export const SHOP_CUSTOM_IDS = { + BUY_PREFIX: "shop_buy_", + BUY: (itemId: number) => `shop_buy_${itemId}`, +} as const; diff --git a/bot/modules/economy/lootdrop.interaction.ts b/bot/modules/economy/lootdrop.interaction.ts index e2a64fb..f0a3725 100644 --- a/bot/modules/economy/lootdrop.interaction.ts +++ b/bot/modules/economy/lootdrop.interaction.ts @@ -3,9 +3,10 @@ import { lootdropService } from "@shared/modules/economy/lootdrop.service"; import { UserError } from "@shared/lib/errors"; import { getLootdropClaimedMessage } from "./lootdrop.view"; import { terminalService } from "@modules/system/terminal.service"; +import { LOOTDROP_CUSTOM_IDS } from "./economy.types"; export async function handleLootdropInteraction(interaction: ButtonInteraction) { - if (interaction.customId === "lootdrop_claim") { + if (interaction.customId === LOOTDROP_CUSTOM_IDS.CLAIM) { await interaction.deferReply({ ephemeral: true }); const result = await lootdropService.tryClaim(interaction.message.id, interaction.user.id, interaction.user.username); diff --git a/bot/modules/economy/lootdrop.view.ts b/bot/modules/economy/lootdrop.view.ts index 300a902..c8cc8c0 100644 --- a/bot/modules/economy/lootdrop.view.ts +++ b/bot/modules/economy/lootdrop.view.ts @@ -1,12 +1,13 @@ import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; import { generateLootdropCard, generateClaimedLootdropCard } from "@/graphics/lootdrop"; +import { LOOTDROP_CUSTOM_IDS } from "./economy.types"; export async function getLootdropMessage(reward: number, currency: string) { const cardBuffer = await generateLootdropCard(reward, currency); const attachment = new AttachmentBuilder(cardBuffer, { name: "lootdrop.png" }); const claimButton = new ButtonBuilder() - .setCustomId("lootdrop_claim") + .setCustomId(LOOTDROP_CUSTOM_IDS.CLAIM) .setLabel("CLAIM REWARD") .setStyle(ButtonStyle.Secondary) // Changed to Secondary to fit the darker theme better? Or keep Success? Let's try Secondary with custom emoji .setEmoji("🌠"); @@ -28,7 +29,7 @@ export async function getLootdropClaimedMessage(userId: string, username: string const newRow = new ActionRowBuilder() .addComponents( new ButtonBuilder() - .setCustomId("lootdrop_claim_disabled") + .setCustomId(LOOTDROP_CUSTOM_IDS.CLAIM_DISABLED) .setLabel("CLAIMED") .setStyle(ButtonStyle.Secondary) .setEmoji("βœ…") diff --git a/bot/modules/economy/shop.interaction.ts b/bot/modules/economy/shop.interaction.ts index 0b397ae..26e8356 100644 --- a/bot/modules/economy/shop.interaction.ts +++ b/bot/modules/economy/shop.interaction.ts @@ -2,13 +2,14 @@ import { ButtonInteraction, MessageFlags } from "discord.js"; import { inventoryService } from "@shared/modules/inventory/inventory.service"; import { userService } from "@shared/modules/user/user.service"; import { UserError } from "@shared/lib/errors"; +import { SHOP_CUSTOM_IDS } from "./economy.types"; export async function handleShopInteraction(interaction: ButtonInteraction) { - if (!interaction.customId.startsWith("shop_buy_")) return; + if (!interaction.customId.startsWith(SHOP_CUSTOM_IDS.BUY_PREFIX)) return; await interaction.deferReply({ flags: MessageFlags.Ephemeral }); - const itemId = parseInt(interaction.customId.replace("shop_buy_", "")); + const itemId = parseInt(interaction.customId.replace(SHOP_CUSTOM_IDS.BUY_PREFIX, "")); if (isNaN(itemId)) { throw new UserError("Invalid Item ID."); } diff --git a/bot/modules/economy/shop.view.ts b/bot/modules/economy/shop.view.ts index 2b55749..943dcb1 100644 --- a/bot/modules/economy/shop.view.ts +++ b/bot/modules/economy/shop.view.ts @@ -19,6 +19,7 @@ import { existsSync } from "fs"; import { LootType, EffectType } from "@shared/lib/constants"; import type { LootTableItem } from "@shared/lib/types"; import { getRarityConfig, defaultName, stripQuery } from "@shared/lib/rarity"; +import { SHOP_CUSTOM_IDS } from "./economy.types"; export function getShopListingMessage( item: { @@ -100,7 +101,7 @@ export function getShopListingMessage( // Create buy button (used in either main or loot container) const buyButton = new ButtonBuilder() - .setCustomId(`shop_buy_${item.id}`) + .setCustomId(SHOP_CUSTOM_IDS.BUY(item.id)) .setLabel(`Purchase for ${item.price} πŸͺ™`) .setStyle(ButtonStyle.Success) .setEmoji("πŸ›’"); diff --git a/bot/modules/feedback/feedback.interaction.ts b/bot/modules/feedback/feedback.interaction.ts index a511a23..f137a4c 100644 --- a/bot/modules/feedback/feedback.interaction.ts +++ b/bot/modules/feedback/feedback.interaction.ts @@ -8,7 +8,7 @@ import { UserError } from "@shared/lib/errors"; export const handleFeedbackInteraction = async (interaction: Interaction) => { // Handle select menu for choosing feedback type - if (interaction.isStringSelectMenu() && interaction.customId === "feedback_select_type") { + if (interaction.isStringSelectMenu() && interaction.customId === FEEDBACK_CUSTOM_IDS.SELECT_TYPE) { const feedbackType = interaction.values[0] as FeedbackType; if (!feedbackType) { diff --git a/bot/modules/feedback/feedback.types.ts b/bot/modules/feedback/feedback.types.ts index 31beec3..444950d 100644 --- a/bot/modules/feedback/feedback.types.ts +++ b/bot/modules/feedback/feedback.types.ts @@ -16,8 +16,10 @@ export const FEEDBACK_TYPE_LABELS: Record = { }; export const FEEDBACK_CUSTOM_IDS = { + PREFIX: "feedback_", + SELECT_TYPE: "feedback_select_type", MODAL: "feedback_modal", TYPE_FIELD: "feedback_type", TITLE_FIELD: "feedback_title", - DESCRIPTION_FIELD: "feedback_description" + DESCRIPTION_FIELD: "feedback_description", } as const; diff --git a/bot/modules/feedback/feedback.view.ts b/bot/modules/feedback/feedback.view.ts index 6d40fef..201852f 100644 --- a/bot/modules/feedback/feedback.view.ts +++ b/bot/modules/feedback/feedback.view.ts @@ -14,7 +14,7 @@ import { FEEDBACK_TYPE_LABELS, FEEDBACK_CUSTOM_IDS, type FeedbackData, type Feed export function getFeedbackTypeMenu() { const select = new StringSelectMenuBuilder() - .setCustomId("feedback_select_type") + .setCustomId(FEEDBACK_CUSTOM_IDS.SELECT_TYPE) .setPlaceholder("Choose feedback type") .addOptions([ { diff --git a/bot/modules/inventory/inventory.interaction.ts b/bot/modules/inventory/inventory.interaction.ts index 459e03d..2755fa8 100644 --- a/bot/modules/inventory/inventory.interaction.ts +++ b/bot/modules/inventory/inventory.interaction.ts @@ -3,6 +3,7 @@ import { inventoryService } from "@shared/modules/inventory/inventory.service"; import { getLootboxResultMessage } from "./inventory.view"; import type { ItemUsageData } from "@shared/lib/types"; import { getGuildConfig } from "@shared/lib/config"; +import { INVENTORY_CUSTOM_IDS } from "./inventory.types"; export interface InventoryState { ownerId: string; @@ -25,7 +26,7 @@ export function parseInventoryCustomId(customId: string): { action: string; view * Checks if a custom ID belongs to the inventory system. */ export function isInventoryInteraction(customId: string): boolean { - return customId.startsWith("inv_"); + return customId.startsWith(INVENTORY_CUSTOM_IDS.PREFIX); } /** diff --git a/bot/modules/inventory/inventory.types.ts b/bot/modules/inventory/inventory.types.ts new file mode 100644 index 0000000..4f744f3 --- /dev/null +++ b/bot/modules/inventory/inventory.types.ts @@ -0,0 +1,13 @@ +export const INVENTORY_CUSTOM_IDS = { + PREFIX: "inv_", + SELECT: (viewerId: string) => `inv_select_${viewerId}`, + PREV: (viewerId: string) => `inv_prev_${viewerId}`, + PAGE: (viewerId: string) => `inv_page_${viewerId}`, + NEXT: (viewerId: string) => `inv_next_${viewerId}`, + BACK: (viewerId: string) => `inv_back_${viewerId}`, + USE: (viewerId: string) => `inv_use_${viewerId}`, + DISCARD: (viewerId: string) => `inv_discard_${viewerId}`, + DISCARD_CONFIRM: (viewerId: string) => `inv_discard_confirm_${viewerId}`, + DISCARD_CANCEL: (viewerId: string) => `inv_discard_cancel_${viewerId}`, + USE_BACK: (viewerId: string) => `inv_use_back_${viewerId}`, +} as const; diff --git a/bot/modules/inventory/inventory.view.ts b/bot/modules/inventory/inventory.view.ts index 7158f9c..5bf7c3d 100644 --- a/bot/modules/inventory/inventory.view.ts +++ b/bot/modules/inventory/inventory.view.ts @@ -22,6 +22,7 @@ import { ItemType } from "@shared/lib/constants"; import type { ItemUsageData } from "@shared/lib/types"; import { join } from "path"; import { existsSync } from "fs"; +import { INVENTORY_CUSTOM_IDS } from "./inventory.types"; export const ITEMS_PER_PAGE = 5; @@ -101,7 +102,7 @@ export function getInventoryListMessage( // Select menu with current page items const selectMenu = new StringSelectMenuBuilder() - .setCustomId(`inv_select_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.SELECT(viewerId)) .setPlaceholder("Select an item for details"); for (const entry of pageItems) { @@ -121,17 +122,17 @@ export function getInventoryListMessage( // Pagination buttons const navRow = new ActionRowBuilder().addComponents( new ButtonBuilder() - .setCustomId(`inv_prev_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.PREV(viewerId)) .setLabel("β—€ Previous") .setStyle(ButtonStyle.Secondary) .setDisabled(safePage <= 0), new ButtonBuilder() - .setCustomId(`inv_page_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.PAGE(viewerId)) .setLabel(`Page ${safePage + 1}/${totalPages}`) .setStyle(ButtonStyle.Secondary) .setDisabled(true), new ButtonBuilder() - .setCustomId(`inv_next_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.NEXT(viewerId)) .setLabel("Next β–Ά") .setStyle(ButtonStyle.Secondary) .setDisabled(safePage >= totalPages - 1), @@ -225,7 +226,7 @@ export function getItemDetailMessage( const actionRow = new ActionRowBuilder().addComponents( new ButtonBuilder() - .setCustomId(`inv_back_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.BACK(viewerId)) .setLabel("β—€ Back") .setStyle(ButtonStyle.Primary) ); @@ -233,7 +234,7 @@ export function getItemDetailMessage( if (isUsable) { actionRow.addComponents( new ButtonBuilder() - .setCustomId(`inv_use_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.USE(viewerId)) .setLabel("πŸ§ͺ Use") .setStyle(ButtonStyle.Success) ); @@ -242,7 +243,7 @@ export function getItemDetailMessage( if (isOwner) { actionRow.addComponents( new ButtonBuilder() - .setCustomId(`inv_discard_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.DISCARD(viewerId)) .setLabel("πŸ—‘ Discard") .setStyle(ButtonStyle.Danger) ); @@ -271,11 +272,11 @@ export function getDiscardConfirmMessage(entry: InventoryEntry, viewerId: string .addActionRowComponents( new ActionRowBuilder().addComponents( new ButtonBuilder() - .setCustomId(`inv_discard_confirm_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.DISCARD_CONFIRM(viewerId)) .setLabel("Confirm") .setStyle(ButtonStyle.Danger), new ButtonBuilder() - .setCustomId(`inv_discard_cancel_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.DISCARD_CANCEL(viewerId)) .setLabel("Cancel") .setStyle(ButtonStyle.Secondary) ) @@ -296,7 +297,7 @@ export function getDiscardConfirmMessage(entry: InventoryEntry, viewerId: string export function appendUseBackButton(message: any, viewerId: string): any { const backRow = new ActionRowBuilder().addComponents( new ButtonBuilder() - .setCustomId(`inv_use_back_${viewerId}`) + .setCustomId(INVENTORY_CUSTOM_IDS.USE_BACK(viewerId)) .setLabel("β—€ Back to Inventory") .setStyle(ButtonStyle.Primary) ); diff --git a/bot/modules/moderation/prune.types.ts b/bot/modules/moderation/prune.types.ts index 7b1e051..1fca5be 100644 --- a/bot/modules/moderation/prune.types.ts +++ b/bot/modules/moderation/prune.types.ts @@ -1,3 +1,8 @@ +export const PRUNE_CUSTOM_IDS = { + CONFIRM: "confirm_prune", + CANCEL: "cancel_prune", +} as const; + export interface PruneOptions { amount?: number; userId?: string; diff --git a/bot/modules/moderation/prune.view.ts b/bot/modules/moderation/prune.view.ts index ba93fb2..1fe982b 100644 --- a/bot/modules/moderation/prune.view.ts +++ b/bot/modules/moderation/prune.view.ts @@ -1,5 +1,5 @@ import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, Colors } from "discord.js"; -import type { PruneResult, PruneProgress } from "./prune.types"; +import { PRUNE_CUSTOM_IDS, type PruneResult, type PruneProgress } from "./prune.types"; /** * Creates a confirmation message for prune operations @@ -25,12 +25,12 @@ export function getConfirmationMessage( .setTimestamp(); const confirmButton = new ButtonBuilder() - .setCustomId("confirm_prune") + .setCustomId(PRUNE_CUSTOM_IDS.CONFIRM) .setLabel("Confirm") .setStyle(ButtonStyle.Danger); const cancelButton = new ButtonBuilder() - .setCustomId("cancel_prune") + .setCustomId(PRUNE_CUSTOM_IDS.CANCEL) .setLabel("Cancel") .setStyle(ButtonStyle.Secondary); diff --git a/bot/modules/quest/quest.types.ts b/bot/modules/quest/quest.types.ts new file mode 100644 index 0000000..a1a2e16 --- /dev/null +++ b/bot/modules/quest/quest.types.ts @@ -0,0 +1,8 @@ +export const QUEST_CUSTOM_IDS = { + ACCEPT_PREFIX: "quest_accept:", + ACCEPT: (questId: number) => `quest_accept:${questId}`, + PAGE_PREV: "quest_page_prev", + PAGE_NEXT: "quest_page_next", + VIEW_ACTIVE: "quest_view_active", + VIEW_AVAILABLE: "quest_view_available", +} as const; diff --git a/bot/modules/quest/quest.view.ts b/bot/modules/quest/quest.view.ts index c53f0de..1d4e3b7 100644 --- a/bot/modules/quest/quest.view.ts +++ b/bot/modules/quest/quest.view.ts @@ -8,6 +8,7 @@ import { SeparatorSpacingSize, MessageFlags } from "discord.js"; +import { QUEST_CUSTOM_IDS } from "./quest.types"; /** * Quest entry with quest details and progress @@ -169,7 +170,7 @@ export function getAvailableQuestsComponents(availableQuests: AvailableQuest[], container.addActionRowComponents( new ActionRowBuilder().addComponents( new ButtonBuilder() - .setCustomId(`quest_accept:${quest.id}`) + .setCustomId(QUEST_CUSTOM_IDS.ACCEPT(quest.id)) .setLabel("Accept Quest") .setStyle(ButtonStyle.Success) .setEmoji("βœ…") @@ -191,12 +192,12 @@ export function getQuestActionRows(viewType: 'active' | 'available', totalItems: if (totalPages > 1) { rows.push(new ActionRowBuilder().addComponents( new ButtonBuilder() - .setCustomId("quest_page_prev") + .setCustomId(QUEST_CUSTOM_IDS.PAGE_PREV) .setLabel("β—€ Prev") .setStyle(ButtonStyle.Secondary) .setDisabled(page <= 0), new ButtonBuilder() - .setCustomId("quest_page_next") + .setCustomId(QUEST_CUSTOM_IDS.PAGE_NEXT) .setLabel("Next β–Ά") .setStyle(ButtonStyle.Secondary) .setDisabled(page >= totalPages - 1) @@ -206,12 +207,12 @@ export function getQuestActionRows(viewType: 'active' | 'available', totalItems: // Tab navigation row rows.push(new ActionRowBuilder().addComponents( new ButtonBuilder() - .setCustomId("quest_view_active") + .setCustomId(QUEST_CUSTOM_IDS.VIEW_ACTIVE) .setLabel("πŸ“œ Active") .setStyle(viewType === 'active' ? ButtonStyle.Primary : ButtonStyle.Secondary) .setDisabled(viewType === 'active'), new ButtonBuilder() - .setCustomId("quest_view_available") + .setCustomId(QUEST_CUSTOM_IDS.VIEW_AVAILABLE) .setLabel("πŸ—ΊοΈ Available") .setStyle(viewType === 'available' ? ButtonStyle.Primary : ButtonStyle.Secondary) .setDisabled(viewType === 'available') diff --git a/bot/modules/trade/trade.interaction.ts b/bot/modules/trade/trade.interaction.ts index acdc537..b69476e 100644 --- a/bot/modules/trade/trade.interaction.ts +++ b/bot/modules/trade/trade.interaction.ts @@ -12,6 +12,7 @@ import { inventoryService } from "@shared/modules/inventory/inventory.service"; import { createErrorEmbed, createWarningEmbed, createSuccessEmbed, createInfoEmbed } from "@lib/embeds"; import { UserError } from "@shared/lib/errors"; import { getTradeDashboard, getTradeMoneyModal, getItemSelectMenu, getTradeCompletedEmbed } from "./trade.view"; +import { TRADE_CUSTOM_IDS } from "./trade.types"; @@ -23,25 +24,25 @@ export async function handleTradeInteraction(interaction: Interaction) { if (!threadId) return; - if (customId === 'trade_cancel') { + if (customId === TRADE_CUSTOM_IDS.CANCEL) { await handleCancel(interaction, threadId); - } else if (customId === 'trade_lock') { + } else if (customId === TRADE_CUSTOM_IDS.LOCK) { await handleLock(interaction, threadId); - } else if (customId === 'trade_confirm') { + } else if (customId === TRADE_CUSTOM_IDS.CONFIRM) { // Confirm logic is handled implicitly by both locking or explicitly if needed. // For now, locking both triggers execution, so no separate confirm handler is actively used // unless we re-introduce a specific button. keeping basic handler stub if needed. - } else if (customId === 'trade_add_money') { + } else if (customId === TRADE_CUSTOM_IDS.ADD_MONEY) { await handleAddMoneyClick(interaction); - } else if (customId === 'trade_money_modal') { + } else if (customId === TRADE_CUSTOM_IDS.MONEY_MODAL) { await handleMoneySubmit(interaction as ModalSubmitInteraction, threadId); - } else if (customId === 'trade_add_item') { + } else if (customId === TRADE_CUSTOM_IDS.ADD_ITEM) { await handleAddItemClick(interaction as ButtonInteraction, threadId); - } else if (customId === 'trade_select_item') { + } else if (customId === TRADE_CUSTOM_IDS.SELECT_ITEM) { await handleItemSelect(interaction as StringSelectMenuInteraction, threadId); - } else if (customId === 'trade_remove_item') { + } else if (customId === TRADE_CUSTOM_IDS.REMOVE_ITEM) { await handleRemoveItemClick(interaction as ButtonInteraction, threadId); - } else if (customId === 'trade_remove_item_select') { + } else if (customId === TRADE_CUSTOM_IDS.REMOVE_ITEM_SELECT) { await handleRemoveItemSelect(interaction as StringSelectMenuInteraction, threadId); } } @@ -82,7 +83,7 @@ async function handleAddMoneyClick(interaction: Interaction) { } async function handleMoneySubmit(interaction: ModalSubmitInteraction, threadId: string) { - const amountStr = interaction.fields.getTextInputValue('amount'); + const amountStr = interaction.fields.getTextInputValue(TRADE_CUSTOM_IDS.MONEY_AMOUNT_FIELD); const amount = BigInt(amountStr); if (amount < 0n) throw new UserError("Amount must be positive"); @@ -107,7 +108,7 @@ async function handleAddItemClick(interaction: ButtonInteraction, threadId: stri description: `Rarity: ${entry.item.rarity} ` })); - const { components } = getItemSelectMenu(options, 'trade_select_item', 'Select an item to add'); + const { components } = getItemSelectMenu(options, TRADE_CUSTOM_IDS.SELECT_ITEM, 'Select an item to add'); await interaction.reply({ content: "Select an item to add:", components, ephemeral: true }); } @@ -142,7 +143,7 @@ async function handleRemoveItemClick(interaction: ButtonInteraction, threadId: s value: i.id.toString(), })); - const { components } = getItemSelectMenu(options, 'trade_remove_item_select', 'Select an item to remove'); + const { components } = getItemSelectMenu(options, TRADE_CUSTOM_IDS.REMOVE_ITEM_SELECT, 'Select an item to remove'); await interaction.reply({ content: "Select an item to remove:", components, ephemeral: true }); } diff --git a/bot/modules/trade/trade.types.ts b/bot/modules/trade/trade.types.ts index 5246710..b9a3ebe 100644 --- a/bot/modules/trade/trade.types.ts +++ b/bot/modules/trade/trade.types.ts @@ -1,3 +1,16 @@ +export const TRADE_CUSTOM_IDS = { + PREFIX: "trade_", + ADD_ITEM: "trade_add_item", + ADD_MONEY: "trade_add_money", + REMOVE_ITEM: "trade_remove_item", + LOCK: "trade_lock", + CANCEL: "trade_cancel", + CONFIRM: "trade_confirm", + MONEY_MODAL: "trade_money_modal", + MONEY_AMOUNT_FIELD: "amount", + SELECT_ITEM: "trade_select_item", + REMOVE_ITEM_SELECT: "trade_remove_item_select", +} as const; export interface TradeItem { id: number; diff --git a/bot/modules/trade/trade.view.ts b/bot/modules/trade/trade.view.ts index e4b2279..12e886e 100644 --- a/bot/modules/trade/trade.view.ts +++ b/bot/modules/trade/trade.view.ts @@ -1,6 +1,6 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ModalBuilder, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import { createBaseEmbed } from "@lib/embeds"; -import type { TradeSession, TradeParticipant } from "./trade.types"; +import { TRADE_CUSTOM_IDS, type TradeSession, type TradeParticipant } from "./trade.types"; const EMBED_COLOR = 0xFFD700; // Gold @@ -34,11 +34,11 @@ export function getTradeDashboard(session: TradeSession) { const row = new ActionRowBuilder() .addComponents( - new ButtonBuilder().setCustomId('trade_add_item').setLabel('Add Item').setStyle(ButtonStyle.Secondary), - new ButtonBuilder().setCustomId('trade_add_money').setLabel('Add Money').setStyle(ButtonStyle.Success), - new ButtonBuilder().setCustomId('trade_remove_item').setLabel('Remove Item').setStyle(ButtonStyle.Secondary), - new ButtonBuilder().setCustomId('trade_lock').setLabel('Lock / Unlock').setStyle(ButtonStyle.Primary), - new ButtonBuilder().setCustomId('trade_cancel').setLabel('Cancel').setStyle(ButtonStyle.Danger), + new ButtonBuilder().setCustomId(TRADE_CUSTOM_IDS.ADD_ITEM).setLabel('Add Item').setStyle(ButtonStyle.Secondary), + new ButtonBuilder().setCustomId(TRADE_CUSTOM_IDS.ADD_MONEY).setLabel('Add Money').setStyle(ButtonStyle.Success), + new ButtonBuilder().setCustomId(TRADE_CUSTOM_IDS.REMOVE_ITEM).setLabel('Remove Item').setStyle(ButtonStyle.Secondary), + new ButtonBuilder().setCustomId(TRADE_CUSTOM_IDS.LOCK).setLabel('Lock / Unlock').setStyle(ButtonStyle.Primary), + new ButtonBuilder().setCustomId(TRADE_CUSTOM_IDS.CANCEL).setLabel('Cancel').setStyle(ButtonStyle.Danger), ); return { embeds: [embed], components: [row] }; @@ -57,11 +57,11 @@ export function getTradeCompletedEmbed(session: TradeSession) { export function getTradeMoneyModal() { const modal = new ModalBuilder() - .setCustomId('trade_money_modal') + .setCustomId(TRADE_CUSTOM_IDS.MONEY_MODAL) .setTitle('Add Money'); const input = new TextInputBuilder() - .setCustomId('amount') + .setCustomId(TRADE_CUSTOM_IDS.MONEY_AMOUNT_FIELD) .setLabel("Amount to trade") .setStyle(TextInputStyle.Short) .setPlaceholder("100") diff --git a/bot/modules/trivia/trivia.types.ts b/bot/modules/trivia/trivia.types.ts new file mode 100644 index 0000000..6c77188 --- /dev/null +++ b/bot/modules/trivia/trivia.types.ts @@ -0,0 +1,7 @@ +export const TRIVIA_CUSTOM_IDS = { + PREFIX: "trivia_", + ANSWER: (sessionId: string, index: number) => `trivia_answer_${sessionId}_${index}`, + GIVE_UP: (sessionId: string) => `trivia_giveup_${sessionId}`, + RESULT: (index: number) => `trivia_result_${index}`, + TIMEOUT: (index: number) => `trivia_timeout_${index}`, +} as const; diff --git a/bot/modules/trivia/trivia.view.ts b/bot/modules/trivia/trivia.view.ts index 602d0ff..9d0af7d 100644 --- a/bot/modules/trivia/trivia.view.ts +++ b/bot/modules/trivia/trivia.view.ts @@ -1,5 +1,6 @@ import { MessageFlags } from "discord.js"; import type { TriviaSession, TriviaResult } from "@shared/modules/trivia/trivia.service"; +import { TRIVIA_CUSTOM_IDS } from "./trivia.types"; /** * Get color based on difficulty level @@ -97,14 +98,14 @@ export function getTriviaQuestionView(session: TriviaSession, username: string): components: [ { type: 2, // Button - custom_id: `trivia_answer_${sessionId}_${trueIndex}`, + custom_id: TRIVIA_CUSTOM_IDS.ANSWER(sessionId, trueIndex), label: 'True', style: 3, // Success emoji: { name: 'βœ…' } }, { type: 2, // Button - custom_id: `trivia_answer_${sessionId}_${falseIndex}`, + custom_id: TRIVIA_CUSTOM_IDS.ANSWER(sessionId, falseIndex), label: 'False', style: 4, // Danger emoji: { name: '❌' } @@ -129,7 +130,7 @@ export function getTriviaQuestionView(session: TriviaSession, username: string): buttonRow.components.push({ type: 2, // Button - custom_id: `trivia_answer_${sessionId}_${i}`, + custom_id: TRIVIA_CUSTOM_IDS.ANSWER(sessionId, i), label: `${label}: ${answer.substring(0, 30)}${answer.length > 30 ? '...' : ''}`, style: 2, // Secondary emoji: { name: emoji } @@ -145,7 +146,7 @@ export function getTriviaQuestionView(session: TriviaSession, username: string): components: [ { type: 2, // Button - custom_id: `trivia_giveup_${sessionId}`, + custom_id: TRIVIA_CUSTOM_IDS.GIVE_UP(sessionId), label: 'Give Up', style: 4, // Danger emoji: { name: '🏳️' } @@ -245,7 +246,7 @@ export function getTriviaResultView( buttonRow.components.push({ type: 2, // Button - custom_id: `trivia_result_${i}`, + custom_id: TRIVIA_CUSTOM_IDS.RESULT(i), label: `${label}: ${answer.substring(0, 30)}${answer.length > 30 ? '...' : ''}`, style: isCorrect ? 3 : wasUserAnswer ? 4 : 2, // Success : Danger : Secondary emoji: { name: isCorrect ? 'βœ…' : wasUserAnswer ? '❌' : emoji }, @@ -318,7 +319,7 @@ export function getTriviaTimeoutView( buttonRow.components.push({ type: 2, // Button - custom_id: `trivia_timeout_${i}`, + custom_id: TRIVIA_CUSTOM_IDS.TIMEOUT(i), label: `${label}: ${answer.substring(0, 30)}${answer.length > 30 ? '...' : ''}`, style: isCorrect ? 3 : 2, // Success : Secondary emoji: { name: isCorrect ? 'βœ…' : emoji }, diff --git a/bot/modules/user/user.types.ts b/bot/modules/user/user.types.ts new file mode 100644 index 0000000..f2f5857 --- /dev/null +++ b/bot/modules/user/user.types.ts @@ -0,0 +1,3 @@ +export const ENROLLMENT_CUSTOM_IDS = { + ENROLL: "enrollment", +} as const;