add administration commands

This commit is contained in:
Matthieu Jolimaitre 2024-02-05 03:56:16 +01:00
parent c106b1f21d
commit 9dda450728
4 changed files with 241 additions and 39 deletions

View file

@ -1,5 +1,6 @@
import {
CacheType,
ChatInputCommandInteraction,
Client,
EmbedBuilder,
GatewayIntentBits,
@ -10,6 +11,9 @@ import {
SlashCommandStringOption,
} from "npm:discord.js@14.14.1";
import { channel, log_from, SimpleResult, split_promise } from "./utils.ts";
import { RuleSet } from "./rules.ts";
import { SlashCommandSubcommandBuilder } from "npm:discord.js@14.14.1";
import { SlashCommandRoleOption } from "npm:discord.js@14.14.1";
const log = log_from(import.meta.url);
/**
@ -19,14 +23,16 @@ export class EpitlsBot {
private bot;
private token;
private rest;
private rules;
private assoc_channel;
public constructor(bot_token: string) {
public constructor(bot_token: string, rules: RuleSet) {
this.token = bot_token;
const intents = [GatewayIntentBits.Guilds];
this.bot = new Client({ intents });
this.bot.on("interactionCreate", (interaction) => this.handle_interaction(interaction));
this.rest = new REST().setToken(bot_token);
this.rules = rules;
this.assoc_channel = channel<
{ cri_login: string; discord_user_id: string; callback: (success: SimpleResult) => void }
>();
@ -48,12 +54,16 @@ export class EpitlsBot {
* Assigns a discord role to a discord user.
*/
public async assign_role(user_id: string, guild_id: string, role_id: string) {
const guild = await this.bot.guilds.fetch(guild_id);
const member = await guild.members.fetch({ user: user_id });
const role = await guild.roles.fetch(role_id);
if (role === null) return console.error("Role", role_id, "not found in guild", guild_id);
member.roles.add(role_id);
log(`Assigned role '${role.name}' to user '${member.displayName}'.`);
try {
const guild = await this.bot.guilds.fetch(guild_id);
const member = await guild.members.fetch({ user: user_id });
const role = await guild.roles.fetch(role_id);
if (role === null) return console.error("Role", role_id, "not found in guild", guild_id);
member.roles.add(role_id);
log(`Assigned role '${role.name}' to user '${member.displayName}'.`);
} catch (_) {
//
}
}
/**
@ -80,30 +90,78 @@ export class EpitlsBot {
}
private async register_commands() {
const cmd = new SlashCommandBuilder()
const assoc_cmd = new SlashCommandBuilder()
.setName("associate")
.setDescription("Associates a cri account to your discord account.")
.addStringOption(
new SlashCommandStringOption()
.setName("cri_email")
.setDescription("claude.nougaro@epita.fr")
.setDescription("par exemple, claude.nougaro@epita.fr")
.setRequired(true),
).toJSON();
const rule_cmd = new SlashCommandBuilder()
.setName("rule")
.setDescription("Gestion des règles d'associations groupes cri / rôles.")
.setDefaultMemberPermissions(0x0000000000000008n)
.addSubcommand(
new SlashCommandSubcommandBuilder()
.setName("list")
.setDescription("Liste les règles d'association."),
)
.addSubcommand(
new SlashCommandSubcommandBuilder()
.setName("add")
.setDescription("Ajoute une règle d'association.")
.addStringOption(
new SlashCommandStringOption()
.setName("group")
.setDescription("Groupe CRI à associer.")
.setRequired(true),
)
.addRoleOption(
new SlashCommandRoleOption()
.setName("role")
.setDescription("Role à assigner.")
.setRequired(true),
),
)
.addSubcommand(
new SlashCommandSubcommandBuilder()
.setName("remove")
.setDescription("Retire une règle d'association.")
.addStringOption(
new SlashCommandStringOption()
.setName("group")
.setDescription("Groupe CRI à retirer.")
.setRequired(true),
),
)
.toJSON();
for (const guild_id of this.bot.guilds.cache.keys()) {
await this.rest.put(
Routes.applicationGuildCommands(this.bot.application!.id, guild_id),
{ body: [cmd] },
{ body: [assoc_cmd, rule_cmd] },
);
}
}
private async handle_interaction(interaction: Interaction<CacheType>) {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName != "associate") return;
if (interaction.commandName === "associate") await this.handle_command_associate(interaction);
if (interaction.commandName === "rule") {
const subcommand = interaction.options.getSubcommand();
if (subcommand === "list") await this.handle_command_rule_list(interaction);
if (subcommand === "add") await this.handle_command_rule_add(interaction);
if (subcommand === "remove") await this.handle_command_rule_remove(interaction);
}
}
private async handle_command_associate(interaction: ChatInputCommandInteraction<CacheType>) {
const email = interaction.options.getString("cri_email")!;
if (!email.endsWith("@epita.fr")) {
await interaction.reply(message_command_response_error("invalid cri email " + email));
await interaction.reply(message_command_response_assoc_error("invalid cri email " + email));
return;
}
const [cri_login] = email.split("@epita.fr");
@ -111,17 +169,70 @@ export class EpitlsBot {
const { promise, resolver } = split_promise<SimpleResult>();
this.assoc_channel.send({ cri_login, discord_user_id, callback: resolver });
await interaction.reply(message_command_response_sending_email(email));
await interaction.reply(message_command_response_assoc_pending(email));
log(`Started verification for discord id '${discord_user_id}' with cri login '${cri_login}'.`);
const result = await promise;
if (result === true) interaction.editReply(message_command_response_success());
else interaction.editReply(message_command_response_error(result));
if (result === true) interaction.editReply(message_command_response_assoc_success());
else interaction.editReply(message_command_response_assoc_error(result));
}
private async handle_command_rule_list(interaction: ChatInputCommandInteraction<CacheType>) {
const guild_id = interaction.guildId;
if (guild_id === null) {
await interaction.reply(
message_command_response_list_error("Cette commande peut être exécutée uniquement sur un serveur."),
);
return;
}
const rules = [] as GuildRule[];
for (const { group_id, role_id } of this.rules.roles_for_guild(guild_id)) {
const role = await interaction.guild!.roles.fetch(role_id);
const role_name = role?.name ?? "<DELETED>";
rules.push({ group_id, role_name });
}
await interaction.reply(message_command_response_rule_list_success(rules));
}
private async handle_command_rule_add(interaction: ChatInputCommandInteraction<CacheType>) {
const guild_id = interaction.guildId;
if (guild_id === null) {
await interaction.reply(
message_command_response_list_error("Cette commande peut être exécutée uniquement sur un serveur."),
);
return;
}
const group_id = interaction.options.getString("group", true);
const role = interaction.options.getRole("role", true);
// TODO : test if role exists
const role_id = role.id;
const role_name = role.name;
await this.rules.append_rule(group_id, { guild_id, role_id });
await interaction.reply(message_command_response_rule_add_success({ group_id, role_name }));
}
private async handle_command_rule_remove(interaction: ChatInputCommandInteraction<CacheType>) {
const guild_id = interaction.guildId;
if (guild_id === null) {
await interaction.reply(
message_command_response_list_error("Cette commande peut être exécutée uniquement sur un serveur."),
);
return;
}
const group_id = interaction.options.getString("group", true);
const removed = await this.rules.remove_rules(group_id, guild_id);
const rules = [] as GuildRule[];
for (const { group_id, target_role: { role_id } } of removed) {
const role = await interaction.guild!.roles.fetch(role_id);
const role_name = role?.name ?? "<DELETED>";
rules.push({ group_id, role_name });
}
await interaction.reply(message_command_response_rule_remove_success(rules));
}
}
function message_command_response_sending_email(email: string) {
function message_command_response_assoc_pending(email: string) {
const embed = new EmbedBuilder()
.setTitle("`🟡`| Vérification")
.setTitle("`🟡`| Association")
.setDescription(`
Un lien de vérification a é envoyé par e-mail à \`${email}\`.
Le lien expirera dans 5 minutes.
@ -130,16 +241,53 @@ Le lien expirera dans 5 minutes.
return { embeds: [embed] };
}
function message_command_response_error(msg: string) {
function message_command_response_assoc_error(msg: string) {
const embed = new EmbedBuilder()
.setTitle("`❌`| Vérification")
.setDescription(`Échec de la vérification :\n\`${msg}\``);
.setTitle("`❌`| Association")
.setDescription(`Échec de l'association :\n\`${msg}\``);
return { embeds: [embed] };
}
function message_command_response_success() {
function message_command_response_assoc_success() {
const embed = new EmbedBuilder()
.setTitle("`✅`| Vérification")
.setTitle("`✅`| Association")
.setDescription("Votre e-mail CRI a bien été associé à ce compte Discord.");
return { embeds: [embed] };
}
function message_command_response_list_error(msg: string) {
const embed = new EmbedBuilder()
.setTitle("`❌`| Liste des règles")
.setDescription(`Échec :\n\`${msg}\``);
return { embeds: [embed] };
}
type GuildRule = { group_id: string; role_name: string };
function message_command_response_rule_list_success(rules: GuildRule[]) {
const rule_list = rules.map((r) => `- \`${r.group_id}\`\`@${r.role_name}\``).join("\n");
const embed = new EmbedBuilder()
.setTitle("`📚️`| Liste des règles")
.setDescription(`Règles d'associations groupe / rôle.
${rule_list}
`);
return { embeds: [embed] };
}
function message_command_response_rule_add_success(rule: GuildRule) {
const embed = new EmbedBuilder()
.setTitle("`✅`| Règle ajouté")
.setDescription(`Règle d'association ajoutée :
- \`${rule.group_id}\`\`@${rule.role_name}\`
`);
return { embeds: [embed] };
}
function message_command_response_rule_remove_success(rules: GuildRule[]) {
const rule_list = rules.map((r) => `- \`${r.group_id}\`\`@${r.role_name}\``).join("\n");
const embed = new EmbedBuilder()
.setTitle("`✅`| Règle ajouté")
.setDescription(`Règle d'association retirées :
${rule_list}
`);
return { embeds: [embed] };
}