add export command
This commit is contained in:
parent
4355b49e6f
commit
cbfed03f66
3 changed files with 78 additions and 5 deletions
61
src/bot.ts
61
src/bot.ts
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
AttachmentBuilder,
|
||||||
CacheType,
|
CacheType,
|
||||||
ChatInputCommandInteraction,
|
ChatInputCommandInteraction,
|
||||||
Client,
|
Client,
|
||||||
|
@ -17,6 +18,7 @@ import {
|
||||||
} from "npm:discord.js@14.14.1";
|
} from "npm:discord.js@14.14.1";
|
||||||
import { RuleSet } from "./rules.ts";
|
import { RuleSet } from "./rules.ts";
|
||||||
import { channel, log_from, SimpleResult, split_promise, try_ } from "./utils.ts";
|
import { channel, log_from, SimpleResult, split_promise, try_ } from "./utils.ts";
|
||||||
|
import { StoredUser } from "./state.ts";
|
||||||
const log = log_from(import.meta.url);
|
const log = log_from(import.meta.url);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +38,7 @@ export class EpitlsBot {
|
||||||
this.bot.on("interactionCreate", (interaction) => this.handle_interaction(interaction));
|
this.bot.on("interactionCreate", (interaction) => this.handle_interaction(interaction));
|
||||||
this.rest = new REST().setToken(bot_token);
|
this.rest = new REST().setToken(bot_token);
|
||||||
this.rules = rules;
|
this.rules = rules;
|
||||||
this.assoc_channel = channel<Association | Dissociation>();
|
this.assoc_channel = channel<Association | Dissociation | Export>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,6 +137,12 @@ export class EpitlsBot {
|
||||||
.setRequired(true),
|
.setRequired(true),
|
||||||
).toJSON();
|
).toJSON();
|
||||||
|
|
||||||
|
const export_cmd = new SlashCommandBuilder()
|
||||||
|
.setName("export")
|
||||||
|
.setDescription("Exporte les associations des login cri vers les compte discord.")
|
||||||
|
.setDefaultMemberPermissions(ADMIN_PERMISSIONS)
|
||||||
|
.toJSON();
|
||||||
|
|
||||||
const rule_cmd = new SlashCommandBuilder()
|
const rule_cmd = new SlashCommandBuilder()
|
||||||
.setName("rule")
|
.setName("rule")
|
||||||
.setDescription("Gestion des règles d'associations groupes cri / rôles.")
|
.setDescription("Gestion des règles d'associations groupes cri / rôles.")
|
||||||
|
@ -183,7 +191,7 @@ export class EpitlsBot {
|
||||||
for (const guild_id of this.bot.guilds.cache.keys()) {
|
for (const guild_id of this.bot.guilds.cache.keys()) {
|
||||||
await this.rest.put(
|
await this.rest.put(
|
||||||
Routes.applicationGuildCommands(this.bot.application!.id, guild_id),
|
Routes.applicationGuildCommands(this.bot.application!.id, guild_id),
|
||||||
{ body: [assoc_cmd, dissoc_cmd, rule_cmd] },
|
{ body: [assoc_cmd, dissoc_cmd, rule_cmd, export_cmd] },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,6 +200,7 @@ export class EpitlsBot {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
if (!interaction.isChatInputCommand()) return;
|
||||||
if (interaction.commandName === "associate") await this.handle_command_associate(interaction);
|
if (interaction.commandName === "associate") await this.handle_command_associate(interaction);
|
||||||
if (interaction.commandName === "dissociate") await this.handle_command_dissociate(interaction);
|
if (interaction.commandName === "dissociate") await this.handle_command_dissociate(interaction);
|
||||||
|
if (interaction.commandName === "export") await this.handle_command_export(interaction);
|
||||||
if (interaction.commandName === "rule") {
|
if (interaction.commandName === "rule") {
|
||||||
const subcommand = interaction.options.getSubcommand();
|
const subcommand = interaction.options.getSubcommand();
|
||||||
if (subcommand === "list") await this.handle_command_rule_list(interaction);
|
if (subcommand === "list") await this.handle_command_rule_list(interaction);
|
||||||
|
@ -228,6 +237,17 @@ export class EpitlsBot {
|
||||||
await interaction.reply(message_command_response_dissoc_success());
|
await interaction.reply(message_command_response_dissoc_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handle_command_export(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||||
|
const { promise, resolver } = split_promise<SimpleResult<StoredUser[]>>();
|
||||||
|
this.assoc_channel.send({ kind: "export", callback: resolver });
|
||||||
|
const result = await promise;
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
const csv = await this.users_to_csv(result);
|
||||||
|
const response = message_command_response_export_success(result.length, csv);
|
||||||
|
await interaction.reply(response);
|
||||||
|
} else await interaction.reply(message_command_response_export_error(result));
|
||||||
|
}
|
||||||
|
|
||||||
private async handle_command_rule_list(interaction: ChatInputCommandInteraction<CacheType>) {
|
private async handle_command_rule_list(interaction: ChatInputCommandInteraction<CacheType>) {
|
||||||
const guild_id = interaction.guildId;
|
const guild_id = interaction.guildId;
|
||||||
if (guild_id === null) {
|
if (guild_id === null) {
|
||||||
|
@ -281,6 +301,15 @@ export class EpitlsBot {
|
||||||
}
|
}
|
||||||
await interaction.reply(message_command_response_rule_remove_success(rules));
|
await interaction.reply(message_command_response_rule_remove_success(rules));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async users_to_csv(users: StoredUser[]) {
|
||||||
|
const promises = users.map(async (user) => ({ ...user, username: await this.get_username(user.discord_user_id) }));
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
const values = results.map((
|
||||||
|
{ discord_user_id, cri_login, username },
|
||||||
|
) => [discord_user_id, cri_login, username ?? "<unknown>"]);
|
||||||
|
return values.map((line) => line.map(csv_escape).join(",")).join("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Association = {
|
export type Association = {
|
||||||
|
@ -296,6 +325,11 @@ export type Dissociation = {
|
||||||
callback: (success: SimpleResult) => void;
|
callback: (success: SimpleResult) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Export = {
|
||||||
|
kind: "export";
|
||||||
|
callback: (value: SimpleResult<StoredUser[]>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
function message_command_response_assoc_pending(email: string) {
|
function message_command_response_assoc_pending(email: string) {
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("`🟡`| Association")
|
.setTitle("`🟡`| Association")
|
||||||
|
@ -338,6 +372,21 @@ ${rules.map(display_rule).join("\n")}
|
||||||
return { embeds: [embed] };
|
return { embeds: [embed] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function message_command_response_export_success(count: number, csv: string) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle("`✅`| Export")
|
||||||
|
.setDescription(`\`${count}\` associations exportées.`);
|
||||||
|
const attachment = new AttachmentBuilder(csv).setName(`export-${Date.now()}.csv`);
|
||||||
|
return { embeds: [embed], files: [attachment] };
|
||||||
|
}
|
||||||
|
|
||||||
|
function message_command_response_export_error(msg: string) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle("`❌`| Export")
|
||||||
|
.setDescription(`Échec :\n\`${msg}\``);
|
||||||
|
return { embeds: [embed] };
|
||||||
|
}
|
||||||
|
|
||||||
function message_command_response_rule_add_success(rule: GuildRule) {
|
function message_command_response_rule_add_success(rule: GuildRule) {
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("`✅`| Règle ajouté")
|
.setTitle("`✅`| Règle ajouté")
|
||||||
|
@ -374,3 +423,11 @@ function display_rule(rule: GuildRule) {
|
||||||
if (rule.include_historical) return `- \`${rule.group_id}\` [H] → \`@${rule.role_name}\``;
|
if (rule.include_historical) return `- \`${rule.group_id}\` [H] → \`@${rule.role_name}\``;
|
||||||
else return `- \`${rule.group_id}\` → \`@${rule.role_name}\``;
|
else return `- \`${rule.group_id}\` → \`@${rule.role_name}\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function csv_escape(text: string) {
|
||||||
|
const escaped = text
|
||||||
|
.replaceAll("\\", "\\\\")
|
||||||
|
.replaceAll("\n", "\\n")
|
||||||
|
.replaceAll('"', '\\"');
|
||||||
|
return `"${escaped}"`;
|
||||||
|
}
|
||||||
|
|
14
src/main.ts
14
src/main.ts
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-net --allow-env --unstable-kv
|
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-net --allow-env --unstable-kv
|
||||||
|
|
||||||
import { log_from, read_conf, root_path, SimpleResult } from "./utils.ts";
|
import { collect, log_from, read_conf, root_path, SimpleResult } from "./utils.ts";
|
||||||
import { State } from "./state.ts";
|
import { State, StoredUser } from "./state.ts";
|
||||||
import { RuleSet, TargetRole } from "./rules.ts";
|
import { RuleSet, TargetRole } from "./rules.ts";
|
||||||
import { EpitlsBot } from "./bot.ts";
|
import { EpitlsBot } from "./bot.ts";
|
||||||
import { CriApi } from "./cri.ts";
|
import { CriApi } from "./cri.ts";
|
||||||
|
@ -64,6 +64,7 @@ class Service {
|
||||||
for await (const req of this.bot.receive_associations()) {
|
for await (const req of this.bot.receive_associations()) {
|
||||||
if (req.kind === "associate") this.association_procedure(req.discord_user_id, req.cri_login).then(req.callback);
|
if (req.kind === "associate") this.association_procedure(req.discord_user_id, req.cri_login).then(req.callback);
|
||||||
if (req.kind === "dissociate") this.dissociation_procedure(req.discord_user_id).then(req.callback);
|
if (req.kind === "dissociate") this.dissociation_procedure(req.discord_user_id).then(req.callback);
|
||||||
|
if (req.kind === "export") this.export_procedure().then(req.callback);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -133,6 +134,15 @@ class Service {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async export_procedure(): Promise<SimpleResult<StoredUser[]>> {
|
||||||
|
try {
|
||||||
|
return await collect(this.state.users());
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return `${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// bot.assign_role_to_member("358338548174159873", "871777993922588712", "1202346358867238952");
|
// bot.assign_role_to_member("358338548174159873", "871777993922588712", "1202346358867238952");
|
||||||
|
|
|
@ -105,7 +105,7 @@ export function placeholder_conf() {
|
||||||
return JSON.stringify(result, null, 4);
|
return JSON.stringify(result, null, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SimpleResult = true | string;
|
export type SimpleResult<T = true> = T | string;
|
||||||
|
|
||||||
export async function wait(ms: number) {
|
export async function wait(ms: number) {
|
||||||
const { promise, resolver } = split_promise<void>();
|
const { promise, resolver } = split_promise<void>();
|
||||||
|
@ -131,3 +131,9 @@ export async function try_<T>(operation: () => Promise<T> | T, recovery: () => P
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function collect<T>(generator: AsyncIterable<T>) {
|
||||||
|
const result = [] as T[];
|
||||||
|
for await (const item of generator) result.push(item);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue