┌─ TS ───────────────────────────────────────────────────────────────────────┐
│ // discord bot interaction endpoint │
│ │
│ import type { VercelRequest, VercelResponse } from "@vercel/node"; │
│ import { │
│ InteractionType, │
│ InteractionResponseType, │
│ type DiscordInteraction, │
│ } from "../src/discord/types.js"; │
│ import { │
│ verifyDiscordSignature, │
│ extractDiscordHeaders, │
│ } from "../src/discord/verify.js"; │
│ import { initializeCaches } from "../src/discord/indexes/cache.js"; │
│ import { handlePlayerCommand } from "../src/discord/commands/player.js"; │
│ import { handleLeaderboardCommand } from "../src/discord/commands/leaderboard.js"; │
│ import { autocompleteDungeon } from "../src/discord/autocomplete/dungeons.js"; │
│ import { autocompleteRealm } from "../src/discord/autocomplete/realms.js"; │
│ import { autocompleteRegionByPlayer } from "../src/discord/autocomplete/regions.js │
│ "; │
│ import { autocompletePlayer } from "../src/discord/autocomplete/players.js"; │
│ import { autocompleteClass } from "../src/discord/autocomplete/classes.js"; │
│ import { createGenericErrorEmbed } from "../src/discord/embeds/error.js"; │
│ │
│ // cache initialization flag │
│ let cacheInitialized = false; │
│ │
│ // refresh cooldown tracking (message_id -> timestamp) │
│ const refreshCooldowns = new Map<string, number>(); │
│ const REFRESH_COOLDOWN_MS = 30000; // 30 seconds │
│ │
│ /** │
│ * Main Discord interaction handler │
│ */ │
│ export default async function handler( │
│ req: VercelRequest, │
│ res: VercelResponse, │
│ ): Promise<void> { │
│ // only accept POST requests │
│ if (req.method !== "POST") { │
│ res.status(405).json({ error: "Method not allowed" }); │
│ return; │
│ } │
│ │
│ // verify Discord signature │
│ const publicKey = process.env.DISCORD_PUBLIC_KEY; │
│ if (!publicKey) { │
│ console.error("DISCORD_PUBLIC_KEY not configured"); │
│ res.status(500).json({ error: "Bot not configured" }); │
│ return; │
│ } │
│ │
│ const headers = extractDiscordHeaders(req.headers); │
│ if (!headers) { │
│ res.status(401).json({ error: "Missing Discord headers" }); │
│ return; │
│ } │
│ │
│ // get raw body for verification │
│ const rawBody = JSON.stringify(req.body); │
│ const isValid = await verifyDiscordSignature( │
│ headers.signature, │
│ headers.timestamp, │
│ rawBody, │
│ publicKey, │
│ ); │
│ │
│ if (!isValid) { │
│ console.error("Invalid Discord signature"); │
│ res.status(401).json({ error: "Invalid signature" }); │
│ return; │
│ } │
│ │
│ // initialize caches on first request (cold start) │
│ if (!cacheInitialized) { │
│ console.log("Cold start - initializing caches..."); │
│ await initializeCaches(); │
│ cacheInitialized = true; │
│ } │
│ │
│ const interaction = req.body as DiscordInteraction; │
│ │
│ try { │
│ // handle PING (Discord verification) │
│ if (interaction.type === InteractionType.PING) { │
│ res.json({ type: InteractionResponseType.PONG }); │
│ return; │
│ } │
│ │
│ // handle AUTOCOMPLETE │
│ if (interaction.type === InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE) { │
│ const choices = await handleAutocomplete(interaction); │
│ res.json({ │
│ type: InteractionResponseType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT, │
│ data: { choices }, │
│ }); │
│ return; │
│ } │
│ │
│ // handle COMMAND │
│ if (interaction.type === InteractionType.APPLICATION_COMMAND) { │
│ const response = await handleCommand(interaction); │
│ res.json({ │
│ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, │
│ data: response, │
│ }); │
│ return; │
│ } │
│ │
│ // handle BUTTON (pagination) │
│ if (interaction.type === InteractionType.MESSAGE_COMPONENT) { │
│ const response = await handleButton(interaction); │
│ res.json({ │
│ type: InteractionResponseType.UPDATE_MESSAGE, │
│ data: response, │
│ }); │
│ return; │
│ } │
│ │
│ // unknown interaction type │
│ console.warn("Unknown interaction type:", interaction.type); │
│ res.status(400).json({ error: "Unknown interaction type" }); │
│ } catch (error) { │
│ console.error("Error handling interaction:", error); │
│ res.json({ │
│ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, │
│ data: { │
│ embeds: [createGenericErrorEmbed()], │
│ }, │
│ }); │
│ } │
│ } │
│ │
│ /** │
│ * Routes autocomplete to appropriate handler │
│ */ │
│ async function handleAutocomplete(interaction: DiscordInteraction) { │
│ const commandName = interaction.data?.name; │
│ const options = interaction.data?.options || []; │
│ │
│ // find focused option │
│ const focusedOption = findFocusedOption(options); │
│ if (!focusedOption) { │
│ return []; │
│ } │
│ │
│ const query = String(focusedOption.value || ""); │
│ │
│ // route based on command and option name │
│ if (commandName === "leaderboard") { │
│ if (focusedOption.name === "dungeon") { │
│ return autocompleteDungeon(query); │
│ } │
│ if (focusedOption.name === "realm") { │
│ const region = getOptionValue(options[0]?.options || [], "region"); │
│ const regionStr = typeof region === "string" ? region : undefined; │
│ return autocompleteRealm( │
│ query, │
│ regionStr as "us" | "eu" | "kr" | "tw" | undefined, │
│ ); │
│ } │
│ if (focusedOption.name === "class") { │
│ return autocompleteClass(query); │
│ } │
│ } │
│ │
│ if (commandName === "player") { │
│ if (focusedOption.name === "name") { │
│ const region = getOptionValue(options, "region"); │
│ const realm = getOptionValue(options, "realm"); │
│ return autocompletePlayer( │
│ query, │
│ typeof region === "string" ? region : undefined, │
│ typeof realm === "string" ? realm : undefined, │
│ ); │
│ } │
│ if (focusedOption.name === "region") { │
│ const name = getOptionValue(options, "name"); │
│ console.log("[Discord Handler] All options:", JSON.stringify(options)); │
│ console.log( │
│ "[Discord Handler] Region autocomplete - name option value:", │
│ name, │
│ "type:", │
│ typeof name, │
│ ); │
│ console.log( │
│ "[Discord Handler] Focused option:", │
│ JSON.stringify(focusedOption), │
│ ); │
│ return autocompleteRegionByPlayer( │
│ query, │
│ typeof name === "string" ? name : undefined, │
│ ); │
│ } │
│ if (focusedOption.name === "realm") { │
│ const region = getOptionValue(options, "region"); │
│ const name = getOptionValue(options, "name"); │
│ const regionStr = typeof region === "string" ? region : undefined; │
│ const nameStr = typeof name === "string" ? name : undefined; │
│ return autocompleteRealm( │
│ query, │
│ regionStr as "us" | "eu" | "kr" | "tw" | undefined, │
│ nameStr, │
│ ); │
│ } │
│ } │
│ │
│ return []; │
│ } │
│ │
│ /** │
│ * Routes commands to appropriate handler │
│ */ │
│ async function handleCommand(interaction: DiscordInteraction) { │
│ const commandName = interaction.data?.name; │
│ │
│ if (commandName === "player") { │
│ return handlePlayerCommand(interaction); │
│ } │
│ │
│ if (commandName === "leaderboard") { │
│ return handleLeaderboardCommand(interaction); │
│ } │
│ │
│ return { │
│ content: "Unknown command", │
│ }; │
│ } │
│ │
│ /** │
│ * Handles button clicks │
│ */ │
│ async function handleButton(interaction: DiscordInteraction) { │
│ const customId = interaction.data?.custom_id || ""; │
│ │
│ // check cooldown for all refresh actions │
│ if (customId.startsWith("refresh_")) { │
│ const messageId = interaction.message?.id; │
│ if (messageId) { │
│ const lastRefresh = refreshCooldowns.get(messageId); │
│ const now = Date.now(); │
│ │
│ if (lastRefresh && now - lastRefresh < REFRESH_COOLDOWN_MS) { │
│ // silently ignore refresh attempts within cooldown │
│ // return current message unchanged │
│ return interaction.message || {}; │
│ } │
│ │
│ // update cooldown timestamp │
│ refreshCooldowns.set(messageId, now); │
│ │
│ // cleanup old entries (older than 5 minutes) │
│ for (const [id, timestamp] of refreshCooldowns.entries()) { │
│ if (now - timestamp > 300000) { │
│ refreshCooldowns.delete(id); │
│ } │
│ } │
│ } │
│ } │
│ │
│ // handle refresh player profile │
│ if (customId.startsWith("refresh_player:")) { │
│ const [, region, realm, name, season] = customId.split(":"); │
│ │
│ const { handlePlayerCommand } = await import( │
│ "../src/discord/commands/player.js" │
│ ); │
│ │
│ const options: any[] = [ │
│ { name: "name", type: 3, value: name }, // STRING │
│ { name: "region", type: 3, value: region }, // STRING │
│ { name: "realm", type: 3, value: realm }, // STRING │
│ ]; │
│ if (season) options.push({ name: "season", type: 3, value: season }); // STRIN │
│ G │
│ │
│ const fakeInteraction: DiscordInteraction = { │
│ ...interaction, │
│ data: { │
│ name: "player", │
│ options, │
│ }, │
│ }; │
│ │
│ const response = await handlePlayerCommand(fakeInteraction); │
│ return response; │
│ } │
│ │
│ // handle refresh dungeon leaderboard │
│ if (customId.startsWith("refresh_dungeon:")) { │
│ const [, dungeon, scope, region, realm, limit, season] = │
│ customId.split(":"); │
│ │
│ const { handleLeaderboardCommand } = await import( │
│ "../src/discord/commands/leaderboard.js" │
│ ); │
│ │
│ const options: any[] = [ │
│ { name: "dungeon", type: 3, value: dungeon }, // STRING │
│ { name: "scope", type: 3, value: scope }, // STRING │
│ ]; │
│ if (region) options.push({ name: "region", type: 3, value: region }); // STRIN │
│ G │
│ if (realm) options.push({ name: "realm", type: 3, value: realm }); // STRING │
│ if (limit) options.push({ name: "limit", type: 4, value: parseInt(limit) }); / │
│ / INTEGER │
│ if (season) options.push({ name: "season", type: 3, value: season }); // STRIN │
│ G │
│ │
│ const fakeInteraction: DiscordInteraction = { │
│ ...interaction, │
│ data: { │
│ name: "leaderboard", │
│ options: [{ name: "dungeon", type: 1, options }], // SUB_COMMAND │
│ }, │
│ }; │
│ │
│ const response = await handleLeaderboardCommand(fakeInteraction); │
│ return response; │
│ } │
│ │
│ // handle refresh player leaderboard │
│ if (customId.startsWith("refresh_players:")) { │
│ const [, scope, region, realm, className, limit, season] = │
│ customId.split(":"); │
│ │
│ const { handleLeaderboardCommand } = await import( │
│ "../src/discord/commands/leaderboard.js" │
│ ); │
│ │
│ const options: any[] = [{ name: "scope", type: 3, value: scope }]; // STRING │
│ if (region) options.push({ name: "region", type: 3, value: region }); // STRIN │
│ G │
│ if (realm) options.push({ name: "realm", type: 3, value: realm }); // STRING │
│ if (className) options.push({ name: "class", type: 3, value: className }); // │
│ STRING │
│ if (limit) options.push({ name: "limit", type: 4, value: parseInt(limit) }); / │
│ / INTEGER │
│ if (season) options.push({ name: "season", type: 3, value: season }); // STRIN │
│ G │
│ │
│ const fakeInteraction: DiscordInteraction = { │
│ ...interaction, │
│ data: { │
│ name: "leaderboard", │
│ options: [{ name: "players", type: 1, options }], // SUB_COMMAND │
│ }, │
│ }; │
│ │
│ const response = await handleLeaderboardCommand(fakeInteraction); │
│ return response; │
│ } │
│ │
│ return { │
│ content: "Unknown button", │
│ }; │
│ } │
│ │
│ // recursive type for command options │
│ interface CommandOptionTree { │
│ name: string; │
│ value?: unknown; │
│ focused?: boolean; │
│ options?: CommandOptionTree[]; │
│ } │
│ │
│ /** │
│ * Helper to find focused option in nested options │
│ */ │
│ function findFocusedOption( │
│ options: CommandOptionTree[], │
│ ): { name: string; value: unknown } | null { │
│ for (const option of options) { │
│ if (option.focused) { │
│ return { name: option.name, value: option.value }; │
│ } │
│ if (option.options) { │
│ const found = findFocusedOption(option.options); │
│ if (found) return found; │
│ } │
│ } │
│ return null; │
│ } │
│ │
│ /** │
│ * Helper to get option value by name │
│ */ │
│ function getOptionValue( │
│ options: Array<{ name: string; value?: string | number | boolean }>, │
│ name: string, │
│ ): string | number | boolean | undefined { │
│ return options.find((opt) => opt.name === name)?.value; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ TS ─────────────────────────────────┐
│ // discord bot interaction endpoint │
│ │
│ import type { VercelRequest, VercelResponse │
│ } from "@vercel/node"; │
│ import { │
│ InteractionType, │
│ InteractionResponseType, │
│ type DiscordInteraction, │
│ } from "../src/discord/types.js"; │
│ import { │
│ verifyDiscordSignature, │
│ extractDiscordHeaders, │
│ } from "../src/discord/verify.js"; │
│ import { initializeCaches } from "../src/dis │
│ cord/indexes/cache.js"; │
│ import { handlePlayerCommand } from "../src/ │
│ discord/commands/player.js"; │
│ import { handleLeaderboardCommand } from ".. │
│ /src/discord/commands/leaderboard.js"; │
│ import { autocompleteDungeon } from "../src/ │
│ discord/autocomplete/dungeons.js"; │
│ import { autocompleteRealm } from "../src/di │
│ scord/autocomplete/realms.js"; │
│ import { autocompleteRegionByPlayer } from " │
│ ../src/discord/autocomplete/regions.js"; │
│ import { autocompletePlayer } from "../src/d │
│ iscord/autocomplete/players.js"; │
│ import { autocompleteClass } from "../src/di │
│ scord/autocomplete/classes.js"; │
│ import { createGenericErrorEmbed } from "../ │
│ src/discord/embeds/error.js"; │
│ │
│ // cache initialization flag │
│ let cacheInitialized = false; │
│ │
│ // refresh cooldown tracking (message_id -> │
│ timestamp) │
│ const refreshCooldowns = new Map<string, num │
│ ber>(); │
│ const REFRESH_COOLDOWN_MS = 30000; // 30 sec │
│ onds │
│ │
│ /** │
│ * Main Discord interaction handler │
│ */ │
│ export default async function handler( │
│ req: VercelRequest, │
│ res: VercelResponse, │
│ ): Promise<void> { │
│ // only accept POST requests │
│ if (req.method !== "POST") { │
│ res.status(405).json({ error: "Method no │
│ t allowed" }); │
│ return; │
│ } │
│ │
│ // verify Discord signature │
│ const publicKey = process.env.DISCORD_PUBL │
│ IC_KEY; │
│ if (!publicKey) { │
│ console.error("DISCORD_PUBLIC_KEY not co │
│ nfigured"); │
│ res.status(500).json({ error: "Bot not c │
│ onfigured" }); │
│ return; │
│ } │
│ │
│ const headers = extractDiscordHeaders(req. │
│ headers); │
│ if (!headers) { │
│ res.status(401).json({ error: "Missing D │
│ iscord headers" }); │
│ return; │
│ } │
│ │
│ // get raw body for verification │
│ const rawBody = JSON.stringify(req.body); │
│ const isValid = await verifyDiscordSignatu │
│ re( │
│ headers.signature, │
│ headers.timestamp, │
│ rawBody, │
│ publicKey, │
│ ); │
│ │
│ if (!isValid) { │
│ console.error("Invalid Discord signature │
│ "); │
│ res.status(401).json({ error: "Invalid s │
│ ignature" }); │
│ return; │
│ } │
│ │
│ // initialize caches on first request (col │
│ d start) │
│ if (!cacheInitialized) { │
│ console.log("Cold start - initializing c │
│ aches..."); │
│ await initializeCaches(); │
│ cacheInitialized = true; │
│ } │
│ │
│ const interaction = req.body as DiscordInt │
│ eraction; │
│ │
│ try { │
│ // handle PING (Discord verification) │
│ if (interaction.type === InteractionType │
│ .PING) { │
│ res.json({ type: InteractionResponseTy │
│ pe.PONG }); │
│ return; │
│ } │
│ │
│ // handle AUTOCOMPLETE │
│ if (interaction.type === InteractionType │
│ .APPLICATION_COMMAND_AUTOCOMPLETE) { │
│ const choices = await handleAutocomple │
│ te(interaction); │
│ res.json({ │
│ type: InteractionResponseType.APPLIC │
│ ATION_COMMAND_AUTOCOMPLETE_RESULT, │
│ data: { choices }, │
│ }); │
│ return; │
│ } │
│ │
│ // handle COMMAND │
│ if (interaction.type === InteractionType │
│ .APPLICATION_COMMAND) { │
│ const response = await handleCommand(i │
│ nteraction); │
│ res.json({ │
│ type: InteractionResponseType.CHANNE │
│ L_MESSAGE_WITH_SOURCE, │
│ data: response, │
│ }); │
│ return; │
│ } │
│ │
│ // handle BUTTON (pagination) │
│ if (interaction.type === InteractionType │
│ .MESSAGE_COMPONENT) { │
│ const response = await handleButton(in │
│ teraction); │
│ res.json({ │
│ type: InteractionResponseType.UPDATE │
│ _MESSAGE, │
│ data: response, │
│ }); │
│ return; │
│ } │
│ │
│ // unknown interaction type │
│ console.warn("Unknown interaction type:" │
│ , interaction.type); │
│ res.status(400).json({ error: "Unknown i │
│ nteraction type" }); │
│ } catch (error) { │
│ console.error("Error handling interactio │
│ n:", error); │
│ res.json({ │
│ type: InteractionResponseType.CHANNEL_ │
│ MESSAGE_WITH_SOURCE, │
│ data: { │
│ embeds: [createGenericErrorEmbed()], │
│ }, │
│ }); │
│ } │
│ } │
│ │
│ /** │
│ * Routes autocomplete to appropriate handle │
│ r │
│ */ │
│ async function handleAutocomplete(interactio │
│ n: DiscordInteraction) { │
│ const commandName = interaction.data?.name │
│ ; │
│ const options = interaction.data?.options │
│ || []; │
│ │
│ // find focused option │
│ const focusedOption = findFocusedOption(op │
│ tions); │
│ if (!focusedOption) { │
│ return []; │
│ } │
│ │
│ const query = String(focusedOption.value | │
│ | ""); │
│ │
│ // route based on command and option name │
│ if (commandName === "leaderboard") { │
│ if (focusedOption.name === "dungeon") { │
│ return autocompleteDungeon(query); │
│ } │
│ if (focusedOption.name === "realm") { │
│ const region = getOptionValue(options[ │
│ 0]?.options || [], "region"); │
│ const regionStr = typeof region === "s │
│ tring" ? region : undefined; │
│ return autocompleteRealm( │
│ query, │
│ regionStr as "us" | "eu" | "kr" | "t │
│ w" | undefined, │
│ ); │
│ } │
│ if (focusedOption.name === "class") { │
│ return autocompleteClass(query); │
│ } │
│ } │
│ │
│ if (commandName === "player") { │
│ if (focusedOption.name === "name") { │
│ const region = getOptionValue(options, │
│ "region"); │
│ const realm = getOptionValue(options, │
│ "realm"); │
│ return autocompletePlayer( │
│ query, │
│ typeof region === "string" ? region │
│ : undefined, │
│ typeof realm === "string" ? realm : │
│ undefined, │
│ ); │
│ } │
│ if (focusedOption.name === "region") { │
│ const name = getOptionValue(options, " │
│ name"); │
│ console.log("[Discord Handler] All opt │
│ ions:", JSON.stringify(options)); │
│ console.log( │
│ "[Discord Handler] Region autocomple │
│ te - name option value:", │
│ name, │
│ "type:", │
│ typeof name, │
│ ); │
│ console.log( │
│ "[Discord Handler] Focused option:", │
│ JSON.stringify(focusedOption), │
│ ); │
│ return autocompleteRegionByPlayer( │
│ query, │
│ typeof name === "string" ? name : un │
│ defined, │
│ ); │
│ } │
│ if (focusedOption.name === "realm") { │
│ const region = getOptionValue(options, │
│ "region"); │
│ const name = getOptionValue(options, " │
│ name"); │
│ const regionStr = typeof region === "s │
│ tring" ? region : undefined; │
│ const nameStr = typeof name === "strin │
│ g" ? name : undefined; │
│ return autocompleteRealm( │
│ query, │
│ regionStr as "us" | "eu" | "kr" | "t │
│ w" | undefined, │
│ nameStr, │
│ ); │
│ } │
│ } │
│ │
│ return []; │
│ } │
│ │
│ /** │
│ * Routes commands to appropriate handler │
│ */ │
│ async function handleCommand(interaction: Di │
│ scordInteraction) { │
│ const commandName = interaction.data?.name │
│ ; │
│ │
│ if (commandName === "player") { │
│ return handlePlayerCommand(interaction); │
│ } │
│ │
│ if (commandName === "leaderboard") { │
│ return handleLeaderboardCommand(interact │
│ ion); │
│ } │
│ │
│ return { │
│ content: "Unknown command", │
│ }; │
│ } │
│ │
│ /** │
│ * Handles button clicks │
│ */ │
│ async function handleButton(interaction: Dis │
│ cordInteraction) { │
│ const customId = interaction.data?.custom_ │
│ id || ""; │
│ │
│ // check cooldown for all refresh actions │
│ if (customId.startsWith("refresh_")) { │
│ const messageId = interaction.message?.i │
│ d; │
│ if (messageId) { │
│ const lastRefresh = refreshCooldowns.g │
│ et(messageId); │
│ const now = Date.now(); │
│ │
│ if (lastRefresh && now - lastRefresh < │
│ REFRESH_COOLDOWN_MS) { │
│ // silently ignore refresh attempts │
│ within cooldown │
│ // return current message unchanged │
│ return interaction.message || {}; │
│ } │
│ │
│ // update cooldown timestamp │
│ refreshCooldowns.set(messageId, now); │
│ │
│ // cleanup old entries (older than 5 m │
│ inutes) │
│ for (const [id, timestamp] of refreshC │
│ ooldowns.entries()) { │
│ if (now - timestamp > 300000) { │
│ refreshCooldowns.delete(id); │
│ } │
│ } │
│ } │
│ } │
│ │
│ // handle refresh player profile │
│ if (customId.startsWith("refresh_player:") │
│ ) { │
│ const [, region, realm, name, season] = │
│ customId.split(":"); │
│ │
│ const { handlePlayerCommand } = await im │
│ port( │
│ "../src/discord/commands/player.js" │
│ ); │
│ │
│ const options: any[] = [ │
│ { name: "name", type: 3, value: name } │
│ , // STRING │
│ { name: "region", type: 3, value: regi │
│ on }, // STRING │
│ { name: "realm", type: 3, value: realm │
│ }, // STRING │
│ ]; │
│ if (season) options.push({ name: "season │
│ ", type: 3, value: season }); // STRING │
│ │
│ const fakeInteraction: DiscordInteractio │
│ n = { │
│ ...interaction, │
│ data: { │
│ name: "player", │
│ options, │
│ }, │
│ }; │
│ │
│ const response = await handlePlayerComma │
│ nd(fakeInteraction); │
│ return response; │
│ } │
│ │
│ // handle refresh dungeon leaderboard │
│ if (customId.startsWith("refresh_dungeon:" │
│ )) { │
│ const [, dungeon, scope, region, realm, │
│ limit, season] = │
│ customId.split(":"); │
│ │
│ const { handleLeaderboardCommand } = awa │
│ it import( │
│ "../src/discord/commands/leaderboard.j │
│ s" │
│ ); │
│ │
│ const options: any[] = [ │
│ { name: "dungeon", type: 3, value: dun │
│ geon }, // STRING │
│ { name: "scope", type: 3, value: scope │
│ }, // STRING │
│ ]; │
│ if (region) options.push({ name: "region │
│ ", type: 3, value: region }); // STRING │
│ if (realm) options.push({ name: "realm", │
│ type: 3, value: realm }); // STRING │
│ if (limit) options.push({ name: "limit", │
│ type: 4, value: parseInt(limit) }); // INTE │
│ GER │
│ if (season) options.push({ name: "season │
│ ", type: 3, value: season }); // STRING │
│ │
│ const fakeInteraction: DiscordInteractio │
│ n = { │
│ ...interaction, │
│ data: { │
│ name: "leaderboard", │
│ options: [{ name: "dungeon", type: 1 │
│ , options }], // SUB_COMMAND │
│ }, │
│ }; │
│ │
│ const response = await handleLeaderboard │
│ Command(fakeInteraction); │
│ return response; │
│ } │
│ │
│ // handle refresh player leaderboard │
│ if (customId.startsWith("refresh_players:" │
│ )) { │
│ const [, scope, region, realm, className │
│ , limit, season] = │
│ customId.split(":"); │
│ │
│ const { handleLeaderboardCommand } = awa │
│ it import( │
│ "../src/discord/commands/leaderboard.j │
│ s" │
│ ); │
│ │
│ const options: any[] = [{ name: "scope", │
│ type: 3, value: scope }]; // STRING │
│ if (region) options.push({ name: "region │
│ ", type: 3, value: region }); // STRING │
│ if (realm) options.push({ name: "realm", │
│ type: 3, value: realm }); // STRING │
│ if (className) options.push({ name: "cla │
│ ss", type: 3, value: className }); // STRING │
│ if (limit) options.push({ name: "limit", │
│ type: 4, value: parseInt(limit) }); // INTE │
│ GER │
│ if (season) options.push({ name: "season │
│ ", type: 3, value: season }); // STRING │
│ │
│ const fakeInteraction: DiscordInteractio │
│ n = { │
│ ...interaction, │
│ data: { │
│ name: "leaderboard", │
│ options: [{ name: "players", type: 1 │
│ , options }], // SUB_COMMAND │
│ }, │
│ }; │
│ │
│ const response = await handleLeaderboard │
│ Command(fakeInteraction); │
│ return response; │
│ } │
│ │
│ return { │
│ content: "Unknown button", │
│ }; │
│ } │
│ │
│ // recursive type for command options │
│ interface CommandOptionTree { │
│ name: string; │
│ value?: unknown; │
│ focused?: boolean; │
│ options?: CommandOptionTree[]; │
│ } │
│ │
│ /** │
│ * Helper to find focused option in nested o │
│ ptions │
│ */ │
│ function findFocusedOption( │
│ options: CommandOptionTree[], │
│ ): { name: string; value: unknown } | null { │
│ for (const option of options) { │
│ if (option.focused) { │
│ return { name: option.name, value: opt │
│ ion.value }; │
│ } │
│ if (option.options) { │
│ const found = findFocusedOption(option │
│ .options); │
│ if (found) return found; │
│ } │
│ } │
│ return null; │
│ } │
│ │
│ /** │
│ * Helper to get option value by name │
│ */ │
│ function getOptionValue( │
│ options: Array<{ name: string; value?: str │
│ ing | number | boolean }>, │
│ name: string, │
│ ): string | number | boolean | undefined { │
│ return options.find((opt) => opt.name === │
│ name)?.value; │
│ } │
└──────────────────────────────────────────────┘