diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index afc94ec..7d1db25 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,6 +116,8 @@ import { getLocale, getLocalizations } from "../../utils/locales"; import { getFilename } from "../../utils/misc"; export default { + scope: () => [], + data: (client: Client) => { const filename = getFilename(__filename); return new SlashCommandBuilder() @@ -144,6 +146,13 @@ export default { }; ``` +Rapidement, cette structure comporte 3 éléments : + +- `scope` : une liste de guildId où la commande est disponible, si la liste est + est vide, la commande est disponible partout +- `data` : représente les données envoyées à l'API de Discord +- `interaction` : représente le comportement de la commande + Ce template vous permet de commencé rapidement votre commande car il contient déjà tout ce qu'il faut pour le support des langues. Pensez bien à ne pas écrire directement vos chaînes de caractères ici mais bien dans diff --git a/package-lock.json b/package-lock.json index 29a6e0e..ee38fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@discordjs/rest": "^1.1.0", "@types/sqlite3": "^3.1.8", + "@types/uuid": "^9.0.0", "discord-api-types": "^0.36.3", "discord.js": "^14.3.0", "sqlite3": "^5.0.11", @@ -18,7 +19,6 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/parser": "^5.30.7", "dotenv": "^16.0.1", @@ -372,8 +372,7 @@ "node_modules/@types/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==", - "dev": true + "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==" }, "node_modules/@types/ws": { "version": "8.5.4", @@ -3594,8 +3593,7 @@ "@types/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==", - "dev": true + "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==" }, "@types/ws": { "version": "8.5.4", diff --git a/src/commands/loader.ts b/src/commands/loader.ts index f7463df..ca44dc2 100644 --- a/src/commands/loader.ts +++ b/src/commands/loader.ts @@ -35,15 +35,43 @@ export default async (client: Client) => { command.data = command.data(client); client.commands.list.set(command.data.name, command); - return command.data.toJSON(); + return command; }), ); }), ) ).flat(2); - // Send commands to Discord - return await rest.put(Routes.applicationCommands(client.user?.id ?? ''), { - body: commands, + // Send guilds commands to Discord + const scopedCommands = new Map(); + + // Add commands to guild where the bot is + const allowedGuilds = client.guilds.cache; + + // Assign each commands to the guilds + commands.filter((c) => c.scope().length > 0).forEach((c) => { + c.scope().forEach((guild: string) => { + if (allowedGuilds.get(guild) !== undefined) { + const guildCommands = scopedCommands.get(guild); + if (guildCommands === undefined) { + scopedCommands.set(guild, [c.data.toJSON()]); + } else { + guildCommands.push(c.data.toJSON()); + scopedCommands.set(guild, guildCommands); + } + } + }); + }); + + scopedCommands + .forEach(async (command, guild) => await rest.put( + Routes.applicationGuildCommands(client.user?.id as string, guild), { + body: command, + })); + + // Send global commands to Discord + const globalCommands = commands.filter((c) => c.scope().length == 0); + return await rest.put(Routes.applicationCommands(client.user?.id as string), { + body: globalCommands.map((c) => c.data.toJSON()), }); }; diff --git a/src/commands/misc/archive.ts b/src/commands/misc/archive.ts index 5830b45..46b3fb1 100644 --- a/src/commands/misc/archive.ts +++ b/src/commands/misc/archive.ts @@ -1,10 +1,12 @@ import { SlashCommandBuilder } from '@discordjs/builders'; -import { ChannelType, Client, CommandInteraction, EmbedBuilder } from 'discord.js'; +import { ChannelType, Client, Colors, CommandInteraction, EmbedBuilder, NonThreadGuildBasedChannel } from 'discord.js'; +import '../../modules/string'; import { getLocale, getLocalizations } from '../../utils/locales'; import { getFilename } from '../../utils/misc'; -import '../../modules/string'; export default { + scope: () => ['807244911350906920'], + data: (client: Client) => { const filename = getFilename(__filename); return new SlashCommandBuilder() @@ -32,19 +34,19 @@ export default { interaction: async (interaction: CommandInteraction, client: Client) => { const loc = getLocale(client, interaction.locale); - const desired_cat = interaction.options.get(client + const desiredCat = interaction.options.get(client .locales .get(client.config.default_lang) ?.get(`c_${getFilename(__filename)}_opt1_name`) ?? '')?.value as string; // If a category isn't specified - if (!desired_cat) { + if (!desiredCat) { // Sends a list of commands sorted into categories return interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('Blurple') + .setColor(Colors.Blurple) .setTitle(loc.get('c_archive1')) .setDescription(loc.get('c_archive2')), ], @@ -52,58 +54,73 @@ export default { } // If a category is specified - const clean_cat = ['L1', 'L2', 'L3', 'M1', 'M2']; - const channel = clean_cat.includes(desired_cat); + const cleanCat = ['L1', 'L2', 'L3', 'M1', 'M2']; + const channel = cleanCat.includes(desiredCat); if (!channel) { // Category doesn't exist or is not included return interaction.reply({ - content: `${loc.get('c_archive3')} \`${desired_cat}\``, + content: `${loc.get('c_archive3')} \`${desiredCat}\``, ephemeral: true, }); } - // Send information about the command const allChannel = interaction.guild?.channels.fetch(); - allChannel?.then(channel_guild => { - const cat_to_archive = channel_guild.filter(chan => chan.type == ChannelType.GuildCategory).filter(chan => chan.name == desired_cat); - const cat_to_archive_id = cat_to_archive.map(cat => cat.id); - const cat_to_archive_name = cat_to_archive.map(cat => cat.name); + allChannel?.then(async channelGuild => { + // Retrieve category to archive + const catToArchive = channelGuild + .filter(chan => chan?.type == ChannelType.GuildCategory) + .filter(chan => chan?.name == desiredCat); - const cat_archived = channel_guild.filter(chan => chan.type == ChannelType.GuildCategory).filter(chan => chan.name == 'archive - ' + desired_cat); - const cat_archived_id = cat_archived.map(cat => cat.id); - const cat_archived_name = cat_archived.map(cat => cat.name); + // Create/Retrieve the archive category + const catArchivedName = 'archive - ' + desiredCat; + const catArchivedMap = channelGuild + .filter(chan => chan?.type == ChannelType.GuildCategory) + .filter(chan => chan?.name == catArchivedName); - const all_channel_desired = channel_guild.filter(chan => chan.type == 0).filter(chan => chan.parentId == cat_to_archive_id[0]); - if (all_channel_desired.size == 0) { + let catArchived: NonThreadGuildBasedChannel | null | undefined; + if (catArchivedMap.size > 0) { + catArchived = catArchivedMap.at(0); + } else { + catArchived = await interaction.guild?.channels + .create({ name: catArchivedName, type: ChannelType.GuildCategory }); + } + + const allChannelDesired = channelGuild + .filter(chan => chan?.type == 0) + .filter(chan => chan?.parentId == catToArchive.map(cat => cat?.id)[0]); + + // If no channels in the source category + if (allChannelDesired.size == 0) { return interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('Blurple') + .setColor(Colors.Blurple) .setTitle(loc.get('c_archive6')) .setDescription( - // Loads the description - // according to the user's locals loc.get('c_archive7') ), ], }); } - const all_channel_desired_name = all_channel_desired.map(cg_d => cg_d.name); - console.log(all_channel_desired_name); - all_channel_desired.forEach(elem => - elem.setParent(cat_archived_id[0]) - ); - const list_cg_moved = all_channel_desired_name.toString().replaceAll(',', '\n'); + // Move channels to the archived categoryx + allChannelDesired.forEach(elem => elem?.setParent(catArchived?.id as string)); + return interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('Blurple') - .setTitle(loc.get('c_archive4') + cat_to_archive_name + loc.get('c_archive5') + cat_archived_name + '`') + .setColor(Colors.Blurple) + .setTitle(loc.get('c_archive4') + + ' `' + + catToArchive.map(cat => cat?.name) + + '` ' + + loc.get('c_archive5') + + ' `' + + catArchivedName + + '`') .setDescription( - // Loads the description - // according to the user's locals - list_cg_moved + allChannelDesired + .map(cgD => cgD?.name).toString().replaceAll(',', '\n') ), ], }); diff --git a/src/commands/misc/help.ts b/src/commands/misc/help.ts index d43a792..3593ab2 100644 --- a/src/commands/misc/help.ts +++ b/src/commands/misc/help.ts @@ -1,11 +1,13 @@ import { SlashCommandBuilder } from '@discordjs/builders'; import { Locale } from 'discord-api-types/v9'; -import { Client, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { Client, ChatInputCommandInteraction, EmbedBuilder, Colors } from 'discord.js'; import { getLocale, getLocalizations } from '../../utils/locales'; import { getFilename } from '../../utils/misc'; import '../../modules/string'; export default { + scope: () => [], + data: (client: Client) => { const filename = getFilename(__filename); return new SlashCommandBuilder() @@ -46,6 +48,7 @@ export default { }[] = []; // Load all the command per categories + // TODO: Check if the command exist in the context (guild) client.commands.categories.forEach((commands_name, category) => { const commands = commands_name.reduce((data, command_name) => { return data + `\`/${command_name}\`, `; @@ -60,7 +63,7 @@ export default { // Sends a list of commands sorted into categories return interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('Blurple') + .setColor(Colors.Blurple) .setTitle(loc.get('c_help1')) .setDescription(loc.get('c_help2')) .addFields(fields), @@ -68,6 +71,7 @@ export default { } // If a command is specified + // TODO: Check if the command exist in the context (guild) const command = client.commands.list.get(desired_command); if (!command) { // Command don't exist @@ -80,7 +84,7 @@ export default { // Send information about the command return interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('Blurple') + .setColor(Colors.Blurple) .setTitle('`/' + command.data.name + '`') .setDescription( // Loads the description diff --git a/src/commands/misc/ping.ts b/src/commands/misc/ping.ts index 1c938fc..90b5c2b 100644 --- a/src/commands/misc/ping.ts +++ b/src/commands/misc/ping.ts @@ -4,6 +4,8 @@ import { getLocale, getLocalizations } from '../../utils/locales'; import { getFilename } from '../../utils/misc'; export default { + scope: () => [], + data: (client: Client) => { const filename = getFilename(__filename); return new SlashCommandBuilder() diff --git a/src/commands/misc/prep.ts b/src/commands/misc/prep.ts index d924c77..301ed0f 100644 --- a/src/commands/misc/prep.ts +++ b/src/commands/misc/prep.ts @@ -1,10 +1,12 @@ import { SlashCommandBuilder } from '@discordjs/builders'; -import { ChannelType, Client, CommandInteraction, EmbedBuilder } from 'discord.js'; +import { ChannelType, Client, Colors, CommandInteraction, EmbedBuilder } from 'discord.js'; +import '../../modules/string'; import { getLocale, getLocalizations } from '../../utils/locales'; import { getFilename } from '../../utils/misc'; -import '../../modules/string'; export default { + scope: () => ['807244911350906920'], + data: (client: Client) => { const filename = getFilename(__filename); return new SlashCommandBuilder() @@ -44,7 +46,7 @@ export default { return interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('Blurple') + .setColor(Colors.Blurple) .setTitle(loc.get('c_prep1')) .setDescription(loc.get('c_prep2')), ], @@ -52,10 +54,10 @@ export default { } // If a category is specified - const clean_cat = ['L1', 'L2', 'L3', 'M1', 'M2']; - const channel = clean_cat.includes(desired_cat); + const allowedCategories = ['L1', 'L2', 'L3', 'M1', 'M2']; + const channel = allowedCategories.includes(desired_cat); if (!channel) { - // Category doesn't exist or is not included + // Category doesn't exist or is not allowed return interaction.reply({ content: `${loc.get('c_prep3')} \`${desired_cat}\``, ephemeral: true, @@ -65,13 +67,13 @@ export default { // Send information about the command const allChannel = interaction.guild?.channels.fetch(); allChannel?.then(channel_guild => { - const cat_to_prep = channel_guild.filter(chan => chan.type == ChannelType.GuildCategory).filter(chan => chan.name == desired_cat); - const cat_to_prep_id = cat_to_prep.map(cat => cat.id); - const cat_to_prep_name = cat_to_prep.map(cat => cat.name); + const cat_to_prep = channel_guild.filter(chan => chan?.type == ChannelType.GuildCategory).filter(chan => chan?.name == desired_cat); + const cat_to_prep_id = cat_to_prep.map(cat => cat?.id); + const cat_to_prep_name = cat_to_prep.map(cat => cat?.name); // console.log(cat_to_prep); - const all_channel_desired = channel_guild.filter(chan => chan.type == 0).filter(chan => chan.parentId == cat_to_prep_id[0]); - const all_channel_desired_name = all_channel_desired.map(c_d => c_d.name); + const all_channel_desired = channel_guild.filter(chan => chan?.type == 0).filter(chan => chan?.parentId == cat_to_prep_id[0]); + const all_channel_desired_name = all_channel_desired.map(c_d => c_d?.name); let desc = ''; @@ -93,7 +95,7 @@ export default { parent: cat_to_prep_id[0], }); - desc += info + loc.get('c_prep5') + '\n'; + desc += '`' + info + '` ' + loc.get('c_prep5') + '\n'; } if (desc == '') { @@ -103,11 +105,9 @@ export default { return interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('Blurple') + .setColor(Colors.Blurple) .setTitle(loc.get('c_prep4') + cat_to_prep_name) .setDescription( - // Loads the description - // according to the user's locals desc, ), ], diff --git a/src/commands/misc/reminder.ts b/src/commands/misc/reminder.ts index 3a0baa9..70e4a53 100644 --- a/src/commands/misc/reminder.ts +++ b/src/commands/misc/reminder.ts @@ -7,6 +7,8 @@ import { getFilename } from '../../utils/misc'; import { checkOwnershipReminder, deleteReminder, embedListReminders, getReminderInfo, newReminder } from '../../utils/reminder'; export default { + scope: () => [], + data: (client: Client) => { const filename = getFilename(__filename); const loc_default = client.locales.get(client.config.default_lang); diff --git a/src/locales/fr.json b/src/locales/fr.json index a4f6194..05a1d16 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -23,8 +23,8 @@ "c_archive1": "Liste des catégories soumis au nettoyage", "c_archive2": "`L1`, `L2`, `L3`, `M1`, `M2`", "c_archive3": "Impossible de trouver/nettoyer le salon :", - "c_archive4": "Listes des Salons archivés de la catégorie `", - "c_archive5": "` vers `", + "c_archive4": "Liste des salons archivés de la catégorie", + "c_archive5": "vers", "c_archive6": "Nettoyage", "c_archive7": "Catégorie déjà nettoyée",