Last active 1 month ago

tgAdmin.ts Raw
1import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
2import {
3 handleAdminUserMap,
4 handleAdminPollFixVoter,
5 handleAdminPollShowEntry,
6} from "@subcommands/admin/userMap";
7
8import { UpdatesCommands } from "@subcommands/admin/updates";
9import { ScoreInjectCommands } from "@subcommands/admin/score-inject";
10import { ResultCommands } from "@subcommands/admin/result-post";
11import { TestAlignCommands } from "@subcommands/admin/test-align";
12import { ResetWeekCommands } from "../subcommands/admin/reset-week";
13import { AnnouncementCommands } from "../subcommands/admin/announcement";
14
15export function buildTgAdminCommand(): SlashCommandBuilder {
16 const cmd = new SlashCommandBuilder()
17 .setName("tg-admin")
18 .setDescription("Administrative commands for TG bot management");
19
20 // ── user group ────────────────────────────────────────────────────────────────
21 cmd.addSubcommandGroup((g) => g
22 .setName("user")
23 .setDescription("Manage player registrations")
24 .addSubcommand((s) => s
25 .setName("map")
26 .setDescription("Register a Discord user to a userKey")
27 .addUserOption((o) => o
28 .setName("user")
29 .setDescription("Discord user to map")
30 .setRequired(true)
31 )
32 .addStringOption((o) => o
33 .setName("userkey")
34 .setDescription("The userKey to map this player to (must exist in characters.json)")
35 .setRequired(true)
36 .setAutocomplete(true)
37 )
38 )
39 .addSubcommand((s) => s
40 .setName("unmap")
41 .setDescription("Remove a player's registration")
42 .addUserOption((o) => o
43 .setName("user")
44 .setDescription("Discord user to unmap")
45 .setRequired(true)
46 )
47 )
48 .addSubcommand((s) => s
49 .setName("list")
50 .setDescription("List all registered player mappings")
51 )
52 );
53
54 // ── poll group ─────────────────────────────────────────────────────────────────
55 cmd.addSubcommandGroup((g) => g
56 .setName("poll")
57 .setDescription("Manage active poll entries")
58 .addSubcommand((s) => s
59 .setName("fix-voter")
60 .setDescription("Re-resolve a voter's character in the active poll")
61 .addUserOption((o) => o
62 .setName("user")
63 .setDescription("Discord user whose entry needs fixing")
64 .setRequired(true)
65 )
66 )
67 .addSubcommand((s) => s
68 .setName("show-entry")
69 .setDescription("Show raw poll entry for a user")
70 .addUserOption((o) => o
71 .setName("user")
72 .setDescription("Discord user to inspect")
73 .setRequired(true)
74 )
75 )
76 );
77
78 cmd.addSubcommandGroup((g) => g
79 .setName("updates")
80 .setDescription("Manage update posts")
81 .addSubcommand((s) => s
82 .setName("post")
83 .setDescription("Post or edit an update embed")
84 .addStringOption((o) => o
85 .setName("version")
86 .setDescription("Version to post (defaults to latest)")
87 .setRequired(false)
88 .setAutocomplete(true)
89 )
90 )
91 .addSubcommand((s) => s
92 .setName("preview")
93 .setDescription("Preview an update embed (ephemeral)")
94 .addStringOption((o) => o
95 .setName("version")
96 .setDescription("Version to preview (defaults to latest)")
97 .setRequired(false)
98 .setAutocomplete(true)
99 )
100 )
101 .addSubcommand((s) => s
102 .setName("list")
103 .setDescription("List all versions and their post status")
104 )
105 )
106
107 // ── result group ─────────────────────────────────────────────────────────────────
108
109 cmd.addSubcommandGroup((g) => g
110 .setName("result")
111 .setDescription("Manage TG results")
112 .addSubcommand((s) => s
113 .setName("post")
114 .setDescription("Post or edit a TG result")
115 .addStringOption((o) => o.setName("history_key").setDescription("TG to post").setRequired(true).setAutocomplete(true))
116 )
117 )
118
119 // ── leaderboard group ─────────────────────────────────────────────────────────────────
120
121 cmd.addSubcommandGroup((g) => g
122 .setName("leaderboard")
123 .setDescription("Manage leaderboard")
124 .addSubcommand((s) => s
125 .setName("post")
126 .setDescription("Post or edit the leaderboard")
127 .addStringOption((o) => o.setName("week_key").setDescription("Week e.g. 2026-W24 (defaults current)").setAutocomplete(true))
128 )
129 .addSubcommand((s) => s
130 .setName("post-highlights")
131 .setDescription("Post or edit the leaderboard highlights")
132 .addStringOption((o) => o.setName("week_key").setDescription("Week e.g. 2026-W24 (defaults current)").setAutocomplete(true))
133 )
134)
135
136 cmd.addSubcommand((s) => s
137 .setName("score-inject")
138 .setDescription("Inject a score for any player (officer only)")
139 .addStringOption((o) => o.setName("char_name").setDescription("Character").setRequired(true).setAutocomplete(true))
140 .addIntegerOption((o) => o.setName("pts").setDescription("Points").setRequired(true))
141 .addIntegerOption((o) => o.setName("slot").setDescription("TG slot hour").setRequired(true))
142 .addStringOption((o) => o.setName("date").setDescription("Date YYYY-MM-DD (defaults today)"))
143 .addIntegerOption((o) => o.setName("k").setDescription("Kills"))
144 .addIntegerOption((o) => o.setName("d").setDescription("Deaths"))
145 .addIntegerOption((o) => o.setName("atk").setDescription("ATK damage"))
146 .addIntegerOption((o) => o.setName("def").setDescription("DEF damage taken"))
147 .addIntegerOption((o) => o.setName("heal").setDescription("Healing done"))
148 .addStringOption((o) => o
149 .setName("played_by")
150 .setDescription("Userkey of who actually played (if different from character owner)")
151 )
152 )
153
154 cmd.addSubcommand((s) => s
155 .setName("test-align")
156 .setDescription("[TEMP] Test embed text alignment calibration")
157 .addStringOption((o) => o.setName("name_a").setDescription("First name").setRequired(true))
158 .addStringOption((o) => o.setName("name_b").setDescription("Second name").setRequired(true))
159 .addIntegerOption((o) => o.setName("fillers").setDescription("Filler count to add to name_a").setRequired(true))
160 .addStringOption((o) => o
161 .setName("filler_type")
162 .setDescription("Which invisible character to use")
163 .addChoices(
164 { name: "Hangul Filler", value: "hangul" },
165 { name: "Thin Space", value: "thin" },
166 { name: "Hair Space", value: "hair" },
167 )
168 )
169 )
170
171 cmd.addSubcommandGroup((g) => g
172 .setName("announcement")
173 .setDescription("Manage announcements")
174 .addSubcommand((s) => s
175 .setName("post")
176 .setDescription("Post an announcement")
177 .addStringOption((o) => o.setName("id").setDescription("Announcement ID").setRequired(true).setAutocomplete(true))
178 .addBooleanOption((o) => o.setName("force").setDescription("Repost even if already posted"))
179 )
180 .addSubcommand((s) => s
181 .setName("preview")
182 .setDescription("Preview an announcement")
183 .addStringOption((o) => o.setName("id").setDescription("Announcement ID").setRequired(true).setAutocomplete(true))
184 )
185 .addSubcommand((s) => s
186 .setName("list")
187 .setDescription("List all announcements")
188 )
189 )
190
191 return cmd;
192}
193
194export async function handleTgAdminCommand(interaction: ChatInputCommandInteraction): Promise<void> {
195 const group = interaction.options.getSubcommandGroup(false);
196 const sub = interaction.options.getSubcommand();
197
198 if (group === "user") {
199 if (sub === "map" || sub === "unmap" || sub === "list") {
200 return handleAdminUserMap(interaction);
201 }
202 }
203
204 if (group === "poll") {
205 if (sub === "fix-voter") return handleAdminPollFixVoter(interaction);
206 if (sub === "show-entry") return handleAdminPollShowEntry(interaction);
207 }
208
209 if (group === "updates") {
210 if (sub === "post") return UpdatesCommands.post(interaction);
211 if (sub === "preview") return UpdatesCommands.preview(interaction);
212 if (sub === "list") return UpdatesCommands.list(interaction);
213 }
214
215 if (group === null && sub === "score-inject") return ScoreInjectCommands.inject(interaction);
216 if (group === "result" && sub === "post") return ResultCommands.post(interaction);
217 if (group === "leaderboard" && sub === "post") return ResultCommands.leaderboardPost(interaction);
218 if (group === "leaderboard" && sub === "post-highlights") return ResultCommands.leaderboardHighlights(interaction);
219
220 if (group === "announcement" && sub === "post") return AnnouncementCommands.post(interaction);
221 if (group === "announcement" && sub === "preview") return AnnouncementCommands.preview(interaction);
222 if (group === "announcement" && sub === "list") return AnnouncementCommands.list(interaction);
223
224 if (group === null && sub === "test-align") return TestAlignCommands.handle(interaction);
225 if (group === null && sub === "reset-week") return ResetWeekCommands.handle(interaction);
226}