From f284e5c2762184f4fec6367ea6d21c3c0f2da72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartholom=C3=A9?= Date: Tue, 9 Aug 2022 10:18:10 +0200 Subject: [PATCH] feat(guards): interactions locale extractor (#12) --- README.md | 2 ++ cli/generators/templates/command.ts.hbs | 7 +++-- src/commands/Admin/prefix.ts | 15 ++++++----- src/commands/General/help.ts | 33 ++++++++++------------- src/commands/General/invite.ts | 12 +++------ src/commands/General/ping.ts | 3 ++- src/commands/General/stats.ts | 9 +++---- src/commands/Owner/maintenance.ts | 10 +++---- src/events/interactionCreate.ts | 5 ++-- src/guards/disabled.ts | 2 +- src/guards/extractLocale.ts | 36 +++++++++++++++++++++++++ src/utils/classes/index.ts | 2 +- src/utils/types/interactions.d.ts | 7 ++++- 13 files changed, 90 insertions(+), 53 deletions(-) create mode 100644 src/guards/extractLocale.ts diff --git a/README.md b/README.md index 3bfb311c..f0c62325 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ You can also find useful documentations: Click here to expand the roadmap #### Todo +- [ ] fix backup path fs for docker (with __dirname) +- [ ] finish full localization of built-in commands #### Discord - [ ] Custom events diff --git a/cli/generators/templates/command.ts.hbs b/cli/generators/templates/command.ts.hbs index 3d0762e2..8df646a6 100644 --- a/cli/generators/templates/command.ts.hbs +++ b/cli/generators/templates/command.ts.hbs @@ -1,6 +1,6 @@ import { Client } from "discordx" import { Category } from "@discordx/utilities" -import { CommandInteraction } from "discord.js" +import { } from "discord.js" import { Discord, Slash, SlashOption } from "@decorators" import { Guard } from "@guards" @@ -13,7 +13,10 @@ export default class {{pascalCase name}}Command { 'Here goes the command description!' }) @Guard() - {{camelCase name}}(interaction: CommandInteraction): void { + {{camelCase name}}( + interaction: CommandInteraction + { localize }: InteractionData + ): void { interaction.reply('{{camelCase name}} command invoked!') } diff --git a/src/commands/Admin/prefix.ts b/src/commands/Admin/prefix.ts index 1266c938..af78934d 100644 --- a/src/commands/Admin/prefix.ts +++ b/src/commands/Admin/prefix.ts @@ -7,7 +7,6 @@ import { Guard, UserPermissions } from "@guards" import { Guild } from "@entities" import { resolveGuild, simpleSuccessEmbed } from "@utils/functions" import { Database } from "@services" -import { getLocaleFromInteraction, L } from "@i18n" import { generalConfig } from '@config' import { UnknownReplyError } from "@errors" @@ -29,9 +28,11 @@ export default class PrefixCommand { ) async prefix( @SlashOption('prefix', { required: false, type: ApplicationCommandOptionType.String }) prefix: string | undefined, - interaction: CommandInteraction + interaction: CommandInteraction, + { localize }: InteractionData ) { - + + const guild = resolveGuild(interaction), guildData = await this.db.getRepo(Guild).findOne({ id: guild?.id || '' }) @@ -39,13 +40,13 @@ export default class PrefixCommand { guildData.prefix = prefix || null this.db.getRepo(Guild).persistAndFlush(guildData) - - const locale = getLocaleFromInteraction(interaction) + simpleSuccessEmbed( interaction, - L[locale]['COMMANDS']['PREFIX']['CHANGED']({ + localize['COMMANDS']['PREFIX']['CHANGED']({ prefix: prefix || generalConfig.simpleCommandsPrefix - })) + }) + ) } else { throw new UnknownReplyError(interaction) diff --git a/src/commands/General/help.ts b/src/commands/General/help.ts index 07ed2c93..0eab50e9 100644 --- a/src/commands/General/help.ts +++ b/src/commands/General/help.ts @@ -1,11 +1,10 @@ import { Client, MetadataStorage, SelectMenuComponent } from "discordx" import { Category } from "@discordx/utilities" -import { CommandInteraction, Formatters, ActionRowBuilder, EmbedBuilder, SelectMenuBuilder, APISelectMenuOption, SelectMenuInteraction } from "discord.js" +import { Formatters, ActionRowBuilder, EmbedBuilder, SelectMenuBuilder, APISelectMenuOption, CommandInteraction, SelectMenuInteraction } from "discord.js" import { Discord, Slash } from "@decorators" -import { Guard } from "@guards" import { chunkArray, getColor, validString } from "@utils/functions" -import { getLocaleFromInteraction, L, Locales } from "@i18n" +import { L, Locales } from "@i18n" @Discord() @Category('General') @@ -20,14 +19,14 @@ export default class HelpCommand { @Slash('help', { description: 'Get global help about the bot and its commands' }) - help(interaction: CommandInteraction, client: Client): void { - - const locale = getLocaleFromInteraction(interaction) + help(interaction: CommandInteraction, client: Client, { sanitizedLocale }: InteractionData): void { - const embed = this.getEmbed({ client, interaction, locale }); + console.debug(1, sanitizedLocale) + + const embed = this.getEmbed({ client, interaction, locale: sanitizedLocale }); let components: any[] = []; - components.push(this.getSelectDropdown("categories", locale).toJSON()) + components.push(this.getSelectDropdown("categories", sanitizedLocale).toJSON()) interaction.followUp({ embeds: [embed], @@ -36,15 +35,13 @@ export default class HelpCommand { } @SelectMenuComponent('help-category-selector') - async selectCategory(interaction: SelectMenuInteraction, client: Client) { - - const locale = getLocaleFromInteraction(interaction) + async selectCategory(interaction: SelectMenuInteraction, client: Client, { sanitizedLocale }: InteractionData) { const category = interaction.values[0] - const embed = await this.getEmbed({ client, interaction, locale, category }) + const embed = await this.getEmbed({ client, interaction, category, locale: sanitizedLocale }) let components: any[] = []; - components.push(this.getSelectDropdown("categories", locale).toJSON()) + components.push(this.getSelectDropdown("categories", sanitizedLocale).toJSON()) interaction.update({ embeds: [embed], @@ -53,12 +50,12 @@ export default class HelpCommand { } - private getEmbed({ client, interaction, locale, category = '', pageNumber = 0 }: { + private getEmbed({ client, interaction, category = '', pageNumber = 0, locale }: { client: Client, interaction: CommandInteraction | SelectMenuInteraction, - locale: Locales, category?: string, pageNumber?: number + locale: Locales }): EmbedBuilder { const commands = this._categories.get(category) @@ -77,10 +74,8 @@ export default class HelpCommand { for (const category of this._categories) { embed.addFields([{ - name: category[0], - value: category[1] - .map(command => command.name) - .join(', ') + name: category[0], + value: category[1].map(command => command.name).join(', ') }]) } diff --git a/src/commands/General/invite.ts b/src/commands/General/invite.ts index 4d569c93..38f6bb13 100644 --- a/src/commands/General/invite.ts +++ b/src/commands/General/invite.ts @@ -1,11 +1,9 @@ -import { Client } from "discordx" import { Category } from "@discordx/utilities" import { CommandInteraction, EmbedBuilder } from "discord.js" -import { Discord, Slash, SlashOption } from "@decorators" +import { Discord, Slash } from "@decorators" import { Guard } from "@guards" import { getColor } from "@utils/functions" -import { getLocaleFromInteraction, L } from "@i18n" import { generalConfig } from "@config" @Discord() @@ -16,13 +14,11 @@ export default class InviteCommand { 'A simple invite command!' }) @Guard() - invite(interaction: CommandInteraction): void { - - const locale = getLocaleFromInteraction(interaction) + invite(interaction: CommandInteraction, { localize }: InteractionData): void { const embed = new EmbedBuilder() - .setTitle(L[locale].COMMANDS.INVITE.TITLE()) - .setDescription(L[locale].COMMANDS.INVITE.DESCRIPTION({link: generalConfig.inviteLink})) + .setTitle(localize.COMMANDS.INVITE.TITLE()) + .setDescription(localize.COMMANDS.INVITE.DESCRIPTION({link: generalConfig.inviteLink})) .setColor(getColor('primary')) .setFooter({ text : 'Powered by DiscBot Team ❤'}) diff --git a/src/commands/General/ping.ts b/src/commands/General/ping.ts index 384f770b..73ce9218 100644 --- a/src/commands/General/ping.ts +++ b/src/commands/General/ping.ts @@ -14,7 +14,8 @@ export default class PingCommand { }) async ping( interaction: CommandInteraction, - client: Client + client: Client, + { localize }: InteractionData ) { const msg = (await interaction.followUp({ content: "Pinging...", fetchReply: true })) as Message diff --git a/src/commands/General/stats.ts b/src/commands/General/stats.ts index 151a4822..d9b9d1e8 100644 --- a/src/commands/General/stats.ts +++ b/src/commands/General/stats.ts @@ -1,4 +1,3 @@ -import { Client } from "discordx" import { Category } from "@discordx/utilities" import { CommandInteraction, EmbedBuilder, User } from "discord.js" import { injectable } from "tsyringe" @@ -9,7 +8,6 @@ import { import { Discord, Slash, SlashOption } from "@decorators" import { Stats } from "@services" -import { getLocaleFromInteraction, L } from "@i18n" import { getColor } from "@utils/functions" const statsResolver: StatsResolverType = [ @@ -57,19 +55,18 @@ export default class StatsCommand { }) async statsHandler( @SlashOption('days') days: number, - interaction: CommandInteraction + interaction: CommandInteraction, + { localize }: InteractionData ) { const embeds: EmbedBuilder[] = [] - const locale = getLocaleFromInteraction(interaction) - for (const stat of statsResolver) { const stats = await stat.data(this.stats, days), link = await this.generateLink( stats, - L[locale]['COMMANDS']['STATS']['HEADERS'][stat.name as keyof typeof L[(typeof locale)]['COMMANDS']['STATS']['HEADERS']]()), + localize['COMMANDS']['STATS']['HEADERS'][stat.name as keyof typeof localize['COMMANDS']['STATS']['HEADERS']]()), embed = this.getEmbed(interaction.user, link) embeds.push(embed) diff --git a/src/commands/Owner/maintenance.ts b/src/commands/Owner/maintenance.ts index 66e2f747..e9ed3ba8 100644 --- a/src/commands/Owner/maintenance.ts +++ b/src/commands/Owner/maintenance.ts @@ -1,9 +1,8 @@ -import { CommandInteraction } from "discord.js" - import { Slash, Discord, SlashOption, Guard } from "@decorators" import { setMaintenance, simpleSuccessEmbed } from "@utils/functions" import { getLocaleFromInteraction, L } from "@i18n" import { Disabled } from "@guards" +import { CommandInteraction } from "discord.js" @Discord() export default class MaintenanceCommand { @@ -16,15 +15,16 @@ export default class MaintenanceCommand { ) async maintenance( @SlashOption('state') state: boolean, - interaction: CommandInteraction + interaction: CommandInteraction, + { localize }: InteractionData ) { - + await setMaintenance(state) const locale = getLocaleFromInteraction(interaction) simpleSuccessEmbed( interaction, - L[locale]['COMMANDS']['MAINTENANCE']['SUCCESS']({ + localize['COMMANDS']['MAINTENANCE']['SUCCESS']({ state: state ? 'on' : 'off' }) ) diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 8e1f2e7b..71d73d63 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,3 +1,4 @@ +import { MessageComponentInteraction, CommandInteraction as DCommandInteraction } from 'discord.js' import { Client, ArgsOf } from 'discordx' import { injectable } from 'tsyringe' @@ -6,7 +7,7 @@ import { Maintenance } from '@guards' import { Guild, User } from '@entities' import { On, Guard, Discord } from '@decorators' import { syncUser } from '@utils/functions' -import { CommandInteraction, MessageComponentInteraction } from 'discord.js' +import { getLocaleFromInteraction, L } from '@i18n' @Discord() @injectable() @@ -29,7 +30,7 @@ export default class InteractionCreateEvent { // defer the reply if( interaction instanceof MessageComponentInteraction || - interaction instanceof CommandInteraction + interaction instanceof DCommandInteraction ) await interaction.deferReply(); // insert user in db if not exists diff --git a/src/guards/disabled.ts b/src/guards/disabled.ts index 3788b299..71fb2e51 100644 --- a/src/guards/disabled.ts +++ b/src/guards/disabled.ts @@ -1,5 +1,5 @@ import { GuardFunction, SimpleCommandMessage } from 'discordx' -import { CommandInteraction, ContextMenuCommandInteraction } from 'discord.js' +import { ContextMenuCommandInteraction, CommandInteraction } from 'discord.js' import { getLocaleFromInteraction, L } from '@i18n' import { resolveUser, replyToInteraction } from '@utils/functions' diff --git a/src/guards/extractLocale.ts b/src/guards/extractLocale.ts new file mode 100644 index 00000000..52857428 --- /dev/null +++ b/src/guards/extractLocale.ts @@ -0,0 +1,36 @@ +import { GuardFunction, SimpleCommandMessage } from 'discordx' +import { ContextMenuCommandInteraction, CommandInteraction as DCommandInteraction, Interaction, MessageContextMenuCommandInteraction, UserContextMenuCommandInteraction, CommandInteraction, SelectMenuInteraction, ButtonInteraction } from 'discord.js' + +import { getLocaleFromInteraction, L } from '@i18n' +import { resolveUser, replyToInteraction } from '@utils/functions' + +import { generalConfig } from '@config' + +/** + * Extract locale from any interaction and pass it as guard data + */ +export const Disabled: GuardFunction = async (interaction, client, next, guardData) => { + + if ( + interaction instanceof SimpleCommandMessage + || interaction instanceof CommandInteraction + || interaction instanceof ContextMenuCommandInteraction + || interaction instanceof SelectMenuInteraction + || interaction instanceof ButtonInteraction + ) { + + const sanitizedLocale = getLocaleFromInteraction(interaction as AllInteractions) + + const interactionData: InteractionData = { + sanitizedLocale: sanitizedLocale, + localize: L[sanitizedLocale] + } + + guardData = { + ...guardData, + ...interactionData + } + } + + await next() +} \ No newline at end of file diff --git a/src/utils/classes/index.ts b/src/utils/classes/index.ts index 85460d45..2f6f5c50 100644 --- a/src/utils/classes/index.ts +++ b/src/utils/classes/index.ts @@ -1,2 +1,2 @@ export * from './BaseError' -export * from './BaseController' +export * from './BaseController' \ No newline at end of file diff --git a/src/utils/types/interactions.d.ts b/src/utils/types/interactions.d.ts index a3a738f4..c878426f 100644 --- a/src/utils/types/interactions.d.ts +++ b/src/utils/types/interactions.d.ts @@ -8,4 +8,9 @@ type AllInteractions = EmittedInteractions | OnTheFlyInteractions type InteractionsConstants = 'CHAT_INPUT_COMMAND_INTERACTION' | 'SIMPLE_COMMAND_MESSAGE' | 'CONTEXT_MENU_INTERACTION' | 'BUTTON_INTERACTION' | 'SELECT_MENU_INTERACTION' | 'MODAL_SUBMIT_INTERACTION' -type CommandCategory = import('discordx').DApplicationCommand & import('@discordx/utilities').ICategory \ No newline at end of file +type CommandCategory = import('discordx').DApplicationCommand & import('@discordx/utilities').ICategory + +type InteractionData = { + sanitizedLocale: import('src/i18n').Locales + localize: import('src/i18n/i18n-types').TranslationFunctions +} \ No newline at end of file