Zuletzt aktiv 3 weeks ago

interactions.ts Originalformat
1import { Interaction, ChatInputCommandInteraction, ButtonInteraction, TextChannel, StringSelectMenuInteraction } from "discord.js";
2import { handleButton, handleScoreSubmitButton } from "@handlers/buttons";
3import { handleTgCommand } from "@commands/tg";
4import { handleTgConfigCommand } from "@commands/tgConfig";
5import { handleBorrowAcceptButton } from "@subcommands/char/accept";
6import { handleBorrowDeclineButton } from "@subcommands/char/decline";
7import { handleConflictButton } from "@systems/conflict";
8import { handleImpersonateButton } from "@subcommands/impersonate";
9import { handleAutocomplete } from "@handlers/autocomplete";
10import { setActiveCharacter, getCharacterByName, getCharacters } from "@systems/characters";
11import { Ephemeral } from "@registry/ephemeral-registry";
12import { getImpersonation } from "@systems/impersonate";
13import { format } from "@format";
14import { modals } from "@handlers/modals";
15import { Character } from "@systems/character";
16import { handleTgAdminCommand } from "@commands/tgAdmin";
17import fs from "fs";
18import path from "path";
19
20async function handleSwitchAfterReclaim(btn: ButtonInteraction): Promise<void> {
21 const prefix = btn.customId.startsWith("companion_switch:") ? "companion_switch:" : "switch_after_reclaim:";
22 const withoutPrefix = btn.customId.slice(prefix.length);
23 const firstColon = withoutPrefix.indexOf(":");
24 const userKey = withoutPrefix.slice(0, firstColon);
25 const rest = withoutPrefix.slice(firstColon + 1);
26 const lastColon = rest.lastIndexOf(":");
27 const charName = rest.slice(0, lastColon);
28 const prevVoteType = (rest.slice(lastColon + 1) || "yes") as "yes" | "no";
29
30 const impersonating = getImpersonation(btn.user.id);
31 const voteId = impersonating ? `impersonated:${impersonating}` : btn.user.id;
32
33 const chars = JSON.parse(
34 fs.readFileSync(path.join(__dirname, "../../data/characters.json"), "utf8")
35 );
36
37 await btn.deferUpdate()
38
39 // Resolve char without switching
40 let resolvedChar: any = null;
41 let borrowedFrom: string | null = null;
42
43 const ownEntry = chars[userKey]?.characters?.find((c: any) => c.name === charName);
44 if (ownEntry) {
45 resolvedChar = ownEntry;
46 } else {
47 for (const [ownerKey, data] of Object.entries(chars) as [string, any][]) {
48 const char = data.characters?.find(
49 (c: any) => c.name === charName && c.sharedWith?.includes(userKey)
50 );
51 if (char) {
52 resolvedChar = char;
53 borrowedFrom = ownerKey;
54 break;
55 }
56 }
57 }
58
59 if (!resolvedChar) {
60 await btn.followUp({ content: `❌ Could not switch to **${charName}**.`, ephemeral: true });
61 return;
62 }
63
64 // Delegate to shared switch logic
65 const result = await Character.performSwitch(userKey, resolvedChar, borrowedFrom, btn, prevVoteType);
66
67 if (result.replyData) {
68 const { content, components } = result.replyData;
69 console.log(`[switchAfterReclaim] replyData, isCompanion=${btn.customId.startsWith("companion_switch:")}`);
70 if (btn.customId.startsWith("companion_switch:")) {
71 await Ephemeral.update(voteId, "companion", content, components, { final: false });
72 } else {
73 await btn.followUp(result.replyData);
74 }
75 return;
76 }
77
78 if (result.success && result.message) {
79 const companionExists = !!Ephemeral.get(voteId, "companion");
80 console.log(`[switchAfterReclaim] success, companionExists=${companionExists} voteId=${voteId}`);
81 await Ephemeral.update(voteId, "companion", result.message, []);
82 // Ephemeral.delete(voteId, "companion"); // clean up after final switch
83 if (!companionExists) {
84 await btn.followUp({ content: result.message, ephemeral: true });
85 }
86 }
87}
88
89export async function handleInteraction(interaction: Interaction): Promise<void> {
90 try {
91 if (interaction.isAutocomplete()) {
92 await handleAutocomplete(interaction);
93 return;
94 }
95
96 if (interaction.isButton()) {
97 const btn = interaction as ButtonInteraction;
98
99 console.log("[interactions] interaction btnId:", btn.customId);
100 if (btn.customId.startsWith("conflict_")) {
101 console.log("[interactions] routing to conflict handler:", btn.customId);
102 return await handleConflictButton(btn);
103 }
104
105 if (btn.customId.startsWith("impersonate_")) {
106 return await handleImpersonateButton(btn);
107 }
108
109 if (btn.customId.startsWith("switch_after_reclaim:")) {
110 return await handleSwitchAfterReclaim(btn);
111 }
112
113 if (btn.customId.startsWith("borrow_accept:")) {
114 const [, ownerKey, requesterKey] = btn.customId.split(":");
115 return await handleBorrowAcceptButton(btn, ownerKey, requesterKey);
116 }
117
118 if (btn.customId.startsWith("borrow_decline:")) {
119 const [, ownerKey, requesterKey] = btn.customId.split(":");
120 return await handleBorrowDeclineButton(btn, ownerKey, requesterKey);
121 }
122
123 if (btn.customId === "tg_score_submit") {
124 return await handleScoreSubmitButton(btn);
125 }
126
127 if (btn.customId.startsWith("companion_switch:")) {
128 return await handleSwitchAfterReclaim(btn);
129 }
130
131 return await handleButton(btn);
132 }
133
134 if (interaction.isModalSubmit()) {
135 return await modals.handleModal(interaction);
136 }
137
138 if (interaction.isStringSelectMenu()) {
139 const sel = interaction as StringSelectMenuInteraction;
140 if (sel.customId.startsWith("score_slot_select:")) {
141 const userKey = sel.customId.split(":")[1];
142 const slot = parseInt(sel.values[0], 10);
143 await sel.showModal(modals.buildScoreModal(userKey, slot));
144 return;
145 }
146 }
147
148 if (interaction.isChatInputCommand()) {
149 const cmd = interaction as ChatInputCommandInteraction;
150 if (cmd.commandName === "tg") await handleTgCommand(cmd);
151 if (cmd.commandName === "tg-config") await handleTgConfigCommand(cmd);
152 if (cmd.commandName === "tg-admin") await handleTgAdminCommand(cmd);
153 }
154 } catch (err) {
155 console.error("Interaction error:", err);
156 try {
157 const msg = { content: "❌ An error occurred.", ephemeral: true };
158 if ((interaction as any).replied || (interaction as any).deferred) {
159 await (interaction as any).followUp(msg);
160 } else {
161 await (interaction as any).reply(msg);
162 }
163 } catch {}
164 }
165}