feat: help command (#36)

- command.data easier to use
- store commands categories
- ping command more readable
- store commands in a different way (cf. categories)
- update translations
- add string.capitalize() extension
- lowercase command names in localized thing

Reviewed-on: https://git.kennel.ml/ConfrerieDuKassoulait/Botanique/pulls/36
This commit is contained in:
Anri 2022-07-25 00:54:19 +02:00
parent e6659a4b41
commit 1ece0ecb16
9 changed files with 182 additions and 20 deletions

View file

@ -2,6 +2,7 @@ import { REST } from '@discordjs/rest';
import { Routes } from 'discord-api-types/v9';
import { Client } from 'discord.js';
import { readdir } from 'fs/promises';
import { removeExtension } from '../utils/misc';
/** Load all the commands. */
export default async (client: Client) => {
@ -17,6 +18,12 @@ export default async (client: Client) => {
// Retrieve all the commands
const command_files = await readdir(`${__dirname}/${command_category}`);
// Add the category to the collection for the help command
client.commands.categories.set(
command_category,
command_files.map(removeExtension),
);
// Add the command
return Promise.all(
command_files.map(async command_file => {
@ -25,9 +32,10 @@ export default async (client: Client) => {
).default;
// Add it to the collection so the interaction will work
client.commands.set(command.data(client).name, command);
command.data = command.data(client);
client.commands.list.set(command.data.name, command);
return command.data(client).toJSON();
return command.data.toJSON();
}),
);
}),

94
src/commands/misc/help.ts Normal file
View file

@ -0,0 +1,94 @@
import { SlashCommandBuilder } from '@discordjs/builders';
import { Locale } from 'discord-api-types/v9';
import { Client, CommandInteraction, MessageEmbed } from 'discord.js';
import { getLocale, getLocalizations } from '../../utils/locales';
import { getFilename } from '../../utils/misc';
import '../../modules/string';
export default {
data: (client: Client) => {
const filename = getFilename(__filename);
return new SlashCommandBuilder()
.setName(
filename.toLowerCase())
.setDescription(client.locales.get(client.config.default_lang)
?.get(`c_${filename}_desc`) ?? '')
.setNameLocalizations(
getLocalizations(client, `c_${filename}_name`, true))
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`))
// Command option
.addStringOption(option => option
.setName(client.locales.get(client.config.default_lang)
?.get(`c_${filename}_opt1_name`) ?? '')
.setDescription(client.locales.get(client.config.default_lang)
?.get(`c_${filename}_opt1_desc`) ?? '')
.setNameLocalizations(
getLocalizations(client, `c_${filename}_opt1_name`, true))
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_opt1_desc`))
);
},
interaction: async (interaction: CommandInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale);
const desired_command = interaction.options.getString(client
.locales
.get(client.config.default_lang)
?.get(`c_${getFilename(__filename)}_opt1_name`) ?? '');
// If a command isn't specified
if (!desired_command) {
const fields: {
name: string;
value: string;
}[] = [];
// Load all the command per categories
client.commands.categories.forEach((commands_name, category) => {
const commands = commands_name.reduce((data, command_name) => {
return data + `\`/${command_name}\`, `;
}, '');
fields.push({
name: category.capitalize() + ` (${commands_name.length})`,
value: commands.slice(0, -2),
});
});
// Sends a list of commands sorted into categories
return interaction.reply({ embeds: [
new MessageEmbed()
.setColor('BLURPLE')
.setTitle(loc.get('c_help1'))
.setDescription(loc.get('c_help2'))
.addFields(fields),
] });
}
// If a command is specified
const command = client.commands.list.get(desired_command);
if (!command) {
// Command don't exist
return interaction.reply({
content: `${loc.get('c_help3')} \`${desired_command}\``,
ephemeral: true,
});
}
// Send information about the command
return interaction.reply({ embeds: [
new MessageEmbed()
.setColor('BLURPLE')
.setTitle('`/' + command.data.name + '`')
.setDescription(
// Loads the description
// according to the user's locals
command.data.description_localizations
?.[interaction.locale as Locale]
?? command.data.description
),
] });
},
};

View file

