tgAdmin.ts
· 9.2 KiB · TypeScript
Raw
import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
import {
handleAdminUserMap,
handleAdminPollFixVoter,
handleAdminPollShowEntry,
} from "@subcommands/admin/userMap";
import { UpdatesCommands } from "@subcommands/admin/updates";
import { ScoreInjectCommands } from "@subcommands/admin/score-inject";
import { ResultCommands } from "@subcommands/admin/result-post";
import { TestAlignCommands } from "@subcommands/admin/test-align";
import { ResetWeekCommands } from "../subcommands/admin/reset-week";
import { AnnouncementCommands } from "../subcommands/admin/announcement";
export function buildTgAdminCommand(): SlashCommandBuilder {
const cmd = new SlashCommandBuilder()
.setName("tg-admin")
.setDescription("Administrative commands for TG bot management");
// ── user group ────────────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("user")
.setDescription("Manage player registrations")
.addSubcommand((s) => s
.setName("map")
.setDescription("Register a Discord user to a userKey")
.addUserOption((o) => o
.setName("user")
.setDescription("Discord user to map")
.setRequired(true)
)
.addStringOption((o) => o
.setName("userkey")
.setDescription("The userKey to map this player to (must exist in characters.json)")
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand((s) => s
.setName("unmap")
.setDescription("Remove a player's registration")
.addUserOption((o) => o
.setName("user")
.setDescription("Discord user to unmap")
.setRequired(true)
)
)
.addSubcommand((s) => s
.setName("list")
.setDescription("List all registered player mappings")
)
);
// ── poll group ─────────────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("poll")
.setDescription("Manage active poll entries")
.addSubcommand((s) => s
.setName("fix-voter")
.setDescription("Re-resolve a voter's character in the active poll")
.addUserOption((o) => o
.setName("user")
.setDescription("Discord user whose entry needs fixing")
.setRequired(true)
)
)
.addSubcommand((s) => s
.setName("show-entry")
.setDescription("Show raw poll entry for a user")
.addUserOption((o) => o
.setName("user")
.setDescription("Discord user to inspect")
.setRequired(true)
)
)
);
cmd.addSubcommandGroup((g) => g
.setName("updates")
.setDescription("Manage update posts")
.addSubcommand((s) => s
.setName("post")
.setDescription("Post or edit an update embed")
.addStringOption((o) => o
.setName("version")
.setDescription("Version to post (defaults to latest)")
.setRequired(false)
.setAutocomplete(true)
)
)
.addSubcommand((s) => s
.setName("preview")
.setDescription("Preview an update embed (ephemeral)")
.addStringOption((o) => o
.setName("version")
.setDescription("Version to preview (defaults to latest)")
.setRequired(false)
.setAutocomplete(true)
)
)
.addSubcommand((s) => s
.setName("list")
.setDescription("List all versions and their post status")
)
)
// ── result group ─────────────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("result")
.setDescription("Manage TG results")
.addSubcommand((s) => s
.setName("post")
.setDescription("Post or edit a TG result")
.addStringOption((o) => o.setName("history_key").setDescription("TG to post").setRequired(true).setAutocomplete(true))
)
)
// ── leaderboard group ─────────────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("leaderboard")
.setDescription("Manage leaderboard")
.addSubcommand((s) => s
.setName("post")
.setDescription("Post or edit the leaderboard")
.addStringOption((o) => o.setName("week_key").setDescription("Week e.g. 2026-W24 (defaults current)").setAutocomplete(true))
)
.addSubcommand((s) => s
.setName("post-highlights")
.setDescription("Post or edit the leaderboard highlights")
.addStringOption((o) => o.setName("week_key").setDescription("Week e.g. 2026-W24 (defaults current)").setAutocomplete(true))
)
)
cmd.addSubcommand((s) => s
.setName("score-inject")
.setDescription("Inject a score for any player (officer only)")
.addStringOption((o) => o.setName("char_name").setDescription("Character").setRequired(true).setAutocomplete(true))
.addIntegerOption((o) => o.setName("pts").setDescription("Points").setRequired(true))
.addIntegerOption((o) => o.setName("slot").setDescription("TG slot hour").setRequired(true))
.addStringOption((o) => o.setName("date").setDescription("Date YYYY-MM-DD (defaults today)"))
.addIntegerOption((o) => o.setName("k").setDescription("Kills"))
.addIntegerOption((o) => o.setName("d").setDescription("Deaths"))
.addIntegerOption((o) => o.setName("atk").setDescription("ATK damage"))
.addIntegerOption((o) => o.setName("def").setDescription("DEF damage taken"))
.addIntegerOption((o) => o.setName("heal").setDescription("Healing done"))
.addStringOption((o) => o
.setName("played_by")
.setDescription("Userkey of who actually played (if different from character owner)")
)
)
cmd.addSubcommand((s) => s
.setName("test-align")
.setDescription("[TEMP] Test embed text alignment calibration")
.addStringOption((o) => o.setName("name_a").setDescription("First name").setRequired(true))
.addStringOption((o) => o.setName("name_b").setDescription("Second name").setRequired(true))
.addIntegerOption((o) => o.setName("fillers").setDescription("Filler count to add to name_a").setRequired(true))
.addStringOption((o) => o
.setName("filler_type")
.setDescription("Which invisible character to use")
.addChoices(
{ name: "Hangul Filler", value: "hangul" },
{ name: "Thin Space", value: "thin" },
{ name: "Hair Space", value: "hair" },
)
)
)
cmd.addSubcommandGroup((g) => g
.setName("announcement")
.setDescription("Manage announcements")
.addSubcommand((s) => s
.setName("post")
.setDescription("Post an announcement")
.addStringOption((o) => o.setName("id").setDescription("Announcement ID").setRequired(true).setAutocomplete(true))
.addBooleanOption((o) => o.setName("force").setDescription("Repost even if already posted"))
)
.addSubcommand((s) => s
.setName("preview")
.setDescription("Preview an announcement")
.addStringOption((o) => o.setName("id").setDescription("Announcement ID").setRequired(true).setAutocomplete(true))
)
.addSubcommand((s) => s
.setName("list")
.setDescription("List all announcements")
)
)
return cmd;
}
export async function handleTgAdminCommand(interaction: ChatInputCommandInteraction): Promise<void> {
const group = interaction.options.getSubcommandGroup(false);
const sub = interaction.options.getSubcommand();
if (group === "user") {
if (sub === "map" || sub === "unmap" || sub === "list") {
return handleAdminUserMap(interaction);
}
}
if (group === "poll") {
if (sub === "fix-voter") return handleAdminPollFixVoter(interaction);
if (sub === "show-entry") return handleAdminPollShowEntry(interaction);
}
if (group === "updates") {
if (sub === "post") return UpdatesCommands.post(interaction);
if (sub === "preview") return UpdatesCommands.preview(interaction);
if (sub === "list") return UpdatesCommands.list(interaction);
}
if (group === null && sub === "score-inject") return ScoreInjectCommands.inject(interaction);
if (group === "result" && sub === "post") return ResultCommands.post(interaction);
if (group === "leaderboard" && sub === "post") return ResultCommands.leaderboardPost(interaction);
if (group === "leaderboard" && sub === "post-highlights") return ResultCommands.leaderboardHighlights(interaction);
if (group === "announcement" && sub === "post") return AnnouncementCommands.post(interaction);
if (group === "announcement" && sub === "preview") return AnnouncementCommands.preview(interaction);
if (group === "announcement" && sub === "list") return AnnouncementCommands.list(interaction);
if (group === null && sub === "test-align") return TestAlignCommands.handle(interaction);
if (group === null && sub === "reset-week") return ResetWeekCommands.handle(interaction);
}
| 1 | import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; |
| 2 | import { |
| 3 | handleAdminUserMap, |
| 4 | handleAdminPollFixVoter, |
| 5 | handleAdminPollShowEntry, |
| 6 | } from "@subcommands/admin/userMap"; |
| 7 | |
| 8 | import { UpdatesCommands } from "@subcommands/admin/updates"; |
| 9 | import { ScoreInjectCommands } from "@subcommands/admin/score-inject"; |
| 10 | import { ResultCommands } from "@subcommands/admin/result-post"; |
| 11 | import { TestAlignCommands } from "@subcommands/admin/test-align"; |
| 12 | import { ResetWeekCommands } from "../subcommands/admin/reset-week"; |
| 13 | import { AnnouncementCommands } from "../subcommands/admin/announcement"; |
| 14 | |
| 15 | export 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 | |
| 194 | export 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 | } |