350 lines
23 KiB
TypeScript
350 lines
23 KiB
TypeScript
import {
|
|
ChatInputCommandInteraction,
|
|
SlashCommandBuilder,
|
|
REST,
|
|
Routes,
|
|
} from "discord.js";
|
|
import { cfg } from "../systems/config";
|
|
import { hasOfficerRole } from "../systems/users";
|
|
|
|
// Poll subcommands
|
|
import { handleStart } from "../subcommands/poll/start";
|
|
import { handleLock } from "../subcommands/poll/lock";
|
|
import { handleUnlock } from "../subcommands/poll/unlock";
|
|
import { handleConfirm } from "../subcommands/poll/confirm";
|
|
import { handleStatus } from "../subcommands/poll/status";
|
|
import { handleReload } from "../subcommands/poll/reload";
|
|
import { handleSetMessage, handleClearMessage, handleSetEphemeral, handleClearEphemeral } from "../subcommands/poll/setMessage";
|
|
import { handleInject, handleRemoveVote } from "../subcommands/poll/inject";
|
|
import { handleSeed } from "../subcommands/poll/seed";
|
|
import { handlePurge } from "../subcommands/poll/purge";
|
|
import { handleImpersonate } from "../subcommands/impersonate";
|
|
|
|
// Char subcommands (borrow / sharing system)
|
|
import { handleCharBorrow } from "../subcommands/char/borrow";
|
|
import { handleCharAccept } from "../subcommands/char/accept";
|
|
import { handleCharDecline } from "../subcommands/char/decline";
|
|
import { handleCharShare, handleCharUnshare } from "../subcommands/char/share";
|
|
|
|
// Score subcommands
|
|
import { handleScoreSet } from "../subcommands/score/set";
|
|
import { handleScoreGet } from "../subcommands/score/get";
|
|
|
|
// Rank subcommands
|
|
import { handleRankGet } from "../subcommands/rank/get";
|
|
import { handleRankPost } from "../subcommands/rank/post";
|
|
|
|
// Result subcommands
|
|
import { handleResultSet } from "../subcommands/result/set";
|
|
import { handleResultView } from "../subcommands/result/view";
|
|
import { handleResultPost } from "../subcommands/result/post";
|
|
|
|
// Bringer subcommands
|
|
import { handleBringerSet } from "../subcommands/bringer/set";
|
|
import { handleBringerClear } from "../subcommands/bringer/clear";
|
|
|
|
// Other
|
|
import { handleSwitch } from "../subcommands/switch";
|
|
import { handleHistory } from "../subcommands/history";
|
|
|
|
// Import char handlers here to keep tg.ts clean
|
|
import { handleCharAdd } from "../subcommands/char/add";
|
|
import { handleCharRemove } from "../subcommands/char/remove";
|
|
import { handleCharSetActive } from "../subcommands/char/setActive";
|
|
import { handleCharSetNation } from "../subcommands/char/setNation";
|
|
import { handleCharSetStats } from "../subcommands/char/setStats";
|
|
import { handleCharActive } from "../subcommands/char/active";
|
|
|
|
export function buildTgCommand(): SlashCommandBuilder {
|
|
const cmd = new SlashCommandBuilder()
|
|
.setName("tg")
|
|
.setDescription("TG planning and tracking");
|
|
|
|
// ── poll group ─────────────────────────────────────────────────────────────
|
|
cmd.addSubcommandGroup((g) => g
|
|
.setName("poll")
|
|
.setDescription("Manage the TG poll")
|
|
.addSubcommand((s) => s.setName("start").setDescription("Post a fresh TG poll")
|
|
.addStringOption((o) => o.setName("slot").setDescription("TG hour (e.g. 20, 22)").setRequired(false))
|
|
)
|
|
.addSubcommand((s) => s.setName("lock").setDescription("Lock the active poll")
|
|
.addStringOption((o) => o.setName("message").setDescription("One-time lock message").setRequired(false))
|
|
.addBooleanOption((o) => o.setName("simulate_close").setDescription("Simulate poll lock").setRequired(false))
|
|
)
|
|
.addSubcommand((s) => s.setName("unlock").setDescription("Unlock the active poll"))
|
|
.addSubcommand((s) => s.setName("confirm").setDescription("Confirm whether TG is happening")
|
|
.addStringOption((o) => o.setName("decision").setDescription("yes or no").setRequired(true)
|
|
.addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
|
|
.addStringOption((o) => o.setName("message").setDescription("One-time confirm message").setRequired(false))
|
|
.addBooleanOption((o) => o.setName("tag").setDescription("Tag configured roles?").setRequired(false))
|
|
)
|
|
.addSubcommand((s) => s.setName("reload").setDescription("Reload messages and emojis from disk")
|
|
.addStringOption(opt =>
|
|
opt.setName("target")
|
|
.setDescription("What to reload")
|
|
.setRequired(false)
|
|
.addChoices(
|
|
{ name: "All", value: "all" },
|
|
{ name: "Config", value: "config" },
|
|
{ name: "Messages", value: "messages" },
|
|
{ name: "Emojis", value: "emojis" },
|
|
{ name: "Characters", value: "characters" },
|
|
{ name: "W.Rank", value: "wrank" },
|
|
{ name: "Poll", value: "poll" },
|
|
)
|
|
)
|
|
)
|
|
.addSubcommand((s) => s.setName("status").setDescription("Show current poll and config status"))
|
|
.addSubcommand((s) => s.setName("set-message").setDescription("Set public message override for a user")
|
|
.addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(true)
|
|
.addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
|
|
.addStringOption((o) => o.setName("message").setDescription("Message to show").setRequired(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("clear-message").setDescription("Clear public message override")
|
|
.addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(false)
|
|
.addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("set-ephemeral").setDescription("Set ephemeral message override for a user")
|
|
.addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(true)
|
|
.addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
|
|
.addStringOption((o) => o.setName("message").setDescription("Message to show").setRequired(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("clear-ephemeral").setDescription("Clear ephemeral message override")
|
|
.addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(false)
|
|
.addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" }))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("inject").setDescription("Inject a vote for a registered user")
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("vote_type").setDescription("yes or no").setRequired(true)
|
|
.addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" })))
|
|
.addSubcommand((s) => s.setName("remove-vote").setDescription("Remove a vote for a registered user")
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key").setRequired(true).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("purge").setDescription("Delete all bot messages from the poll channel"))
|
|
.addSubcommand((s) => s.setName("seed").setDescription("Inject all registered players as Yes votes for layout testing"))
|
|
);
|
|
|
|
// ── score group ────────────────────────────────────────────────────────────
|
|
cmd.addSubcommandGroup((g) => g
|
|
.setName("score")
|
|
.setDescription("Score management")
|
|
.addSubcommand((s) => s.setName("set").setDescription("Submit a score")
|
|
.addIntegerOption((o) => o.setName("pts").setDescription("Points").setRequired(true))
|
|
.addStringOption((o) => o.setName("slot").setDescription("TG hour (e.g. 20, 8pm, midnight)").setRequired(false))
|
|
.addIntegerOption((o) => o.setName("k").setDescription("Kills").setRequired(false))
|
|
.addIntegerOption((o) => o.setName("d").setDescription("Deaths").setRequired(false))
|
|
.addIntegerOption((o) => o.setName("atk").setDescription("Attack score").setRequired(false))
|
|
.addIntegerOption((o) => o.setName("def").setDescription("Defense score").setRequired(false))
|
|
.addIntegerOption((o) => o.setName("heal").setDescription("Healing score (FA only)").setRequired(false))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("get").setDescription("View a score")
|
|
.addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
);
|
|
|
|
// ── rank group ─────────────────────────────────────────────────────────────
|
|
cmd.addSubcommandGroup((g) => g
|
|
.setName("rank")
|
|
.setDescription("W.Rank management")
|
|
.addSubcommand((s) => s.setName("get").setDescription("View W.Rank")
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("post").setDescription("Post leaderboard publicly (officer only)"))
|
|
);
|
|
|
|
// ── result group ───────────────────────────────────────────────────────────
|
|
cmd.addSubcommandGroup((g) => g
|
|
.setName("result")
|
|
.setDescription("TG result management")
|
|
.addSubcommand((s) => s.setName("set").setDescription("Set nation K/D (officer only)")
|
|
.addStringOption((o) => o.setName("nation").setDescription("Source nation").setRequired(true)
|
|
.addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" }))
|
|
.addIntegerOption((o) => o.setName("kills").setDescription("Kills").setRequired(true))
|
|
.addIntegerOption((o) => o.setName("deaths").setDescription("Deaths").setRequired(true))
|
|
.addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false)))
|
|
.addSubcommand((s) => s.setName("view").setDescription("View result for a slot")
|
|
.addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false)))
|
|
.addSubcommand((s) => s.setName("post").setDescription("Post result publicly (officer only)")
|
|
.addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false)))
|
|
);
|
|
|
|
// ── bringer group ──────────────────────────────────────────────────────────
|
|
cmd.addSubcommandGroup((g) => g
|
|
.setName("bringer")
|
|
.setDescription("Bringer management (officer only)")
|
|
.addSubcommand((s) => s.setName("set").setDescription("Manually set Bringer")
|
|
.addStringOption((o) => o.setName("nation").setDescription("Nation").setRequired(true)
|
|
.addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" }))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key").setRequired(true).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("clear").setDescription("Clear Bringer override")
|
|
.addStringOption((o) => o.setName("nation").setDescription("Nation").setRequired(true)
|
|
.addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" })))
|
|
);
|
|
|
|
// ── switch ─────────────────────────────────────────────────────────────────
|
|
cmd.addSubcommand((s) => s.setName("switch").setDescription("Switch active character")
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
);
|
|
|
|
// ── char group ─────────────────────────────────────────────────────────────
|
|
cmd.addSubcommandGroup((g) => g
|
|
.setName("char")
|
|
.setDescription("Character management")
|
|
.addSubcommand((s) => s.setName("add").setDescription("Add a character")
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true))
|
|
.addStringOption((o) => o.setName("class").setDescription("Class").setRequired(true)
|
|
.addChoices(
|
|
{ name: "Blader (BL)", value: "BL" },
|
|
{ name: "Force Blader (FB)", value: "FB" },
|
|
{ name: "Force Shielder (FS)", value: "FS" },
|
|
{ name: "Force Archer (FA)", value: "FA" },
|
|
{ name: "Force Gunner (FG)", value: "FG" },
|
|
{ name: "Gladiator (GL)", value: "GL" },
|
|
{ name: "Dark Mage (DM)", value: "DM" },
|
|
{ name: "Wizard (WI)", value: "WI" },
|
|
{ name: "Warrior (WA)", value: "WA" },
|
|
))
|
|
.addIntegerOption((o) => o.setName("level").setDescription("Level").setRequired(true))
|
|
.addStringOption((o) => o.setName("nation").setDescription("Nation").setRequired(true)
|
|
.addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" }))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("remove").setDescription("Remove a character")
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("set-active").setDescription("Set active character")
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("set-nation").setDescription("Change a character's nation")
|
|
.addStringOption((o) => o.setName("nation").setDescription("Nation").setRequired(true)
|
|
.addChoices({ name: "Capella", value: "Capella" }, { name: "Procyon", value: "Procyon" }))
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name (defaults to active)").setRequired(false).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("set-stats").setDescription("Set character combat stats")
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name (defaults to active)").setRequired(false))
|
|
.addIntegerOption((o) => o.setName("atk").setDescription("Attack score").setRequired(false))
|
|
.addIntegerOption((o) => o.setName("def").setDescription("Defense score").setRequired(false))
|
|
.addIntegerOption((o) => o.setName("heal").setDescription("Healing score").setRequired(false))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("borrow").setDescription("Request to borrow a character for this session")
|
|
.addStringOption((o) => o.setName("owner").setDescription("Owner's usermap key").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Grant to this user (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("accept").setDescription("Accept a borrow request")
|
|
.addStringOption((o) => o.setName("name").setDescription("Requester's usermap key").setRequired(true).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("decline").setDescription("Decline a borrow request")
|
|
.addStringOption((o) => o.setName("name").setDescription("Requester's usermap key").setRequired(true).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("share").setDescription("Permanently share a character")
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key to share with").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("owner").setDescription("Owner's usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("unshare").setDescription("Revoke permanent character share")
|
|
.addStringOption((o) => o.setName("char_name").setDescription("Character name").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key to revoke").setRequired(true).setAutocomplete(true))
|
|
.addStringOption((o) => o.setName("owner").setDescription("Owner's usermap key (officer only)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
.addSubcommand((s) => s.setName("active").setDescription("Check active character for a user")
|
|
.addStringOption((o) => o.setName("name").setDescription("Usermap key (officer: check others)").setRequired(false).setAutocomplete(true))
|
|
)
|
|
);
|
|
|
|
// ── history ────────────────────────────────────────────────────────────────
|
|
cmd.addSubcommand((s) => s.setName("history").setDescription("View TG history (officer only)")
|
|
.addStringOption((o) => o.setName("date").setDescription("Date (YYYY-MM-DD)").setRequired(false))
|
|
.addStringOption((o) => o.setName("slot").setDescription("TG hour").setRequired(false))
|
|
);
|
|
|
|
// ── impersonate ────────────────────────────────────────────────────────────────
|
|
cmd.addSubcommand((s) => s.setName("impersonate").setDescription("Impersonate a registered user for testing (officer only)"));
|
|
|
|
return cmd;
|
|
}
|
|
|
|
export async function handleTgCommand(interaction: ChatInputCommandInteraction): Promise<void> {
|
|
const group = interaction.options.getSubcommandGroup(false);
|
|
const sub = interaction.options.getSubcommand();
|
|
const member = await interaction.guild!.members.fetch(interaction.user.id);
|
|
const isOfficer = hasOfficerRole(member, cfg("officerRoles"));
|
|
|
|
// Officer-only commands
|
|
const officerOnlyGroups = ["poll", "result", "bringer"];
|
|
const officerOnlySubs = ["history"];
|
|
const officerOnlyRankSubs = ["post"];
|
|
|
|
if (group && officerOnlyGroups.includes(group) && !isOfficer) {
|
|
return void interaction.reply({ content: "❌ You don't have permission to use this command.", ephemeral: true });
|
|
}
|
|
if (!group && officerOnlySubs.includes(sub) && !isOfficer) {
|
|
return void interaction.reply({ content: "❌ You don't have permission to use this command.", ephemeral: true });
|
|
}
|
|
if (group === "rank" && officerOnlyRankSubs.includes(sub) && !isOfficer) {
|
|
return void interaction.reply({ content: "❌ You don't have permission to use this command.", ephemeral: true });
|
|
}
|
|
|
|
// Route
|
|
if (group === "poll") {
|
|
if (sub === "start") return handleStart(interaction);
|
|
if (sub === "lock") return handleLock(interaction);
|
|
if (sub === "unlock") return handleUnlock(interaction);
|
|
if (sub === "confirm") return handleConfirm(interaction);
|
|
if (sub === "reload") return handleReload(interaction);
|
|
if (sub === "status") return handleStatus(interaction);
|
|
if (sub === "set-message") return handleSetMessage(interaction);
|
|
if (sub === "clear-message") return handleClearMessage(interaction);
|
|
if (sub === "set-ephemeral") return handleSetEphemeral(interaction);
|
|
if (sub === "clear-ephemeral") return handleClearEphemeral(interaction);
|
|
if (sub === "inject") return handleInject(interaction);
|
|
if (sub === "remove-vote") return handleRemoveVote(interaction);
|
|
if (sub === "purge") return handlePurge(interaction);
|
|
if (sub === "seed") return handleSeed(interaction);
|
|
}
|
|
if (group === "score") {
|
|
if (sub === "set") return handleScoreSet(interaction);
|
|
if (sub === "get") return handleScoreGet(interaction);
|
|
}
|
|
if (group === "rank") {
|
|
if (sub === "get") return handleRankGet(interaction);
|
|
if (sub === "post") return handleRankPost(interaction);
|
|
}
|
|
if (group === "result") {
|
|
if (sub === "set") return handleResultSet(interaction);
|
|
if (sub === "view") return handleResultView(interaction);
|
|
if (sub === "post") return handleResultPost(interaction);
|
|
}
|
|
if (group === "bringer") {
|
|
if (sub === "set") return handleBringerSet(interaction);
|
|
if (sub === "clear") return handleBringerClear(interaction);
|
|
}
|
|
if (group === "char") {
|
|
if (sub === "add") return handleCharAdd(interaction);
|
|
if (sub === "remove") return handleCharRemove(interaction);
|
|
if (sub === "set-active") return handleCharSetActive(interaction);
|
|
if (sub === "set-nation") return handleCharSetNation(interaction);
|
|
if (sub === "set-stats") return handleCharSetStats(interaction);
|
|
if (sub === "borrow") return handleCharBorrow(interaction);
|
|
if (sub === "accept") return handleCharAccept(interaction);
|
|
if (sub === "decline") return handleCharDecline(interaction);
|
|
if (sub === "share") return handleCharShare(interaction);
|
|
if (sub === "unshare") return handleCharUnshare(interaction);
|
|
if (sub === "active") return handleCharActive(interaction);
|
|
}
|
|
if (!group && sub === "switch") return handleSwitch(interaction);
|
|
if (!group && sub === "history") return handleHistory(interaction);
|
|
if (!group && sub === "impersonate") return handleImpersonate(interaction);
|
|
}
|