OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 384 LINES
 
[ HISTORY ]  [ UP ]
 

// 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;
}

// 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;
}
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET