Остання активність 1 month ago

nuno ревизій цього gist 1 month ago. До ревизії

1 file changed, 181 insertions

interactions.ts(файл створено)

@@ -0,0 +1,181 @@
1 + import { Interaction, ChatInputCommandInteraction, ButtonInteraction, TextChannel, StringSelectMenuInteraction } from "discord.js";
2 + import { handleButton, handleScoreSubmitButton } from "@handlers/buttons";
3 + import { handleTgCommand } from "@commands/tg";
4 + import { handleTgConfigCommand } from "@commands/tgConfig";
5 + import { handleBorrowAcceptButton } from "@subcommands/char/accept";
6 + import { handleBorrowDeclineButton } from "@subcommands/char/decline";
7 + import { handleConflictButton } from "@systems/conflict";
8 + import { handleImpersonateButton } from "@subcommands/impersonate";
9 + import { handleAutocomplete } from "@handlers/autocomplete";
10 + import { setActiveCharacter, getCharacterByName, getCharacters } from "@systems/characters";
11 + import { setPersistentPreference, clearSessionBorrowForUser, getEffectiveCharacter } from "@systems/borrow";
12 + import { polls, updatePollMessage } from "@systems/poll";
13 + import { cfg } from "@systems/config";
14 + import { resolveMessage, nowFormatted } from "@systems/messages";
15 + import { format } from "@format";
16 + import { modals } from "@handlers/modals";
17 + import fs from "fs";
18 + import path from "path";
19 +
20 + async function handleSwitchAfterReclaim(btn: ButtonInteraction): Promise<void> {
21 + const parts = btn.customId.split(":");
22 + const userKey = parts[1];
23 + const charName = parts[2];
24 + const prevVoteType = (parts[3] ?? "yes") as "yes" | "no";
25 +
26 + const chars = JSON.parse(
27 + fs.readFileSync(path.join(__dirname, "../../data/characters.json"), "utf8")
28 + );
29 +
30 + let resolvedChar: any = null;
31 + let borrowedFrom: string | null = null;
32 +
33 + // Try own char first
34 + const ownEntry = chars[userKey]?.characters?.find((c: any) => c.name === charName);
35 + if (ownEntry) {
36 + setActiveCharacter(userKey, charName);
37 + clearSessionBorrowForUser(userKey);
38 + resolvedChar = ownEntry;
39 + } else {
40 + // Try shared char
41 + for (const [ownerKey, data] of Object.entries(chars) as [string, any][]) {
42 + const char = data.characters?.find(
43 + (c: any) => c.name === charName && c.sharedWith?.includes(userKey)
44 + );
45 + if (char) {
46 + setPersistentPreference(userKey, ownerKey, charName);
47 + clearSessionBorrowForUser(userKey);
48 + resolvedChar = char;
49 + borrowedFrom = ownerKey;
50 + break;
51 + }
52 + }
53 + }
54 +
55 + if (!resolvedChar) {
56 + await btn.reply({ content: `❌ Could not switch to **${charName}**.`, ephemeral: true });
57 + return;
58 + }
59 +
60 + // Re-add to poll with previous vote type
61 + const slot = [...polls.keys()][0];
62 + const state = slot !== undefined ? polls.get(slot) : null;
63 +
64 + if (state && !state.locked && state.confirmed === null) {
65 + const { char } = getEffectiveCharacter(userKey);
66 + const now = nowFormatted();
67 + const publicMsg = resolveMessage("public", prevVoteType, 1, userKey, null, null);
68 + const voteEntry = {
69 + userKey,
70 + displayName: charName,
71 + characterName: char?.name ?? charName,
72 + characterClass: char?.class ?? resolvedChar.class,
73 + characterLevel: char?.level ?? resolvedChar.level,
74 + characterNation: char?.nation ?? resolvedChar.nation,
75 + borrowedFrom: borrowedFrom ?? undefined,
76 + discordId: btn.user.id,
77 + votedAt: now,
78 + publicMessage: publicMsg ?? undefined,
79 + };
80 +
81 + for (const [id, entry] of [...state.yes.entries(), ...state.no.entries()]) {
82 + if (entry.userKey === userKey) {
83 + state.yes.delete(id);
84 + state.no.delete(id);
85 + }
86 + }
87 +
88 + if (prevVoteType === "yes") {
89 + state.yes.set(`switch_reclaim:${userKey}`, voteEntry);
90 + } else {
91 + state.no.set(`switch_reclaim:${userKey}`, voteEntry);
92 + }
93 +
94 + console.log(`[switch_reclaim] cleaning up for userKey=${userKey}`);
95 + console.log(`[switch_reclaim] yes keys:`, [...state.yes.entries()].map(([id, e]) => `${id}:${e.userKey}`));
96 + console.log(`[switch_reclaim] no keys:`, [...state.no.entries()].map(([id, e]) => `${id}:${e.userKey}`));
97 +
98 + const channel = await btn.client.channels.fetch(cfg("pollChannelId")) as TextChannel;
99 + await updatePollMessage(channel, slot!);
100 + }
101 +
102 + const charDisplay = resolvedChar ? format.char(resolvedChar) : charName;
103 + const borrowNote = borrowedFrom ? ` *(shared by ${borrowedFrom})*` : "";
104 + await btn.reply({
105 + content: `🔄 ${charDisplay}${borrowNote}${state ? ` — re-added to poll as **${prevVoteType}**.` : ""}`,
106 + ephemeral: true,
107 + });
108 + }
109 +
110 + export async function handleInteraction(interaction: Interaction): Promise<void> {
111 + try {
112 + if (interaction.isAutocomplete()) {
113 + await handleAutocomplete(interaction);
114 + return;
115 + }
116 +
117 + if (interaction.isButton()) {
118 + const btn = interaction as ButtonInteraction;
119 +
120 + console.log("[interactions] interaction btnId:", btn.customId);
121 + if (btn.customId.startsWith("conflict_")) {
122 + console.log("[interactions] routing to conflict handler:", btn.customId);
123 + return await handleConflictButton(btn);
124 + }
125 +
126 + if (btn.customId.startsWith("impersonate_")) {
127 + return await handleImpersonateButton(btn);
128 + }
129 +
130 + if (btn.customId.startsWith("switch_after_reclaim:")) {
131 + return await handleSwitchAfterReclaim(btn);
132 + }
133 +
134 + if (btn.customId.startsWith("borrow_accept:")) {
135 + const [, ownerKey, requesterKey] = btn.customId.split(":");
136 + return await handleBorrowAcceptButton(btn, ownerKey, requesterKey);
137 + }
138 +
139 + if (btn.customId.startsWith("borrow_decline:")) {
140 + const [, ownerKey, requesterKey] = btn.customId.split(":");
141 + return await handleBorrowDeclineButton(btn, ownerKey, requesterKey);
142 + }
143 +
144 + if (btn.customId === "tg_score_submit") {
145 + return await handleScoreSubmitButton(btn);
146 + }
147 +
148 + return await handleButton(btn);
149 + }
150 +
151 + if (interaction.isModalSubmit()) {
152 + return await modals.handleModal(interaction);
153 + }
154 +
155 + if (interaction.isStringSelectMenu()) {
156 + const sel = interaction as StringSelectMenuInteraction;
157 + if (sel.customId.startsWith("score_slot_select:")) {
158 + const userKey = sel.customId.split(":")[1];
159 + const slot = parseInt(sel.values[0], 10);
160 + await sel.showModal(modals.buildScoreModal(userKey, slot));
161 + return;
162 + }
163 + }
164 +
165 + if (interaction.isChatInputCommand()) {
166 + const cmd = interaction as ChatInputCommandInteraction;
167 + if (cmd.commandName === "tg") await handleTgCommand(cmd);
168 + if (cmd.commandName === "tg-config") await handleTgConfigCommand(cmd);
169 + }
170 + } catch (err) {
171 + console.error("Interaction error:", err);
172 + try {
173 + const msg = { content: "❌ An error occurred.", ephemeral: true };
174 + if ((interaction as any).replied || (interaction as any).deferred) {
175 + await (interaction as any).followUp(msg);
176 + } else {
177 + await (interaction as any).reply(msg);
178 + }
179 + } catch {}
180 + }
181 + }
Новіше Пізніше