@ -7,16 +7,24 @@ export default {
data: (client: Client) => {
const filename = getFilename(__filename);
return new SlashCommandBuilder()
.setName(filename.toLowerCase())
.setDescription(client.locales.get(client.config.default_lang)?.get(`c_${filename}_desc`) ?? '?')
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`))
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`));
.setName(
filename.toLowerCase())
.setDescription(client.locales.get(client.config.default_lang)
?.get(`c_${filename}_desc`) ?? '')
.setNameLocalizations(
getLocalizations(client, `c_${filename}_name`, true))
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`)
);
},
interaction: async (interaction: CommandInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale);
const sent = await interaction.reply({ content: 'Pinging...', fetchReply: true }) as Message;
const sent = await interaction.reply({
content: 'Pinging...',
fetchReply: true,
}) as Message;
interaction.editReply(
`${loc?.get('c_ping1')}: \

View file

@ -4,7 +4,7 @@ import { getLocale } from '../../utils/locales';
/** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-interactionCreate */
export default (interaction: Interaction, client: Client) => {
if (interaction.isCommand()) {
const command = client.commands.get(interaction.commandName);
const command = client.commands.list.get(interaction.commandName);
if (!command) {
const loc = getLocale(client, interaction.locale);
return interaction.reply({

View file

@ -1,7 +1,16 @@
{
"e_interacreate_no_command": "Sorry, the command probably no longer exists...",
"c_ping_name": "Ping",
"c_ping_desc": "Pong!",
"c_ping1": "Roundtrip latency",
"c_ping2": "Websocket heartbeat"
"c_ping2": "Websocket heartbeat",
"c_help_name": "Help",
"c_help_desc": "Informations about commands",
"c_help_opt1_name": "command",
"c_help_opt1_desc": "Command wanted in depth.",
"c_help1": "List of categories and associated commands",
"c_help2": "`/help <command>` to get more information about a command.",
"c_help3": "Can't find :"
}

View file

@ -1,7 +1,16 @@
{
"e_interacreate_no_command": "Désolé, la commande n'existe plus...",
"c_ping_name": "Ping",
"c_ping_desc": "Pong!",
"c_ping1": "Latence totale",
"c_ping2": "Latence du Websocket"
"c_ping2": "Latence du Websocket",
"c_help_name": "Aide",
"c_help_desc": "Informations sur les commandes",
"c_help_opt1_name": "commande",
"c_help_opt1_desc": "Commande voulu en détail.",
"c_help1": "Liste des catégories et des commandes associées",
"c_help2": "`/help <commande>` pour obtenir plus d'informations sur une commande.",
"c_help3": "Impossible de trouver :"
}

16
src/modules/string.ts Normal file
View file

@ -0,0 +1,16 @@
export {};
declare global {
// Declarations
interface String {
/**
* Returns a copy of the string with the first letter capitalized.
*/
capitalize(): string,
}
}
/** Capitalize definition */
String.prototype.capitalize = function(this: string) {
return this[0].toUpperCase() + this.substring(1);
};

View file

@ -16,13 +16,25 @@ declare module 'discord.js' {
default_lang: string
},
/** Store all the slash commands */
commands: Collection<
commands: {
categories: Collection<
/** Category name */
string,
/** Name of the commands in the category */
string[]
>,
list: Collection<
/** Command name */
string,
/** Command itself */
{
/** Data about the command */
data: SlashCommandBuilder,
/** How the command interact */
interaction: (interaction: CommandInteraction, client: Client) => unknown
}
>,
}
/** Store all the localizations */
locales: Map<string, Map<string, string>>
}
@ -42,7 +54,10 @@ export default async () => {
default_lang: process.env.DEFAULT_LANG ?? 'fr',
};
client.commands = new Collection();
client.commands = {
categories: new Collection(),
list: new Collection(),
};
console.log('Translations progression :');
client.locales = await loadLocales(client.config.default_lang);

View file

@ -53,18 +53,21 @@ export const loadLocales = async (default_lang: string) => {
* @param text Name of string to fetch
* @returns the dictionary
*/
export const getLocalizations = (client: Client, text: string) => {
export const getLocalizations = (client: Client, text: string, lowercase = false) => {
const data: Record<string, string> = {};
// Load all the localizations
client.locales.forEach((locale, lang) => {
// Fetch the text and fallback to default lang if needed
// See getLocale for more info on why we *can* fallback
const str = locale.get(text)
let str = locale.get(text)
?? client.locales.get(client.config.default_lang)?.get(text);
// Store it if defined
if (str !== undefined) {
if (lowercase) {
str = str.toLowerCase();
}
data[lang] = str;
}
});