Ultima attività 4 weeks ago

nuno ha revisionato questo gist 4 weeks ago. Vai alla revisione

1 file changed, 160 insertions

switch.ts(file creato)

@@ -0,0 +1,160 @@
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 {
6 + getEffectiveCharacter,
7 + setSessionBorrow,
8 + setPersistentPreference,
9 + clearPersistentPreference,
10 + clearSessionBorrowForUser,
11 + } from "@systems/borrow";
12 + import { polls, updatePollMessage } from "@systems/poll";
13 + import { getClassEmoji } from "@systems/emojis";
14 + import { replyAndDelete } from "@src/utils";
15 + import { format } from "@format";
16 + import { buildCharSelectButtons } from "@systems/charSelect";
17 + import fs from "fs";
18 + import path from "path";
19 +
20 + const CHARS_PATH = path.join(__dirname, "../../data/characters.json");
21 +
22 + function findSharedChar(userKey: string, charName: string): { ownerKey: string; char: any } | null {
23 + try {
24 + const chars = JSON.parse(fs.readFileSync(CHARS_PATH, "utf8"));
25 + for (const [ownerKey, data] of Object.entries(chars) as [string, any][]) {
26 + if (ownerKey === userKey) continue;
27 + const char = data.characters?.find(
28 + (c: any) => c.name.toLowerCase() === charName.toLowerCase() && c.sharedWith?.includes(userKey)
29 + );
30 + if (char) return { ownerKey, char };
31 + }
32 + } catch {}
33 + return null;
34 + }
35 +
36 + function findVoteIdInPoll(state: any, userKey: string): string | null {
37 + for (const [id, entry] of [...state.yes.entries(), ...state.no.entries()]) {
38 + if (entry.userKey === userKey) return id;
39 + }
40 + return null;
41 + }
42 +
43 + export async function handleSwitch(interaction: ChatInputCommandInteraction): Promise<void> {
44 + const member = await interaction.guild!.members.fetch(interaction.user.id);
45 + const isOfficer = hasOfficerRole(member, cfg("officerRoles"));
46 + const nameArg = interaction.options.getString("name");
47 + const charName = interaction.options.getString("char_name", true);
48 +
49 + let userKey: string | null;
50 + if (nameArg) {
51 + if (!isOfficer) return void replyAndDelete(interaction, "❌ Only officers can switch other players' characters.");
52 + userKey = nameArg;
53 + } else {
54 + const user = await resolveUser(member);
55 + userKey = user.userKey;
56 + }
57 +
58 + if (!userKey) return void replyAndDelete(interaction, "❌ You are not registered in the system.");
59 +
60 + // Resolve the target character without switching yet
61 + let resolvedChar: any = null;
62 + let borrowedFrom: string | null = null;
63 +
64 + const ownChar = getCharacterByName(userKey, charName);
65 + if (ownChar) {
66 + resolvedChar = ownChar;
67 + } else {
68 + const shared = findSharedChar(userKey, charName);
69 + if (shared) {
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 + // If already active — just show current state without switching
78 + const current = getEffectiveCharacter(userKey);
79 + if (current.char?.name === resolvedChar.name) {
80 + const classEmoji = getClassEmoji(resolvedChar.class) || resolvedChar.class;
81 + const borrowNote = current.borrowedFrom ? ` *(shared by ${current.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 === interaction.user.id || id === `impersonated:${userKey}`;
91 + if (!isOwnEntry && entry.characterName === resolvedChar.name && entry.userKey !== userKey) {
92 + const slotHour = state.slot;
93 + const charDisplay = format.char(resolvedChar);
94 + const isOwner = getCharacters(userKey).some((c) => c.name === resolvedChar.name);
95 + if (isOwner) {
96 + await interaction.reply({
97 + content: `⚠️ ${charDisplay} is already scheduled for TG at ${slotHour}:00. Vote with your character to trigger the reclaim option, or switch to a different one:`,
98 + components: buildCharSelectButtons(userKey, {
99 + customIdPrefix: `switch_after_reclaim:${userKey}`,
100 + excludeCharName: resolvedChar.name,
101 + appendToCustomId: ":yes",
102 + }),
103 + ephemeral: true,
104 + });
105 + return;
106 + }
107 + const buttons = buildCharSelectButtons(userKey, {
108 + customIdPrefix: `switch_after_reclaim:${userKey}`,
109 + excludeCharName: resolvedChar.name,
110 + appendToCustomId: `:${"yes"}`,
111 + });
112 + await interaction.reply({
113 + content: `❌ ${charDisplay} is already scheduled for TG at ${slotHour}:00. Pick a different character.`,
114 + components: buttons,
115 + ephemeral: true,
116 + });
117 + return;
118 + }
119 + }
120 + }
121 +
122 + // Now actually switch
123 + if (borrowedFrom) {
124 + setSessionBorrow(userKey, borrowedFrom, resolvedChar.name);
125 + setPersistentPreference(userKey, borrowedFrom, resolvedChar.name);
126 + } else {
127 + setActiveCharacter(userKey, charName);
128 + clearPersistentPreference(userKey);
129 + clearSessionBorrowForUser(userKey);
130 + resolvedChar = getActiveCharacter(userKey);
131 + }
132 +
133 + // Update poll embed if user has already voted
134 + if (slot !== undefined) {
135 + const state = polls.get(slot)!;
136 + const voteId = findVoteIdInPoll(state, userKey);
137 +
138 + if (voteId && (state.yes.has(voteId) || state.no.has(voteId))) {
139 + const updateEntry = (map: Map<string, any>) => {
140 + const entry = map.get(voteId);
141 + if (entry) {
142 + entry.characterName = resolvedChar.name;
143 + entry.characterClass = resolvedChar.class;
144 + entry.characterLevel = resolvedChar.level;
145 + entry.characterNation = resolvedChar.nation;
146 + entry.borrowedFrom = borrowedFrom ?? undefined;
147 + }
148 + };
149 + updateEntry(state.yes);
150 + updateEntry(state.no);
151 +
152 + const channel = await interaction.client.channels.fetch(cfg("pollChannelId")) as TextChannel;
153 + await updatePollMessage(channel, slot);
154 + }
155 + }
156 +
157 + const classEmoji = getClassEmoji(resolvedChar.class) || resolvedChar.class;
158 + const borrowNote = borrowedFrom ? ` *(shared by ${borrowedFrom})*` : "";
159 + return void replyAndDelete(interaction, `🔄 ${classEmoji} ${resolvedChar.level} ${resolvedChar.name}${borrowNote}`, true);
160 + }
Più nuovi Più vecchi