Son aktivite 1 month ago

nuno bu gisti düzenledi 1 month ago. Düzenlemeye git

1 file changed, 311 insertions

tg.ts(dosya oluşturuldu)

@@ -0,0 +1,311 @@
1 + import {
2 + ChatInputCommandInteraction,
3 + SlashCommandBuilder,
4 + REST,
5 + Routes,
6 + } from "discord.js";
7 + import { cfg } from "../systems/config";
8 + import { hasOfficerRole } from "../systems/users";
9 +
10 + // Poll subcommands
11 + import { handleStart } from "../subcommands/poll/start";
12 + import { handleLock } from "../subcommands/poll/lock";
13 + import { handleUnlock } from "../subcommands/poll/unlock";
14 + import { handleConfirm } from "../subcommands/poll/confirm";
15 + import { handleStatus } from "../subcommands/poll/status";
16 + import { handleReload } from "../subcommands/poll/reload";
17 + import { handleSetMessage, handleClearMessage, handleSetEphemeral, handleClearEphemeral } from "../subcommands/poll/setMessage";
18 + import { handleInject, handleRemoveVote } from "../subcommands/poll/inject";
19 + import { handleSeed } from "../subcommands/poll/seed";
20 + import { handlePurge } from "../subcommands/poll/purge";
21 + import { handleImpersonate } from "../subcommands/impersonate";
22 +
23 + // Char subcommands (borrow / sharing system)
24 + import { handleCharBorrow } from "../subcommands/char/borrow";
25 + import { handleCharAccept } from "../subcommands/char/accept";
26 + import { handleCharDecline } from "../subcommands/char/decline";
27 + import { handleCharShare, handleCharUnshare } from "../subcommands/char/share";
28 +
29 + // Score subcommands
30 + import { handleScoreSet } from "../subcommands/score/set";
31 + import { handleScoreGet } from "../subcommands/score/get";
32 +
33 + // Rank subcommands
34 + import { handleRankGet } from "../subcommands/rank/get";
35 + import { handleRankPost } from "../subcommands/rank/post";
36 +
37 + // Result subcommands
38 + import { handleResultSet } from "../subcommands/result/set";
39 + import { handleResultView } from "../subcommands/result/view";
40 + import { handleResultPost } from "../subcommands/result/post";
41 +
42 + // Bringer subcommands
43 + import { handleBringerSet } from "../subcommands/bringer/set";
44 + import { handleBringerClear } from "../subcommands/bringer/clear";
45 +
46 + // Other
47 + import { handleSwitch } from "../subcommands/switch";
48 + import { handleHistory } from "../subcommands/history";
49 +
50 + export function buildTgCommand(): SlashCommandBuilder {
51 + const cmd = new SlashCommandBuilder()
52 + .setName("tg")
53 + .setDescription("TG planning and tracking");
54 +
55 + // ── poll group ─────────────────────────────────────────────────────────────
56 + cmd.addSubcommandGroup((g) => g
57 + .setName("poll")
58 + .setDescription("Manage the TG poll")
59 + .addSubcommand((s) => s.setName("start").setDescription("Post a fresh TG poll")
60 + .addStringOption((o) => o.setName("slot").setDescription("TG hour (e.g. 20, 22)").setRequired(false)))
61 + .addSubcommand((s) => s.setName("lock").setDescription("Lock the active poll")
62 + .addStringOption((o) => o.setName("message").setDescription("One-time lock message").setRequired(false)))
63 + .addSubcommand((s) => s.setName("unlock").setDescription("Unlock the active poll"))
64 + .addSubcommand((s) => s.setName("confirm").setDescription("Confirm whether TG is happening")
65 + .addStringOption((o) => o.setName("decision").setDescription("yes or no").setRequired(true)
66 + .addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
67 + .addStringOption((o) => o.setName("message").setDescription("One-time confirm message").setRequired(false))
68 + .addBooleanOption((o) => o.setName("tag").setDescription("Tag configured roles?").setRequired(false)))
69 + .addSubcommand((s) => s.setName("reload").setDescription("Reload messages and emojis from disk"))
70 + .addSubcommand((s) => s.setName("status").setDescription("Show current poll and config status"))
71 + .addSubcommand((s) => s.setName("set-message").setDescription("Set public message override for a user")
72 + .addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(true)
73 + .addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
74 + .addStringOption((o) => o.setName("message").setDescription("Message to show").setRequired(true))
75 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
76 + .addSubcommand((s) => s.setName("clear-message").setDescription("Clear public message override")
77 + .addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(false)
78 + .addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
79 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
80 + .addSubcommand((s) => s.setName("set-ephemeral").setDescription("Set ephemeral message override for a user")
81 + .addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(true)
82 + .addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
83 + .addStringOption((o) => o.setName("message").setDescription("Message to show").setRequired(true))
84 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
85 + .addSubcommand((s) => s.setName("clear-ephemeral").setDescription("Clear ephemeral message override")
86 + .addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(false)
87 + .addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
88 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
89 + .addSubcommand((s) => s.setName("inject").setDescription("Inject a vote for a registered user")
90 + .addStringOption((o) => o.setName("name").setDescription("Usermap key").setRequired(true))
91 + .addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(true)
92 + .addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" })))
93 + .addSubcommand((s) => s.setName("remove-vote").setDescription("Remove a vote for a registered user")
94 + .addStringOption((o) => o.setName("name").setDescription("Usermap key").setRequired(true)))
95 + .addSubcommand((s) => s.setName("purge").setDescription("Delete all bot messages from the poll channel"))
96 + .addSubcommand((s) => s.setName("seed").setDescription("Inject all registered players as Yes votes for layout testing"))
97 + );
98 +
99 + // ── score group ────────────────────────────────────────────────────────────
100 + cmd.addSubcommandGroup((g) => g
101 + .setName("score")
102 + .setDescription("Score management")
103 + .addSubcommand((s) => s.setName("set").setDescription("Submit a score")
104 + .addIntegerOption((o) => o.setName("pts").setDescription("Points").setRequired(true))
105 + .addStringOption((o) => o.setName("slot").setDescription("TG hour (e.g. 20, 8pm, midnight)").setRequired(false))
106 + .addIntegerOption((o) => o.setName("k").setDescription("Kills").setRequired(false))
107 + .addIntegerOption((o) => o.setName("d").setDescription("Deaths").setRequired(false))
108 + .addIntegerOption((o) => o.setName("atk").setDescription("Attack score").setRequired(false))
109 + .addIntegerOption((o) => o.setName("def").setDescription("Defense score").setRequired(false))
110 + .addIntegerOption((o) => o.setName("heal").setDescription("Healing score (FA only)").setRequired(false))
111 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
112 + .addSubcommand((s) => s.setName("get").setDescription("View a score")
113 + .addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false))
114 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
115 + );
116 +
117 + // ── rank group ─────────────────────────────────────────────────────────────
118 + cmd.addSubcommandGroup((g) => g
119 + .setName("rank")
120 + .setDescription("W.Rank management")
121 + .addSubcommand((s) => s.setName("get").setDescription("View W.Rank")
122 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
123 + .addSubcommand((s) => s.setName("post").setDescription("Post leaderboard publicly (officer only)"))
124 + );
125 +
126 + // ── result group ───────────────────────────────────────────────────────────
127 + cmd.addSubcommandGroup((g) => g
128 + .setName("result")
129 + .setDescription("TG result management")
130 + .addSubcommand((s) => s.setName("set").setDescription("Set nation K/D (officer only)")
131 + .addStringOption((o) => o.setName("nation").setDescription("Source nation").setRequired(true)
132 + .addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" }))
133 + .addIntegerOption((o) => o.setName("kills").setDescription("Kills").setRequired(true))
134 + .addIntegerOption((o) => o.setName("deaths").setDescription("Deaths").setRequired(true))
135 + .addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false)))
136 + .addSubcommand((s) => s.setName("view").setDescription("View result for a slot")
137 + .addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false)))
138 + .addSubcommand((s) => s.setName("post").setDescription("Post result publicly (officer only)")
139 + .addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false)))
140 + );
141 +
142 + // ── bringer group ──────────────────────────────────────────────────────────
143 + cmd.addSubcommandGroup((g) => g
144 + .setName("bringer")
145 + .setDescription("Bringer management (officer only)")
146 + .addSubcommand((s) => s.setName("set").setDescription("Manually set Bringer")
147 + .addStringOption((o) => o.setName("nation").setDescription("Nation").setRequired(true)
148 + .addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" }))
149 + .addStringOption((o) => o.setName("name").setDescription("Usermap key").setRequired(true)))
150 + .addSubcommand((s) => s.setName("clear").setDescription("Clear Bringer override")
151 + .addStringOption((o) => o.setName("nation").setDescription("Nation").setRequired(true)
152 + .addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" })))
153 + );
154 +
155 + // ── switch ─────────────────────────────────────────────────────────────────
156 + cmd.addSubcommand((s) => s.setName("switch").setDescription("Switch active character")
157 + .addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true))
158 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false))
159 + );
160 +
161 + // ── char group ─────────────────────────────────────────────────────────────
162 + cmd.addSubcommandGroup((g) => g
163 + .setName("char")
164 + .setDescription("Character management")
165 + .addSubcommand((s) => s.setName("add").setDescription("Add a character")
166 + .addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true))
167 + .addStringOption((o) => o.setName("class").setDescription("Class").setRequired(true)
168 + .addChoices(
169 + { name: "Blader (BL)", value: "BL" },
170 + { name: "Force Blader (FB)", value: "FB" },
171 + { name: "Force Shielder (FS)", value: "FS" },
172 + { name: "Force Archer (FA)", value: "FA" },
173 + { name: "Force Gunner (FG)", value: "FG" },
174 + { name: "Gladiator (GL)", value: "GL" },
175 + { name: "Dark Mage (DM)", value: "DM" },
176 + { name: "Wizard (WI)", value: "WI" },
177 + { name: "Warrior (WA)", value: "WA" },
178 + ))
179 + .addIntegerOption((o) => o.setName("level").setDescription("Level").setRequired(true))
180 + .addStringOption((o) => o.setName("nation").setDescription("Nation").setRequired(true)
181 + .addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" }))
182 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
183 + .addSubcommand((s) => s.setName("remove").setDescription("Remove a character")
184 + .addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true))
185 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
186 + .addSubcommand((s) => s.setName("set-active").setDescription("Set active character")
187 + .addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true))
188 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
189 + .addSubcommand((s) => s.setName("set-nation").setDescription("Change a character's nation")
190 + .addStringOption((o) => o.setName("nation").setDescription("Nation").setRequired(true)
191 + .addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" }))
192 + .addStringOption((o) => o.setName("char_name").setDescription("Character name (defaults to active)").setRequired(false))
193 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
194 + .addSubcommand((s) => s.setName("set-stats").setDescription("Set character combat stats")
195 + .addStringOption((o) => o.setName("char_name").setDescription("Character name (defaults to active)").setRequired(false))
196 + .addIntegerOption((o) => o.setName("atk").setDescription("Attack score").setRequired(false))
197 + .addIntegerOption((o) => o.setName("def").setDescription("Defense score").setRequired(false))
198 + .addIntegerOption((o) => o.setName("heal").setDescription("Healing score").setRequired(false))
199 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false)))
200 + .addSubcommand((s) => s.setName("borrow").setDescription("Request to borrow a character for this session")
201 + .addStringOption((o) => o.setName("owner").setDescription("Owner's usermap key").setRequired(true))
202 + .addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true))
203 + .addStringOption((o) => o.setName("name").setDescription("Grant to this user (officer only)").setRequired(false)))
204 + .addSubcommand((s) => s.setName("accept").setDescription("Accept a borrow request")
205 + .addStringOption((o) => o.setName("name").setDescription("Requester's usermap key").setRequired(true)))
206 + .addSubcommand((s) => s.setName("decline").setDescription("Decline a borrow request")
207 + .addStringOption((o) => o.setName("name").setDescription("Requester's usermap key").setRequired(true)))
208 + .addSubcommand((s) => s.setName("share").setDescription("Permanently share a character")
209 + .addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true))
210 + .addStringOption((o) => o.setName("name").setDescription("Usermap key to share with").setRequired(true))
211 + .addStringOption((o) => o.setName("owner").setDescription("Owner's usermap key (officer only)").setRequired(false)))
212 + .addSubcommand((s) => s.setName("unshare").setDescription("Revoke permanent character share")
213 + .addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true))
214 + .addStringOption((o) => o.setName("name").setDescription("Usermap key to revoke").setRequired(true))
215 + .addStringOption((o) => o.setName("owner").setDescription("Owner's usermap key (officer only)").setRequired(false)))
216 + .addSubcommand((s) => s.setName("active").setDescription("Check active character for a user")
217 + .addStringOption((o) => o.setName("name").setDescription("Usermap key (officer: check others)").setRequired(false)))
218 + );
219 +
220 + // ── history ────────────────────────────────────────────────────────────────
221 + cmd.addSubcommand((s) => s.setName("history").setDescription("View TG history (officer only)")
222 + .addStringOption((o) => o.setName("date").setDescription("Date (YYYY-MM-DD)").setRequired(false))
223 + .addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false))
224 + );
225 +
226 + // ── impersonate ────────────────────────────────────────────────────────────────
227 + cmd.addSubcommand((s) => s.setName("impersonate").setDescription("Impersonate a registered user for testing (officer only)"));
228 +
229 + return cmd;
230 + }
231 +
232 + export async function handleTgCommand(interaction: ChatInputCommandInteraction): Promise<void> {
233 + const group = interaction.options.getSubcommandGroup(false);
234 + const sub = interaction.options.getSubcommand();
235 + const member = await interaction.guild!.members.fetch(interaction.user.id);
236 + const isOfficer = hasOfficerRole(member, cfg("officerRoles"));
237 +
238 + // Officer-only commands
239 + const officerOnlyGroups = ["poll", "result", "bringer"];
240 + const officerOnlySubs = ["history"];
241 + const officerOnlyRankSubs = ["post"];
242 +
243 + if (group && officerOnlyGroups.includes(group) && !isOfficer) {
244 + return void interaction.reply({ content: "❌ You don't have permission to use this command.", ephemeral: true });
245 + }
246 + if (!group && officerOnlySubs.includes(sub) && !isOfficer) {
247 + return void interaction.reply({ content: "❌ You don't have permission to use this command.", ephemeral: true });
248 + }
249 + if (group === "rank" && officerOnlyRankSubs.includes(sub) && !isOfficer) {
250 + return void interaction.reply({ content: "❌ You don't have permission to use this command.", ephemeral: true });
251 + }
252 +
253 + // Route
254 + if (group === "poll") {
255 + if (sub === "start") return handleStart(interaction);
256 + if (sub === "lock") return handleLock(interaction);
257 + if (sub === "unlock") return handleUnlock(interaction);
258 + if (sub === "confirm") return handleConfirm(interaction);
259 + if (sub === "reload") return handleReload(interaction);
260 + if (sub === "status") return handleStatus(interaction);
261 + if (sub === "set-message") return handleSetMessage(interaction);
262 + if (sub === "clear-message") return handleClearMessage(interaction);
263 + if (sub === "set-ephemeral") return handleSetEphemeral(interaction);
264 + if (sub === "clear-ephemeral") return handleClearEphemeral(interaction);
265 + if (sub === "inject") return handleInject(interaction);
266 + if (sub === "remove-vote") return handleRemoveVote(interaction);
267 + if (sub === "purge") return handlePurge(interaction);
268 + if (sub === "seed") return handleSeed(interaction);
269 + }
270 + if (group === "score") {
271 + if (sub === "set") return handleScoreSet(interaction);
272 + if (sub === "get") return handleScoreGet(interaction);
273 + }
274 + if (group === "rank") {
275 + if (sub === "get") return handleRankGet(interaction);
276 + if (sub === "post") return handleRankPost(interaction);
277 + }
278 + if (group === "result") {
279 + if (sub === "set") return handleResultSet(interaction);
280 + if (sub === "view") return handleResultView(interaction);
281 + if (sub === "post") return handleResultPost(interaction);
282 + }
283 + if (group === "bringer") {
284 + if (sub === "set") return handleBringerSet(interaction);
285 + if (sub === "clear") return handleBringerClear(interaction);
286 + }
287 + if (group === "char") {
288 + if (sub === "add") return handleCharAdd(interaction);
289 + if (sub === "remove") return handleCharRemove(interaction);
290 + if (sub === "set-active") return handleCharSetActive(interaction);
291 + if (sub === "set-nation") return handleCharSetNation(interaction);
292 + if (sub === "set-stats") return handleCharSetStats(interaction);
293 + if (sub === "borrow") return handleCharBorrow(interaction);
294 + if (sub === "accept") return handleCharAccept(interaction);
295 + if (sub === "decline") return handleCharDecline(interaction);
296 + if (sub === "share") return handleCharShare(interaction);
297 + if (sub === "unshare") return handleCharUnshare(interaction);
298 + if (sub === "active") return handleCharActive(interaction);
299 + }
300 + if (!group && sub === "switch") return handleSwitch(interaction);
301 + if (!group && sub === "history") return handleHistory(interaction);
302 + if (!group && sub === "impersonate") return handleImpersonate(interaction);
303 + }
304 +
305 + // Import char handlers here to keep tg.ts clean
306 + import { handleCharAdd } from "../subcommands/char/add";
307 + import { handleCharRemove } from "../subcommands/char/remove";
308 + import { handleCharSetActive } from "../subcommands/char/setActive";
309 + import { handleCharSetNation } from "../subcommands/char/setNation";
310 + import { handleCharSetStats } from "../subcommands/char/setStats";
311 + import { handleCharActive } from "../subcommands/char/active";
Daha yeni Daha eski