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:
parent
e6659a4b41
commit
1ece0ecb16
9 changed files with 182 additions and 20 deletions
|
@ -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
94
src/commands/misc/help.ts
Normal 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
|
||||
),
|
||||
] });
|
||||
},
|
||||
};
|
|
@ -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')}: \
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 :"
|
||||
}
|
||||
|
|
|
@ -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
16
src/modules/string.ts
Normal 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);
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue