import { Interaction, ChatInputCommandInteraction, ButtonInteraction, TextChannel, StringSelectMenuInteraction } from "discord.js"; import { handleButton, handleScoreSubmitButton } from "@handlers/buttons"; import { handleTgCommand } from "@commands/tg"; import { handleTgConfigCommand } from "@commands/tgConfig"; import { handleBorrowAcceptButton } from "@subcommands/char/accept"; import { handleBorrowDeclineButton } from "@subcommands/char/decline"; import { handleConflictButton } from "@systems/conflict"; import { handleImpersonateButton } from "@subcommands/impersonate"; import { handleAutocomplete } from "@handlers/autocomplete"; import { setActiveCharacter, getCharacterByName, getCharacters } from "@systems/characters"; import { Ephemeral } from "@registry/ephemeral-registry"; import { getImpersonation } from "@systems/impersonate"; import { format } from "@format"; import { modals } from "@handlers/modals"; import { Character } from "@systems/character"; import { handleTgAdminCommand } from "@commands/tgAdmin"; import fs from "fs"; import path from "path"; async function handleSwitchAfterReclaim(btn: ButtonInteraction): Promise { const prefix = btn.customId.startsWith("companion_switch:") ? "companion_switch:" : "switch_after_reclaim:"; const withoutPrefix = btn.customId.slice(prefix.length); const firstColon = withoutPrefix.indexOf(":"); const userKey = withoutPrefix.slice(0, firstColon); const rest = withoutPrefix.slice(firstColon + 1); const lastColon = rest.lastIndexOf(":"); const charName = rest.slice(0, lastColon); const prevVoteType = (rest.slice(lastColon + 1) || "yes") as "yes" | "no"; const impersonating = getImpersonation(btn.user.id); const voteId = impersonating ? `impersonated:${impersonating}` : btn.user.id; const chars = JSON.parse( fs.readFileSync(path.join(__dirname, "../../data/characters.json"), "utf8") ); await btn.deferUpdate() // Resolve char without switching let resolvedChar: any = null; let borrowedFrom: string | null = null; const ownEntry = chars[userKey]?.characters?.find((c: any) => c.name === charName); if (ownEntry) { resolvedChar = ownEntry; } else { for (const [ownerKey, data] of Object.entries(chars) as [string, any][]) { const char = data.characters?.find( (c: any) => c.name === charName && c.sharedWith?.includes(userKey) ); if (char) { resolvedChar = char; borrowedFrom = ownerKey; break; } } } if (!resolvedChar) { await btn.followUp({ content: `❌ Could not switch to **${charName}**.`, ephemeral: true }); return; } // Delegate to shared switch logic const result = await Character.performSwitch(userKey, resolvedChar, borrowedFrom, btn, prevVoteType); if (result.replyData) { const { content, components } = result.replyData; console.log(`[switchAfterReclaim] replyData, isCompanion=${btn.customId.startsWith("companion_switch:")}`); if (btn.customId.startsWith("companion_switch:")) { await Ephemeral.update(voteId, "companion", content, components, { final: false }); } else { await btn.followUp(result.replyData); } return; } if (result.success && result.message) { const companionExists = !!Ephemeral.get(voteId, "companion"); console.log(`[switchAfterReclaim] success, companionExists=${companionExists} voteId=${voteId}`); await Ephemeral.update(voteId, "companion", result.message, []); // Ephemeral.delete(voteId, "companion"); // clean up after final switch if (!companionExists) { await btn.followUp({ content: result.message, ephemeral: true }); } } } export async function handleInteraction(interaction: Interaction): Promise { try { if (interaction.isAutocomplete()) { await handleAutocomplete(interaction); return; } if (interaction.isButton()) { const btn = interaction as ButtonInteraction; console.log("[interactions] interaction btnId:", btn.customId); if (btn.customId.startsWith("conflict_")) { console.log("[interactions] routing to conflict handler:", btn.customId); return await handleConflictButton(btn); } if (btn.customId.startsWith("impersonate_")) { return await handleImpersonateButton(btn); } if (btn.customId.startsWith("switch_after_reclaim:")) { return await handleSwitchAfterReclaim(btn); } if (btn.customId.startsWith("borrow_accept:")) { const [, ownerKey, requesterKey] = btn.customId.split(":"); return await handleBorrowAcceptButton(btn, ownerKey, requesterKey); } if (btn.customId.startsWith("borrow_decline:")) { const [, ownerKey, requesterKey] = btn.customId.split(":"); return await handleBorrowDeclineButton(btn, ownerKey, requesterKey); } if (btn.customId === "tg_score_submit") { return await handleScoreSubmitButton(btn); } if (btn.customId.startsWith("companion_switch:")) { return await handleSwitchAfterReclaim(btn); } return await handleButton(btn); } if (interaction.isModalSubmit()) { return await modals.handleModal(interaction); } if (interaction.isStringSelectMenu()) { const sel = interaction as StringSelectMenuInteraction; if (sel.customId.startsWith("score_slot_select:")) { const userKey = sel.customId.split(":")[1]; const slot = parseInt(sel.values[0], 10); await sel.showModal(modals.buildScoreModal(userKey, slot)); return; } } if (interaction.isChatInputCommand()) { const cmd = interaction as ChatInputCommandInteraction; if (cmd.commandName === "tg") await handleTgCommand(cmd); if (cmd.commandName === "tg-config") await handleTgConfigCommand(cmd); if (cmd.commandName === "tg-admin") await handleTgAdminCommand(cmd); } } catch (err) { console.error("Interaction error:", err); try { const msg = { content: "❌ An error occurred.", ephemeral: true }; if ((interaction as any).replied || (interaction as any).deferred) { await (interaction as any).followUp(msg); } else { await (interaction as any).reply(msg); } } catch {} } }