tg-bot-ts/src/commands/tgConfig.ts

211 lines
No EOL
14 KiB
TypeScript

import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
import { cfg, setCfg, resetCfg } from "../systems/config";
import { hasOfficerRole } from "../systems/users";
import { replyAndDelete } from "../utils";
import { Nation } from "@types";
export function buildTgConfigCommand(): SlashCommandBuilder {
const cmd = new SlashCommandBuilder()
.setName("tg-config")
.setDescription("Configure the TG bot");
const strOpt = (name: string, desc: string, req = true) =>
(o: any) => o.setName(name).setDescription(desc).setRequired(req);
const decisionOpt = (o: any) => o.setName("decision").setDescription("yes or no").setRequired(true)
.addChoices({ name: "Yes", value: "yes" }, { name: "No", value: "no" });
const nationOpt = (o: any) => o.setName("nation").setDescription("Nation").setRequired(true)
.addChoices({ name: "Capella", value: Nation.Capella }, { name: "Procyon", value: Nation.Procyon });
const roleOpt = strOpt("role", "Role name");
const rolesOpt = strOpt("roles", "Comma-separated role names");
// ── message group ──────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("message")
.setDescription("Configure bot messages")
.addSubcommand((s) => s.setName("set-lock").setDescription("Set default lock message")
.addStringOption(strOpt("message", "New lock message")))
.addSubcommand((s) => s.setName("reset-lock").setDescription("Reset lock message to default"))
.addSubcommand((s) => s.setName("set-confirm").setDescription("Set default confirm message")
.addStringOption(decisionOpt)
.addStringOption(strOpt("message", "New confirm message")))
.addSubcommand((s) => s.setName("reset-confirm").setDescription("Reset confirm message to default")
.addStringOption(decisionOpt))
);
// ── roles group ────────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("roles")
.setDescription("Configure bot roles")
.addSubcommand((s) => s.setName("set-officer").setDescription("Set officer roles (comma-separated)").addStringOption(rolesOpt))
.addSubcommand((s) => s.setName("add-officer").setDescription("Add an officer role").addStringOption(roleOpt))
.addSubcommand((s) => s.setName("remove-officer").setDescription("Remove an officer role").addStringOption(roleOpt))
.addSubcommand((s) => s.setName("reset-officer").setDescription("Reset officer roles to default"))
.addSubcommand((s) => s.setName("set-config").setDescription("Set config roles (comma-separated)").addStringOption(rolesOpt))
.addSubcommand((s) => s.setName("add-config").setDescription("Add a config role").addStringOption(roleOpt))
.addSubcommand((s) => s.setName("remove-config").setDescription("Remove a config role").addStringOption(roleOpt))
.addSubcommand((s) => s.setName("reset-config").setDescription("Reset config roles to default"))
.addSubcommand((s) => s.setName("set-tag").setDescription("Set tag roles (comma-separated)").addStringOption(rolesOpt))
.addSubcommand((s) => s.setName("add-tag").setDescription("Add a tag role").addStringOption(roleOpt))
.addSubcommand((s) => s.setName("remove-tag").setDescription("Remove a tag role").addStringOption(roleOpt))
.addSubcommand((s) => s.setName("reset-tag").setDescription("Reset tag roles to default"))
);
// ── channel group ──────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("channel")
.setDescription("Configure bot channels")
.addSubcommand((s) => s.setName("set-poll").setDescription("Set poll channel")
.addChannelOption((o) => o.setName("channel").setDescription("Channel").setRequired(true)))
.addSubcommand((s) => s.setName("set-results").setDescription("Set results channel")
.addChannelOption((o) => o.setName("channel").setDescription("Channel").setRequired(true)))
.addSubcommand((s) => s.setName("set-score").setDescription("Set score channel")
.addChannelOption((o) => o.setName("channel").setDescription("Channel").setRequired(true)))
);
// ── slot group ─────────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("slot")
.setDescription("Configure TG slots")
.addSubcommand((s) => s.setName("add").setDescription("Add a TG slot")
.addIntegerOption((o) => o.setName("hour").setDescription("TG hour (0-23)").setRequired(true))
.addStringOption(strOpt("poll_opens", "Poll open time e.g. 10:00")))
.addSubcommand((s) => s.setName("remove").setDescription("Remove a TG slot")
.addIntegerOption((o) => o.setName("hour").setDescription("TG hour").setRequired(true)))
);
// ── wrank group ────────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("wrank")
.setDescription("Configure W.Rank settings")
.addSubcommand((s) => s.setName("set-goal").setDescription("Set W.Rank TG goal")
.addIntegerOption((o) => o.setName("goal").setDescription("Number of TGs").setRequired(true)))
.addSubcommand((s) => s.setName("set-post-on-reset").setDescription("Post leaderboard on weekly reset?")
.addBooleanOption((o) => o.setName("enabled").setDescription("true/false").setRequired(true)))
);
// ── tg group ───────────────────────────────────────────────────────────────
cmd.addSubcommandGroup((g) => g
.setName("tg")
.setDescription("Configure TG settings")
.addSubcommand((s) => s.setName("set-score-window").setDescription("Set score submission window (hours)")
.addNumberOption((o) => o.setName("hours").setDescription("Hours").setRequired(true)))
.addSubcommand((s) => s.setName("set-duration").setDescription("Set TG duration (minutes)")
.addIntegerOption((o) => o.setName("minutes").setDescription("Minutes").setRequired(true)))
.addSubcommand((s) => s.setName("set-no-display").setDescription("Where to show No voters")
.addStringOption((o) => o.setName("mode").setDescription("inline or messages").setRequired(true)
.addChoices({ name: "Inline under nation", value: "inline" }, { name: "Messages section only", value: "messages" })))
.addSubcommand((s) => s.setName("set-nation-source").setDescription("Set source of truth nation")
.addStringOption(nationOpt))
);
return cmd;
}
export async function handleTgConfigCommand(interaction: ChatInputCommandInteraction): Promise<void> {
const member = await interaction.guild!.members.fetch(interaction.user.id);
if (!hasOfficerRole(member, cfg("configRoles"))) {
return void replyAndDelete(interaction, "❌ You don't have permission to use this command.");
}
const group = interaction.options.getSubcommandGroup(true);
const sub = interaction.options.getSubcommand();
const roleSubcommand = (cfgKey: "officerRoles" | "configRoles" | "tagRoles", action: string) => {
if (action === "set") {
const roles = interaction.options.getString("roles", true).split(",").map((r) => r.trim()).filter(Boolean);
setCfg(cfgKey, roles);
return void replyAndDelete(interaction, `✅ Roles updated: ${roles.join(", ")}`);
}
if (action === "add") {
const role = interaction.options.getString("role", true).trim();
const roles = [...new Set([...cfg(cfgKey), role])];
setCfg(cfgKey, roles);
return void replyAndDelete(interaction, `✅ Added **${role}**. Current: ${roles.join(", ")}`);
}
if (action === "remove") {
const role = interaction.options.getString("role", true).trim();
const roles = cfg(cfgKey).filter((r: string) => r !== role);
setCfg(cfgKey, roles);
return void replyAndDelete(interaction, `✅ Removed **${role}**. Current: ${roles.join(", ")}`);
}
if (action === "reset") {
resetCfg(cfgKey);
return void replyAndDelete(interaction, `✅ Roles reset to default.`);
}
};
// ── message ────────────────────────────────────────────────────────────────
if (group === "message") {
if (sub === "set-lock") { setCfg("lockMessage", interaction.options.getString("message", true)); return void replyAndDelete(interaction, "✅ Lock message updated."); }
if (sub === "reset-lock") { resetCfg("lockMessage"); return void replyAndDelete(interaction, "✅ Lock message reset."); }
if (sub === "set-confirm") {
const d = interaction.options.getString("decision", true);
setCfg(d === "yes" ? "confirmYesMessage" : "confirmNoMessage", interaction.options.getString("message", true));
return void replyAndDelete(interaction, `✅ Confirm ${d} message updated.`);
}
if (sub === "reset-confirm") {
const d = interaction.options.getString("decision", true);
resetCfg(d === "yes" ? "confirmYesMessage" : "confirmNoMessage");
return void replyAndDelete(interaction, `✅ Confirm ${d} message reset.`);
}
}
// ── roles ──────────────────────────────────────────────────────────────────
if (group === "roles") {
if (sub === "set-officer") return roleSubcommand("officerRoles", "set");
if (sub === "add-officer") return roleSubcommand("officerRoles", "add");
if (sub === "remove-officer") return roleSubcommand("officerRoles", "remove");
if (sub === "reset-officer") return roleSubcommand("officerRoles", "reset");
if (sub === "set-config") return roleSubcommand("configRoles", "set");
if (sub === "add-config") return roleSubcommand("configRoles", "add");
if (sub === "remove-config") return roleSubcommand("configRoles", "remove");
if (sub === "reset-config") return roleSubcommand("configRoles", "reset");
if (sub === "set-tag") return roleSubcommand("tagRoles", "set");
if (sub === "add-tag") return roleSubcommand("tagRoles", "add");
if (sub === "remove-tag") return roleSubcommand("tagRoles", "remove");
if (sub === "reset-tag") return roleSubcommand("tagRoles", "reset");
}
// ── channel ────────────────────────────────────────────────────────────────
if (group === "channel") {
if (sub === "set-poll") { setCfg("pollChannelId", interaction.options.getChannel("channel", true).id); return void replyAndDelete(interaction, "✅ Poll channel updated."); }
if (sub === "set-results") { setCfg("resultsChannelId", interaction.options.getChannel("channel", true).id); return void replyAndDelete(interaction, "✅ Results channel updated."); }
if (sub === "set-score") { setCfg("scoreChannelId", interaction.options.getChannel("channel", true).id); return void replyAndDelete(interaction, "✅ Score channel updated."); }
}
// ── slot ───────────────────────────────────────────────────────────────────
if (group === "slot") {
if (sub === "add") {
const hour = interaction.options.getInteger("hour", true);
const pollOpens = interaction.options.getString("poll_opens", true);
const slots = cfg("slots");
if (slots.some((s) => s.tgHour === hour)) return void replyAndDelete(interaction, `❌ Slot ${hour}:00 already exists.`);
slots.push({ tgHour: hour, pollOpens, closesAfter: cfg("tgDurationMinutes"), active: true });
setCfg("slots", slots);
return void replyAndDelete(interaction, `✅ Slot ${hour}:00 added (poll opens at ${pollOpens}).`);
}
if (sub === "remove") {
const hour = interaction.options.getInteger("hour", true);
const slots = cfg("slots").filter((s) => s.tgHour !== hour);
setCfg("slots", slots);
return void replyAndDelete(interaction, `✅ Slot ${hour}:00 removed.`);
}
}
// ── wrank ──────────────────────────────────────────────────────────────────
if (group === "wrank") {
if (sub === "set-goal") { setCfg("wRankGoal", interaction.options.getInteger("goal", true)); return void replyAndDelete(interaction, "✅ W.Rank goal updated."); }
if (sub === "set-post-on-reset") { setCfg("wRankPostOnReset", interaction.options.getBoolean("enabled", true)); return void replyAndDelete(interaction, "✅ W.Rank post on reset updated."); }
}
// ── tg ─────────────────────────────────────────────────────────────────────
if (group === "tg") {
if (sub === "set-score-window") { setCfg("scoreWindowHours", interaction.options.getNumber("hours", true)); return void replyAndDelete(interaction, "✅ Score window updated."); }
if (sub === "set-duration") { setCfg("tgDurationMinutes", interaction.options.getInteger("minutes", true)); return void replyAndDelete(interaction, "✅ TG duration updated."); }
if (sub === "set-no-display") { setCfg("showNoInNationField" as any, interaction.options.getString("mode", true) === "inline"); return void replyAndDelete(interaction, "✅ No voter display updated."); }
if (sub === "set-nation-source"){ setCfg("nationSource", interaction.options.getString("nation", true) as Nation); return void replyAndDelete(interaction, "✅ Nation source updated."); }
}
}