Última atividade 1 month ago

nuno revisou este gist 1 month ago. Ir para a revisão

1 file changed, 142 insertions

switch.ts(arquivo criado)

@@ -0,0 +1,142 @@
1 + import { ChatInputCommandInteraction, TextChannel } from "discord.js";
2 + import { cfg } from "@systems/config";
3 + import { resolveUser, hasOfficerRole } from "@systems/users";
4 + import { setActiveCharacter, getActiveCharacter, getCharacterByName, getCharacters } from "@systems/characters";
5 + import { getEffectiveCharacter } from "@systems/borrow";
6 + import { setSessionBorrow, getSessionBorrow, setPersistentPreference, clearPersistentPreference } from "@systems/borrow";
7 + import { polls, updatePollMessage } from "@systems/poll";
8 + import { getClassEmoji } from "@systems/emojis";
9 + import { replyAndDelete } from "@src/utils";
10 + import { format } from "@format";
11 +
12 + import fs from "fs";
13 + import path from "path";
14 +
15 + const CHARS_PATH = path.join(__dirname, "../../data/characters.json");
16 +
17 + function findSharedChar(userKey: string, charName: string): { ownerKey: string; char: any } | null {
18 + try {
19 + const chars = JSON.parse(fs.readFileSync(CHARS_PATH, "utf8"));
20 + for (const [ownerKey, data] of Object.entries(chars) as [string, any][]) {
21 + if (ownerKey === userKey) continue;
22 + const char = data.characters?.find(
23 + (c: any) => c.name.toLowerCase() === charName.toLowerCase() && c.sharedWith?.includes(userKey)
24 + );
25 + if (char) return { ownerKey, char };
26 + }
27 + } catch {}
28 + return null;
29 + }
30 +
31 + // Reverse-lookup: find Discord userId for a userKey from current poll voters
32 + function findUserIdInPoll(state: any, userKey: string): string | null {
33 + for (const [id, entry] of [...state.yes.entries(), ...state.no.entries()]) {
34 + if (entry.userKey === userKey) return id;
35 + }
36 + return null;
37 + }
38 +
39 + export async function handleSwitch(interaction: ChatInputCommandInteraction): Promise<void> {
40 + const member = await interaction.guild!.members.fetch(interaction.user.id);
41 + const isOfficer = hasOfficerRole(member, cfg("officerRoles"));
42 + const nameArg = interaction.options.getString("name");
43 + const charName = interaction.options.getString("char_name", true);
44 +
45 + let userKey: string | null;
46 + if (nameArg) {
47 + if (!isOfficer) return void replyAndDelete(interaction, "❌ Only officers can switch other players' characters.");
48 + userKey = nameArg;
49 + } else {
50 + const user = await resolveUser(member);
51 + userKey = user.userKey;
52 + }
53 +
54 + if (!userKey) return void replyAndDelete(interaction, "❌ You are not registered in the system.");
55 +
56 + let resolvedChar: any = null;
57 + let borrowedFrom: string | null = null;
58 +
59 + // Try own characters first
60 + const set = setActiveCharacter(userKey, charName);
61 + if (set) {
62 + clearPersistentPreference(userKey);
63 + resolvedChar = getActiveCharacter(userKey);
64 + } else {
65 + // Fall back to shared characters
66 + const shared = findSharedChar(userKey, charName);
67 + if (shared) {
68 + setSessionBorrow(userKey, shared.ownerKey, shared.char.name);
69 + setPersistentPreference(userKey, shared.ownerKey, shared.char.name);
70 + resolvedChar = shared.char;
71 + borrowedFrom = shared.ownerKey;
72 + }
73 + }
74 +
75 + if (!resolvedChar) return void replyAndDelete(interaction, `❌ No character named **${charName}** found.`);
76 +
77 + const currentActive = getEffectiveCharacter(userKey);
78 + if (currentActive.char?.name === resolvedChar.name) {
79 + // Already active — just show current state
80 + const classEmoji = getClassEmoji(resolvedChar.class) || resolvedChar.class;
81 + const borrowNote = currentActive.borrowedFrom ? ` *(shared by ${currentActive.borrowedFrom})*` : "";
82 + return void replyAndDelete(interaction, `${classEmoji} ${resolvedChar.level} ${resolvedChar.name}${borrowNote}`, true);
83 + }
84 +
85 + // Check if target character is already in the active poll by another player
86 + const slot = [...polls.keys()][0];
87 + if (slot !== undefined) {
88 + const state = polls.get(slot)!;
89 + for (const [id, entry] of state.yes.entries()) {
90 + const isOwnEntry = id === (nameArg ? `injected:${userKey}` : interaction.user.id) ||
91 + id === `impersonated:${userKey}`;
92 + if (!isOwnEntry && entry.characterName === resolvedChar.name && entry.userKey !== userKey) {
93 + // Character is taken — check if we're the owner
94 + const isOwner = getCharacters(userKey).some((c) => c.name === resolvedChar!.name);
95 + if (isOwner) {
96 + // Show conflict embed — need a button interaction for this, so just error with instructions
97 + const slotHour = slot !== undefined ? polls.get(slot!)?.slot : cfg("slots")[0]?.tgHour ?? 20;
98 + const charDisplay = format.char(resolvedChar);
99 + return void replyAndDelete(interaction,
100 + `⚠️ ${charDisplay} is already scheduled for TG at ${slotHour}:00. Use the poll buttons to reclaim it.`,
101 + true
102 + );
103 + }
104 + const slotHour = slot !== undefined ? polls.get(slot)?.slot : cfg("slots")[0]?.tgHour ?? 20;
105 + return void replyAndDelete(interaction,
106 + `❌ ${format.char(resolvedChar)} is already scheduled for TG at ${slotHour}:00. Pick a different character.`,
107 + true
108 + );
109 + }
110 + }
111 + }
112 +
113 + // Update poll embed if user has voted
114 + if (slot !== undefined) {
115 + const state = polls.get(slot)!;
116 + const userId = nameArg
117 + ? findUserIdInPoll(state, userKey)
118 + : interaction.user.id;
119 +
120 + if (userId && (state.yes.has(userId) || state.no.has(userId))) {
121 + const updateEntry = (map: Map<string, any>) => {
122 + const entry = map.get(userId);
123 + if (entry) {
124 + entry.characterName = resolvedChar.name;
125 + entry.characterClass = resolvedChar.class;
126 + entry.characterLevel = resolvedChar.level;
127 + entry.characterNation = resolvedChar.nation;
128 + entry.borrowedFrom = borrowedFrom ?? undefined;
129 + }
130 + };
131 + updateEntry(state.yes);
132 + updateEntry(state.no);
133 +
134 + const channel = await interaction.client.channels.fetch(cfg("pollChannelId")) as TextChannel;
135 + await updatePollMessage(channel, slot);
136 + }
137 + }
138 +
139 + const classEmoji = getClassEmoji(resolvedChar.class) || resolvedChar.class;
140 + const borrowNote = borrowedFrom ? ` *(shared by ${borrowedFrom})*` : "";
141 + return void replyAndDelete(interaction, `🔄 ${classEmoji} ${resolvedChar.level} ${resolvedChar.name}${borrowNote}`, true);
142 + }
Próximo Anterior