From 75beba063b0739d9e21b23d8647bd1b349a73d5e Mon Sep 17 00:00:00 2001 From: GamesTwoLifeTwoLife Date: Tue, 12 Mar 2024 14:49:23 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B4=D0=B0=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=BF=D1=96=D0=B4=D1=82=D1=80=D0=B8=D0=BC=D0=BA=D1=83=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=D0=BB=D1=96=D0=B7=D0=B0=D1=86=D1=96=D1=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Додано папку `locales` яка містить рядки локалізації, проведено локалізацію подій, команд, компонентів відповідно до рядків локалізації.. --- commands/info/ping.js | 16 ++- commands/sample/message sample.js | 13 +- commands/sample/sample.js | 99 ++++++++++----- commands/sample/user sample.js | 13 +- .../{category => sample}/sample.js | 9 +- components/buttons/category/sample.js | 12 -- components/buttons/category/say_hello.js | 12 -- components/buttons/sample/sample.js | 13 ++ components/buttons/sample/say_hello.js | 13 ++ components/modals/category/sample.js | 16 --- components/modals/sample/sample.js | 17 +++ components/selectmenu/category/sample.js | 12 -- .../selectmenu/sample/channel_sample.js | 13 ++ .../selectmenu/sample/mentionable_sample.js | 13 ++ components/selectmenu/sample/role_sample.js | 13 ++ components/selectmenu/sample/string_sample.js | 13 ++ components/selectmenu/sample/user_sample.js | 13 ++ events/Interaction/autocompleteInteraction.js | 19 ++- events/Interaction/butonInteraction.js | 114 ++++++++++-------- .../Interaction/chatInputInteractionCreate.js | 93 ++++++++++++++ .../contextMenuInteractionCreate.js | 80 +++++++----- events/Interaction/interactionCreate.js | 81 ------------- events/Interaction/modalInteraction.js | 83 ++++++++++--- events/Interaction/selectMenuInteraction.js | 107 +++++++++------- index.js | 16 +++ locales/en/commands.json | 53 ++++++++ locales/en/common.json | 15 +++ locales/resources.js | 21 ++++ locales/ru/commands.json | 53 ++++++++ locales/ru/common.json | 16 +++ locales/uk/commands.json | 53 ++++++++ locales/uk/common.json | 15 +++ utils/buttonPagination.js | 27 +++-- 33 files changed, 826 insertions(+), 330 deletions(-) rename components/autocomplete/{category => sample}/sample.js (51%) delete mode 100644 components/buttons/category/sample.js delete mode 100644 components/buttons/category/say_hello.js create mode 100644 components/buttons/sample/sample.js create mode 100644 components/buttons/sample/say_hello.js delete mode 100644 components/modals/category/sample.js create mode 100644 components/modals/sample/sample.js delete mode 100644 components/selectmenu/category/sample.js create mode 100644 components/selectmenu/sample/channel_sample.js create mode 100644 components/selectmenu/sample/mentionable_sample.js create mode 100644 components/selectmenu/sample/role_sample.js create mode 100644 components/selectmenu/sample/string_sample.js create mode 100644 components/selectmenu/sample/user_sample.js create mode 100644 events/Interaction/chatInputInteractionCreate.js delete mode 100644 events/Interaction/interactionCreate.js create mode 100644 locales/en/commands.json create mode 100644 locales/en/common.json create mode 100644 locales/resources.js create mode 100644 locales/ru/commands.json create mode 100644 locales/ru/common.json create mode 100644 locales/uk/commands.json create mode 100644 locales/uk/common.json diff --git a/commands/info/ping.js b/commands/info/ping.js index af75bc0..4661185 100644 --- a/commands/info/ping.js +++ b/commands/info/ping.js @@ -1,4 +1,5 @@ const { SlashCommandBuilder } = require("discord.js"); +const { t } = require("i18next"); /** * @type {import('../../typings').Command} @@ -6,18 +7,27 @@ const { SlashCommandBuilder } = require("discord.js"); module.exports = { data: new SlashCommandBuilder() .setName("ping") - .setDescription("Ping Pong.") + .setDescription(t('commands:info.ping.description', { lng: "en-US" })) + .setDescriptionLocalizations({ + 'en-GB': t('commands:info.ping.description', { lng: "en-GB" }), + uk: t('commands:info.ping.description', { lng: "uk" }), + ru: t('commands:info.ping.description', { lng: "ru" }) + }) .setDMPermission(false), options: { + cooldown: 30, ownerOnly: false, devGuildOnly: true, - cooldown: 0, bot_permissions: ["ViewChannel", "SendMessages"], }, async execute(interaction) { const { client } = interaction; - await interaction.reply({ content: `Pong!\nWebsocket ping: **${client.ws.ping}**ms`, ephemeral: true }); + await interaction.deferReply() + + const reply = await interaction.fetchReply() + + await interaction.editReply({ content: t('commands:info.ping.content', { lng: interaction.locale, ping: Math.round(client.ws.ping), latency: Math.round(reply.createdTimestamp - interaction.createdTimestamp) }) }); }, }; diff --git a/commands/sample/message sample.js b/commands/sample/message sample.js index b605472..ab98c34 100644 --- a/commands/sample/message sample.js +++ b/commands/sample/message sample.js @@ -1,15 +1,20 @@ const { ContextMenuCommandBuilder, ApplicationCommandType } = require("discord.js"); +const { t } = require("i18next"); /** * @type {import("../../typings").Command} */ module.exports = { data: new ContextMenuCommandBuilder() - .setName("message sample") + .setName(t('commands:sample.message_sample.description', { lng: "en" }).slice(0, 32)) + .setNameLocalizations({ + uk: t('commands:sample.message_sample.description', { lng: "uk" }).slice(0, 32), + ru: t('commands:sample.message_sample.description', { lng: "ru" }).slice(0, 32) + }) .setType(ApplicationCommandType.Message) .setDMPermission(false), options: { - cooldown: 0, + cooldown: 10, ownerOnly: false, devGuildOnly: true, bot_permissions: ["ViewChannel", "SendMessages"], @@ -19,9 +24,9 @@ module.exports = { if (!interaction.isMessageContextMenuCommand()) return; const { targetId, targetMessage } = interaction; - + await interaction.reply({ - content: `Це відповідь на команду контекстного меню повідомлення\nID повідомлення: ${targetId}\nКонтент повідомлення: ${targetMessage}`, + content: t('commands:sample.message_sample.content', { lng: interaction.locale, id: targetId, message: targetMessage.content }), ephemeral: true }); }, diff --git a/commands/sample/sample.js b/commands/sample/sample.js index dd2e177..80db7eb 100644 --- a/commands/sample/sample.js +++ b/commands/sample/sample.js @@ -1,4 +1,5 @@ -const { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, EmbedBuilder } = require("discord.js"); +const { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, EmbedBuilder, UserSelectMenuBuilder, ChannelSelectMenuBuilder, MentionableSelectMenuBuilder, RoleSelectMenuBuilder } = require("discord.js"); +const { t } = require("i18next"); const buttonPagination = require("../../utils/buttonPagination"); const buttonWrapper = require("../../utils/buttonWrapper"); @@ -8,15 +9,27 @@ const buttonWrapper = require("../../utils/buttonWrapper"); module.exports = { data: new SlashCommandBuilder() .setName("sample") - .setDescription("Sample Autocomplete/Button/Menu/Modal.") + .setDescription(t('commands:sample.sample.description', { lng: "en" })) + .setDescriptionLocalizations({ + uk: t('commands:sample.sample.description', { lng: "uk" }), + ru: t('commands:sample.sample.description', { lng: "ru" }) + }) .addSubcommand(subcommand => subcommand .setName("autocomplete") - .setDescription("Sample Autocomplete") + .setDescription(t('commands:sample.sample.autocomplete.description', { lng: "en" })) + .setDescriptionLocalizations({ + uk: t('commands:sample.sample.autocomplete.description', { lng: "uk" }), + ru: t('commands:sample.sample.autocomplete.description', { lng: "ru" }) + }) .addStringOption(option => option .setName("input") - .setDescription("Input") + .setDescription(t('commands:sample.sample.autocomplete.options.input', { lng: "en" })) + .setDescriptionLocalizations({ + uk: t('commands:sample.sample.autocomplete.options.input', { lng: "uk" }), + ru: t('commands:sample.sample.autocomplete.options.input', { lng: "ru" }) + }) .setAutocomplete(true) .setRequired(true) ) @@ -24,33 +37,54 @@ module.exports = { .addSubcommand(subcommand => subcommand .setName("button") - .setDescription("Sample Button") + .setDescription(t('commands:sample.sample.button.description', { lng: "en" })) + .setDescriptionLocalizations({ + uk: t('commands:sample.sample.button.description', { lng: "uk" }), + ru: t('commands:sample.sample.button.description', { lng: "ru" }) + }) ) .addSubcommand(subcommand => subcommand .setName("menu") .setDescription("Sample Menu") + .setDescription(t('commands:sample.sample.menu.description', { lng: "en" })) + .setDescriptionLocalizations({ + uk: t('commands:sample.sample.menu.description', { lng: "uk" }), + ru: t('commands:sample.sample.menu.description', { lng: "ru" }) + }) ) .addSubcommand(subcommand => subcommand .setName("modal") - .setDescription("Sample Modal") + .setDescription(t('commands:sample.sample.modal.description', { lng: "en" })) + .setDescriptionLocalizations({ + uk: t('commands:sample.sample.modal.description', { lng: "uk" }), + ru: t('commands:sample.sample.modal.description', { lng: "ru" }) + }) ) .addSubcommand(subcommand => subcommand .setName("pagination") - .setDescription("Sample Pagination") + .setDescription(t('commands:sample.sample.pagination.description', { lng: "en" })) + .setDescriptionLocalizations({ + uk: t('commands:sample.sample.pagination.description', { lng: "uk" }), + ru: t('commands:sample.sample.pagination.description', { lng: "ru" }) + }) ) .addSubcommand(subcommand => subcommand .setName("buttonwrapper") - .setDescription("Sample Button Wrapper") + .setDescription(t('commands:sample.sample.buttonwrapper.description', { lng: "en" })) + .setDescriptionLocalizations({ + uk: t('commands:sample.sample.buttonwrapper.description', { lng: "uk" }), + ru: t('commands:sample.sample.buttonwrapper.description', { lng: "ru" }) + }) ) .setDMPermission(false), options: { + cooldown: 10, ownerOnly: false, devGuildOnly: true, - cooldown: 0, bot_permissions: ["ViewChannel", "SendMessages"], }, @@ -74,8 +108,7 @@ module.exports = { .setLabel("sample button") ); - await interaction.reply({ - content: "Кнопка", + await interaction.reply({ components: [row] }); } break; @@ -83,18 +116,37 @@ module.exports = { case "menu": { const row = new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() - .setCustomId("sample") + .setCustomId("string_sample") .setOptions( { label: "sample option", value: "sample_option" + }, + { + label: "sample option_two", + value: "sample_option_two" } ) ); - await interaction.reply({ - content: "Меню", - components: [row] + const row2 = new ActionRowBuilder().addComponents( + new UserSelectMenuBuilder().setCustomId("user_sample") + ); + + const row3 = new ActionRowBuilder().addComponents( + new MentionableSelectMenuBuilder().setCustomId("mentionable_sample") + ); + + const row4 = new ActionRowBuilder().addComponents( + new ChannelSelectMenuBuilder().setCustomId("channel_sample") + ); + + const row5 = new ActionRowBuilder().addComponents( + new RoleSelectMenuBuilder().setCustomId("role_sample") + ); + + await interaction.reply({ + components: [row, row2, row3, row4, row5] }); } break; @@ -117,17 +169,10 @@ module.exports = { } break; case "pagination": { - let pageStrings = [ - "Котики найкращі :)", - "Песики найкращі :)", - "Україна понад усе!", - "Слава Україні! Героям Слава!", - "Крутий шаблон бота" - ]; let embeds = []; - for (let i = 0; i < 4; i++) { - embeds.push(new EmbedBuilder().setColor(0x2b2d31).setDescription(`${pageStrings[i]}`)); + for (let i = 0; i < 5; i++) { + embeds.push(new EmbedBuilder().setColor(0x2b2d31).setDescription(`${t(`commands:sample.sample.pagination.pageStrings.${[i]}`, { lng: interaction.locale })}`)); } await buttonPagination(interaction, embeds); @@ -138,15 +183,15 @@ module.exports = { new ButtonBuilder() .setCustomId("say_hello") .setStyle(ButtonStyle.Secondary) - .setLabel("Натисни на мене"), + .setLabel(t('commands:sample.sample.buttonwrapper.say_hello', { lng: interaction.locale })), new ButtonBuilder() - .setLabel("Найкращий шаблон Discord бота") + .setLabel(t('commands:sample.sample.buttonwrapper.cool_template', { lng: interaction.locale })) .setStyle(ButtonStyle.Link) .setURL("https://github.com/GamesTwoLife/DiscordBot-Template"), ]; await interaction.reply({ - content: "Натискайте на кнопки знизу:", + content: t('commands:sample.sample.buttonwrapper.content', { lng: interaction.locale }), components: buttonWrapper(buttons) }); } break; diff --git a/commands/sample/user sample.js b/commands/sample/user sample.js index a0da33e..894f9b9 100644 --- a/commands/sample/user sample.js +++ b/commands/sample/user sample.js @@ -1,15 +1,20 @@ const { ContextMenuCommandBuilder, ApplicationCommandType } = require("discord.js"); +const { t } = require("i18next"); /** * @type {import("../../typings").Command} */ module.exports = { data: new ContextMenuCommandBuilder() - .setName("user sample") + .setName(t('commands:sample.user_sample.description', { lng: "en" }).slice(0, 32)) + .setNameLocalizations({ + uk: t('commands:sample.user_sample.description', { lng: "uk" }).slice(0, 32), + ru: t('commands:sample.user_sample.description', { lng: "ru" }).slice(0, 32) + }) .setType(ApplicationCommandType.User) .setDMPermission(false), options: { - cooldown: 0, + cooldown: 10, ownerOnly: false, devGuildOnly: true, bot_permissions: ["ViewChannel", "SendMessages"], @@ -19,9 +24,9 @@ module.exports = { if (!interaction.isUserContextMenuCommand()) return; const { targetId, targetMember, targetUser} = interaction; - + await interaction.reply({ - content: `Це відповідь на команду контекстного меню користувача\nID користувача: ${targetId}\nУчасник/Користувач: ${targetMember} / ${targetUser}`, + content: t('commands:sample.user_sample.content', { lng: interaction.locale, id: targetId, member: targetMember.toString(), user: targetUser.toString() }), ephemeral: true }); }, diff --git a/components/autocomplete/category/sample.js b/components/autocomplete/sample/sample.js similarity index 51% rename from components/autocomplete/category/sample.js rename to components/autocomplete/sample/sample.js index e7e4c42..a004311 100644 --- a/components/autocomplete/category/sample.js +++ b/components/autocomplete/sample/sample.js @@ -1,13 +1,16 @@ /** - * @type {import("../../../typings").AutocompleteInteraction} + * @type {import("../../../typings").Autocomplete} */ module.exports = { name: "sample", + type: "autocomplete", async execute(interaction) { + if (!interaction.isAutocomplete()) return; + const { options } = interaction; - const choices = ['Популярні теми: Потоки', 'Шардинг: Початок роботи', 'Бібліотека: Голосові з’єднання', 'Взаємодії: Відповідь на (/) команди', 'Популярні теми: Попередній перегляд для вставлення']; + const choices = ['Popular Topics: Threads', 'Sharding: Getting started', 'Library: Voice Connections', 'Interactions: Replying to slash commands', 'Popular Topics: Embed preview']; const focusedValue = options.getFocused(); if (focusedValue.length === 0) { @@ -18,7 +21,7 @@ module.exports = { const filtered = choices.filter(choice => choice.startsWith(focusedValue)); - await interaction.respond( + return interaction.respond( filtered.map(choice => ({ name: choice, value: choice })), ); }, diff --git a/components/buttons/category/sample.js b/components/buttons/category/sample.js deleted file mode 100644 index 75d09a5..0000000 --- a/components/buttons/category/sample.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @type {import("../../../typings").ButtonInteraction} - */ -module.exports = { - id: "sample", - - async execute(interaction) { - await interaction.reply({ content: `${interaction.customId}`, ephemeral: true }); - - return; - }, -}; diff --git a/components/buttons/category/say_hello.js b/components/buttons/category/say_hello.js deleted file mode 100644 index c0a62f8..0000000 --- a/components/buttons/category/say_hello.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @type {import("../../../typings").ButtonInteraction} - */ -module.exports = { - id: "say_hello", - - async execute(interaction) { - await interaction.reply({ content: `Привіт, ${interaction.user}!`, ephemeral: true }); - - return; - }, -}; diff --git a/components/buttons/sample/sample.js b/components/buttons/sample/sample.js new file mode 100644 index 0000000..800efd0 --- /dev/null +++ b/components/buttons/sample/sample.js @@ -0,0 +1,13 @@ +/** + * @type {import("../../../typings").Button} + */ +module.exports = { + name: "sample", + type: "button", + + async execute(interaction) { + if (!interaction.isButton()) return; + + return interaction.reply({ content: `${interaction.customId}`, ephemeral: true }); + }, +}; diff --git a/components/buttons/sample/say_hello.js b/components/buttons/sample/say_hello.js new file mode 100644 index 0000000..1dcdcc3 --- /dev/null +++ b/components/buttons/sample/say_hello.js @@ -0,0 +1,13 @@ +/** + * @type {import("../../../typings").Button} + */ +module.exports = { + name: "say_hello", + type: "button", + + async execute(interaction) { + if (!interaction.isButton()) return; + + return interaction.reply({ content: `Hello, ${interaction.user}!`, ephemeral: true }); + }, +}; diff --git a/components/modals/category/sample.js b/components/modals/category/sample.js deleted file mode 100644 index e17cd2f..0000000 --- a/components/modals/category/sample.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @type {import("../../../typings").ModalInteraction} - */ -module.exports = { - id: "sample", - - async execute(interaction) { - const { fields } = interaction; - - const input = fields.getTextInputValue('input'); - - await interaction.reply({ content: `${input}`, ephemeral: true }); - - return; - }, -}; diff --git a/components/modals/sample/sample.js b/components/modals/sample/sample.js new file mode 100644 index 0000000..483df1e --- /dev/null +++ b/components/modals/sample/sample.js @@ -0,0 +1,17 @@ +/** + * @type {import("../../../typings").Modal} + */ +module.exports = { + name: "sample", + type: "modalSubmit", + + async execute(interaction) { + if (!interaction.isModalSubmit()) return; + + const { fields } = interaction; + + const input = fields.getTextInputValue('input'); + + return interaction.reply({ content: `${input}`, ephemeral: true }); + }, +}; diff --git a/components/selectmenu/category/sample.js b/components/selectmenu/category/sample.js deleted file mode 100644 index 2962e76..0000000 --- a/components/selectmenu/category/sample.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @type {import("../../../typings").SelectMenuInteraction} - */ -module.exports = { - id: "sample", - - async execute(interaction) { - if (!interaction.isStringSelectMenu()) return; - - await interaction.reply({ content: `${interaction.values[0]}`, ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/components/selectmenu/sample/channel_sample.js b/components/selectmenu/sample/channel_sample.js new file mode 100644 index 0000000..be68a6d --- /dev/null +++ b/components/selectmenu/sample/channel_sample.js @@ -0,0 +1,13 @@ +/** + * @type {import("../../../typings").ChannelSelectMenu} + */ +module.exports = { + name: "channel_sample", + type: "selectmenu", + + async execute(interaction) { + if (!interaction.isChannelSelectMenu()) return; + + return interaction.reply({ content: `<#${interaction.values[0]}> ${interaction.values[0]}`, ephemeral: true }); + }, +}; \ No newline at end of file diff --git a/components/selectmenu/sample/mentionable_sample.js b/components/selectmenu/sample/mentionable_sample.js new file mode 100644 index 0000000..b5fb24f --- /dev/null +++ b/components/selectmenu/sample/mentionable_sample.js @@ -0,0 +1,13 @@ +/** + * @type {import("../../../typings").MentionableSelectMenu} + */ +module.exports = { + name: "mentionable_sample", + type: "selectmenu", + + async execute(interaction) { + if (!interaction.isMentionableSelectMenu()) return; + + return interaction.reply({ content: `${interaction.values[0]}`, ephemeral: true }); + }, +}; \ No newline at end of file diff --git a/components/selectmenu/sample/role_sample.js b/components/selectmenu/sample/role_sample.js new file mode 100644 index 0000000..37bfebe --- /dev/null +++ b/components/selectmenu/sample/role_sample.js @@ -0,0 +1,13 @@ +/** + * @type {import("../../../typings").RoleSelectMenu} + */ +module.exports = { + name: "role_sample", + type: "selectmenu", + + async execute(interaction) { + if (!interaction.isRoleSelectMenu()) return; + + return interaction.reply({ content: `<@&${interaction.values[0]}> ${interaction.values[0]}`, ephemeral: true }); + }, +}; \ No newline at end of file diff --git a/components/selectmenu/sample/string_sample.js b/components/selectmenu/sample/string_sample.js new file mode 100644 index 0000000..635e255 --- /dev/null +++ b/components/selectmenu/sample/string_sample.js @@ -0,0 +1,13 @@ +/** + * @type {import("../../../typings").StringSelectMenu} + */ +module.exports = { + name: "string_sample", + type: "selectmenu", + + async execute(interaction) { + if (!interaction.isStringSelectMenu()) return; + + return interaction.reply({ content: `${interaction.values[0]}`, ephemeral: true }); + }, +}; \ No newline at end of file diff --git a/components/selectmenu/sample/user_sample.js b/components/selectmenu/sample/user_sample.js new file mode 100644 index 0000000..dc868a1 --- /dev/null +++ b/components/selectmenu/sample/user_sample.js @@ -0,0 +1,13 @@ +/** + * @type {import("../../../typings").UserSelectMenu} + */ +module.exports = { + name: "user_sample", + type: "selectmenu", + + async execute(interaction) { + if (!interaction.isUserSelectMenu()) return; + + return interaction.reply({ content: `<@${interaction.values[0]}> ${interaction.values[0]}`, ephemeral: true }); + }, +}; \ No newline at end of file diff --git a/events/Interaction/autocompleteInteraction.js b/events/Interaction/autocompleteInteraction.js index f602e78..b39609b 100644 --- a/events/Interaction/autocompleteInteraction.js +++ b/events/Interaction/autocompleteInteraction.js @@ -1,4 +1,5 @@ -const { Events } = require("discord.js") +const { Events, Collection } = require("discord.js"); +const { developers } = require("../../config.json"); module.exports = { name: Events.InteractionCreate, @@ -7,19 +8,25 @@ module.exports = { * @param {import('discord.js').AutocompleteInteraction & { client: import('../../typings').MainClient }} interaction */ async execute(interaction) { - const { client } = interaction; + const { client, guild, user } = interaction; if (!interaction.isAutocomplete()) return; try { - const autocomplete = client.autocompletes.get(interaction.commandName); + const autocompletes = client.components.get(interaction.commandName)?.filter(component => component.type === "autocomplete"); - if (!autocomplete) return interaction.respond([]).catch(() => {}); + if (!autocompletes) return interaction.respond([]); - await autocomplete.execute(interaction); + for (const autocomplete of autocompletes) { + if (autocomplete.options && autocomplete.options?.ownerOnly && !developers.includes(user.id)) { + return interaction.respond([]); + } + + return autocomplete.execute(interaction); + } } catch (error) { console.log(error); - return interaction.respond([]).catch(() => {}); + return interaction.respond([]); } }, }; \ No newline at end of file diff --git a/events/Interaction/butonInteraction.js b/events/Interaction/butonInteraction.js index 165148e..abdbbe1 100644 --- a/events/Interaction/butonInteraction.js +++ b/events/Interaction/butonInteraction.js @@ -1,5 +1,6 @@ -const { Events, Collection } = require("discord.js") -const { developers } = require("../../config.json"); +const { Events, Collection } = require("discord.js"); +const { developers, guildId } = require("../../config.json"); +const { t } = require("i18next"); module.exports = { name: Events.InteractionCreate, @@ -8,65 +9,80 @@ module.exports = { * @param {import('discord.js').ButtonInteraction & { client: import('../../typings').MainClient }} interaction */ async execute(interaction) { - const { client, user } = interaction; + const { client, guild, user } = interaction; + const { cooldowns } = client; if (!interaction.isButton()) return; try { - const button = client.buttons.get(interaction.customId); + const buttons = client.components.get(interaction.customId)?.filter(component => component.type === "button"); - if (!button) return; + if (!buttons) return; - if (button.options && button.options?.ownerOnly && !developers.includes(user.id)) { - return interaction.reply({ content: "Ця кнопка лише для розробників бота!", ephemeral: true }); - } - - const { cooldowns } = client; - - if (!cooldowns.has(interaction.customId)) { - cooldowns.set(interaction.customId, new Collection()); - } - - const now = Date.now(); - const timestamps = cooldowns.get(interaction.customId); - const cooldownAmount = (button.options?.cooldown || 0) * 1000; - - if (timestamps.has(user.id)) { - const expirationTime = timestamps.get(user.id) + cooldownAmount; - - if (now < expirationTime) { - const timeLeft = (expirationTime - now) / 1000; - - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ - content: `Зачекайте **${timeLeft.toFixed(1)}** секунд(и), перед повторним використанням кнопки ${interaction.customId}`, - ephemeral: true, - }).catch(() => {}); - } else { - return interaction.reply({ - content: `Зачекайте **${timeLeft.toFixed(1)}** секунд(и), перед повторним використанням кнопки ${interaction.customId}`, - ephemeral: true, - }).catch(() => {}); + for (const button of buttons) { + if (button.options && button.options?.ownerOnly && !developers.includes(user.id)) { + return interaction.reply({ content: t('common:events.Interaction.only_developer', { lng: interaction.locale, member: user.toString() }), ephemeral: true }); + } + + if (button.options && button.options?.bot_permissions && !guild.members.me.permissions.has(button.options?.bot_permissions)) { + const permsBot = button.options?.bot_permissions?.map(x => x).join(', '); + + return interaction.reply({ content: t('common:events.Interaction.missing_permissions', { lng: interaction.locale, member: user.toString(), permissions: permsBot }), ephemeral: true }); + } + + if (!cooldowns.has(interaction.customId)) { + cooldowns.set(interaction.customId, new Collection()); + } + + const now = Date.now(); + const timestamps = cooldowns.get(interaction.customId); + const cooldownAmount = (button.options?.cooldown ?? 5) * 1000; + + if (timestamps.has(user.id)) { + const expirationTime = timestamps.get(user.id) + cooldownAmount; + + if (now < expirationTime) { + const expiredTimestamp = Math.round(expirationTime / 1000); + + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.cooldown_button', { lng: interaction.locale, member: user.toString(), buttonId: interaction.customId, expiredTimestamp }), + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.cooldown_button', { lng: interaction.locale, member: user.toString(), buttonId: interaction.customId, expiredTimestamp }), + ephemeral: true + }); + } else { + return interaction.reply({ + content: t('common:events.Interaction.cooldown_button', { lng: interaction.locale, member: user.toString(), buttonId: interaction.customId, expiredTimestamp }), + ephemeral: true + }); + } } } + + timestamps.set(user.id, now); + setTimeout(() => timestamps.delete(user.id), cooldownAmount); + + return button.execute(interaction); } - - timestamps.set(user.id, now); - setTimeout(() => timestamps.delete(user.id), cooldownAmount); - - await button.execute(interaction); } catch (error) { console.log(error); - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ - content: `Виникла помилка \`${error.message}\` при виконанні кнопки \`${interaction.customId}\``, - ephemeral: true - }).catch(() => {}); - } else { + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }) + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); + } else { return interaction.reply({ - content: `Виникла помилка \`${error.message}\` при виконанні кнопки \`${interaction.customId}\``, - ephemeral: true - }).catch(() => {}); + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); } } }, diff --git a/events/Interaction/chatInputInteractionCreate.js b/events/Interaction/chatInputInteractionCreate.js new file mode 100644 index 0000000..b366b91 --- /dev/null +++ b/events/Interaction/chatInputInteractionCreate.js @@ -0,0 +1,93 @@ +const { Events, Collection } = require("discord.js") +const { developers } = require("../../config.json"); +const { t } = require("i18next"); + +module.exports = { + name: Events.InteractionCreate, + /** + * + * @param {import('discord.js').CommandInteraction & { client: import('../../typings').MainClient }} interaction + */ + async execute(interaction) { + const { client, guild, user } = interaction; + + if (!interaction.isChatInputCommand()) return; + + try { + const command = client.commands.get(interaction.commandName); + + if (!command) { + return interaction.reply({ content: t('common:events.Interaction.no_command', { lng: interaction.locale, member: user.toString() }), ephemeral: true }); + } + + if (command.options && command.options?.ownerOnly && !developers.includes(user.id)) { + return interaction.reply({ content: t('common:events.Interaction.only_developer', { lng: interaction.locale, member: user.toString() }), ephemeral: true }); + } + + if (command.options && command.options?.bot_permissions && !guild.members.me.permissions.has(command.options?.bot_permissions)) { + const permsBot = command.options?.bot_permissions?.map(x => x).join(', '); + + return interaction.reply({ content: t('common:events.Interaction.missing_permissions', { lng: interaction.locale, member: user.toString(), permissions: permsBot }), ephemeral: true }); + } + + const { cooldowns } = client; + + const subCommandNames = interaction.options?.data?.filter(sub => sub.type === 1).map(sub => sub.name); + const commandName = subCommandNames.length > 0 ? `${interaction.commandName} ${subCommandNames[0]}` : interaction.commandName + + if (!cooldowns.has(commandName)) { + cooldowns.set(commandName, new Collection()); + } + + const now = Date.now(); + const timestamps = cooldowns.get(commandName); + const cooldownAmount = (command.options?.cooldown ?? 5) * 1000; + + if (timestamps.has(user.id)) { + const expirationTime = timestamps.get(user.id) + cooldownAmount; + + if (now < expirationTime) { + const expiredTimestamp = Math.round(expirationTime / 1000); + + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.cooldown_command', { lng: interaction.locale, member: user.toString(), commandName: commandName, expiredTimestamp }), + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.cooldown_command', { lng: interaction.locale, member: user.toString(), commandName: commandName, expiredTimestamp }), + ephemeral: true + }); + } else { + return interaction.reply({ + content: t('common:events.Interaction.cooldown_command', { lng: interaction.locale, member: user.toString(), commandName: commandName, expiredTimestamp }), + ephemeral: true + }); + } + } + } + + timestamps.set(user.id, now); + setTimeout(() => timestamps.delete(user.id), cooldownAmount); + + return command.execute(interaction); + } catch (error) { + console.log(error); + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }) + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); + } else { + return interaction.reply({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); + } + } + }, +}; \ No newline at end of file diff --git a/events/Interaction/contextMenuInteractionCreate.js b/events/Interaction/contextMenuInteractionCreate.js index 484b022..bc90f48 100644 --- a/events/Interaction/contextMenuInteractionCreate.js +++ b/events/Interaction/contextMenuInteractionCreate.js @@ -1,4 +1,6 @@ -const { Events, Collection } = require("discord.js") +const { Events, Collection } = require("discord.js"); +const { developers } = require("../../config.json"); +const { t } = require("i18next"); module.exports = { name: Events.InteractionCreate, @@ -16,20 +18,17 @@ module.exports = { const command = client.commands.get(interaction.commandName); if (!command) { - return interaction.reply({ content: "Нажаль, такої контекстної команди не існує!", ephemeral: true }).catch(() => {}); + return interaction.reply({ content: t('common:events.Interaction.no_command', { lng: interaction.locale, member: user.toString() }), ephemeral: true }); } - + if (command.options && command.options?.ownerOnly && !developers.includes(user.id)) { - return interaction.reply({ content: "Ця контекстна команда лише для розробників бота!", ephemeral: true }).catch(() => {}); + return interaction.reply({ content: t('common:events.Interaction.only_developer', { lng: interaction.locale, member: user.toString() }), ephemeral: true }); } - + if (command.options && command.options?.bot_permissions && !guild.members.me.permissions.has(command.options?.bot_permissions)) { const permsBot = command.options?.bot_permissions?.map(x => x).join(', '); - - return interaction.reply({ - content: `Мені не вистачає таких дозволів: ${permsBot}. Для виконання контекстної команди "${interaction.commandName}"`, - ephemeral: true - }).catch(() => {}); + + return interaction.reply({ content: t('common:events.Interaction.missing_permissions', { lng: interaction.locale, member: user.toString(), permissions: permsBot }), ephemeral: true }); } const { cooldowns } = client; @@ -40,24 +39,28 @@ module.exports = { const now = Date.now(); const timestamps = cooldowns.get(interaction.commandName); - const cooldownAmount = (command.options?.cooldown || 0) * 1000; + const cooldownAmount = (command.options?.cooldown ?? 5) * 1000; if (timestamps.has(user.id)) { const expirationTime = timestamps.get(user.id) + cooldownAmount; if (now < expirationTime) { - const timeLeft = (expirationTime - now) / 1000; + const expiredTimestamp = Math.round(expirationTime / 1000); - if (interaction.deferred || interaction.replied) { + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.cooldown_command', { lng: interaction.locale, member: user.toString(), commandName: interaction.commandName, expiredTimestamp }), + }); + } else if (interaction.replied) { return interaction.followUp({ - content: `Зачекайте **${timeLeft.toFixed(1)}** секунд(и), перед повторним використанням контекстної команди `, - ephemeral: true, - }).catch(() => {}); + content: t('common:events.Interaction.cooldown_command', { lng: interaction.locale, member: user.toString(), commandName: interaction.commandName, expiredTimestamp }), + ephemeral: true + }); } else { return interaction.reply({ - content: `Зачекайте **${timeLeft.toFixed(1)}** секунд(и), перед повторним використанням контекстної команди `, - ephemeral: true, - }).catch(() => {}); + content: t('common:events.Interaction.cooldown_command', { lng: interaction.locale, member: user.toString(), commandName: interaction.commandName, expiredTimestamp }), + ephemeral: true + }); } } } @@ -68,23 +71,38 @@ module.exports = { await command.execute(interaction); } catch (error) { console.log(error); - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ content: `Виникла помилка при виконанні контекстної команди "${interaction.commandName}"`, ephemeral: true }).catch(() => {}); + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); } else { - return interaction.reply({ content: `Виникла помилка при виконанні контекстної команди "${interaction.commandName}"`, ephemeral: true }).catch(() => {}); + return interaction.reply({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); } } } else { - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ - content: "Щось дивне відбувається у контекстному меню. Отримано контекстне меню невідомого типу.", - ephemeral: true - }).catch(() => {}); - } else { + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.something_strange', { lng: interaction.locale, member: user.toString() }) + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.something_strange', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); + } else { return interaction.reply({ - content: "Щось дивне відбувається у контекстному меню. Отримано контекстне меню невідомого типу.", - ephemeral: true - }).catch(() => {}); + content: t('common:events.Interaction.something_strange', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); } } }, diff --git a/events/Interaction/interactionCreate.js b/events/Interaction/interactionCreate.js deleted file mode 100644 index 6f5b2e6..0000000 --- a/events/Interaction/interactionCreate.js +++ /dev/null @@ -1,81 +0,0 @@ -const { Events, Collection } = require("discord.js") -const { developers } = require("../../config.json"); - -module.exports = { - name: Events.InteractionCreate, - /** - * - * @param {import('discord.js').CommandInteraction & { client: import('../../typings').MainClient }} interaction - */ - async execute(interaction) { - const { client, guild, user } = interaction; - - if (!interaction.isChatInputCommand()) return; - - try { - const command = client.commands.get(interaction.commandName); - - if (!command) { - return interaction.reply({ content: "Нажаль, такої команди не існує або виникла якась неочікувана помилка!", ephemeral: true }); - } - - if (command.options && command.options?.ownerOnly && !developers.includes(user.id)) { - return interaction.reply({ content: "Ця команда лише для розробників бота!", ephemeral: true }); - } - - if (command.options && command.options?.bot_permissions && !guild.members.me.permissions.has(command.options?.bot_permissions)) { - const permsBot = command.options?.bot_permissions?.map(x => x).join(', '); - - return interaction.reply({ content: `Мені не вистачає таких дозволів: ${permsBot}. Для виконання команди `, ephemeral: true }); - } - - const { cooldowns } = client; - - if (!cooldowns.has(interaction.commandName)) { - cooldowns.set(interaction.commandName, new Collection()); - } - - const now = Date.now(); - const timestamps = cooldowns.get(interaction.commandName); - const cooldownAmount = (command.options?.cooldown || 3) * 1000; - - if (timestamps.has(user.id)) { - const expirationTime = timestamps.get(user.id) + cooldownAmount; - - if (now < expirationTime) { - const timeLeft = (expirationTime - now) / 1000; - - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ - content: `Зачекайте **${timeLeft.toFixed(1)}** секунд(и), перед повторним використанням команди `, - ephemeral: true, - }).catch(() => {}); - } else { - return interaction.reply({ - content: `Зачекайте **${timeLeft.toFixed(1)}** секунд(и), перед повторним використанням команди `, - ephemeral: true, - }).catch(() => {}); - } - } - } - - timestamps.set(user.id, now); - setTimeout(() => timestamps.delete(user.id), cooldownAmount); - - await command.execute(interaction); - } catch (error) { - console.log(error); - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ - content: `Виникла помилка \`${error.message}\` при виконанні команди `, - ephemeral: true - }).catch(() => {}); - } else { - return interaction.reply({ - content: `Виникла помилка \`${error.message}\` при виконанні команди `, - ephemeral: true - }).catch(() => {}); - } - } - }, -}; \ No newline at end of file diff --git a/events/Interaction/modalInteraction.js b/events/Interaction/modalInteraction.js index e7fafe5..aeaa73b 100644 --- a/events/Interaction/modalInteraction.js +++ b/events/Interaction/modalInteraction.js @@ -1,4 +1,6 @@ -const { Events } = require("discord.js") +const { Events, Collection } = require("discord.js"); +const { developers } = require("../../config.json"); +const { t } = require("i18next"); module.exports = { name: Events.InteractionCreate, @@ -7,28 +9,81 @@ module.exports = { * @param {import('discord.js').ModalSubmitInteraction & { client: import('../../typings').MainClient }} interaction */ async execute(interaction) { - const { client } = interaction; + const { client, guild, user } = interaction; if (!interaction.isModalSubmit()) return; try { - const modal = client.modals.get(interaction.customId); + const modals = client.components.get(interaction.customId)?.filter(component => component.type === "modalSubmit"); - if (!modal) return; + if (!modals) return; - await modal.execute(interaction); + for (const modal of modals) { + if (modal.options && modal.options?.ownerOnly && !developers.includes(user.id)) { + return interaction.reply({ content: t('common:events.Interaction.no_command', { lng: interaction.locale, member: user.toString() }), ephemeral: true }); + } + + if (modal.options && modal.options?.bot_permissions && !guild.members.me.permissions.has(modal.options?.bot_permissions)) { + const permsBot = modal.options?.bot_permissions?.map(x => x).join(', '); + + return interaction.reply({ content: t('common:events.Interaction.missing_permissions', { lng: interaction.locale, member: user.toString(), permissions: permsBot }), ephemeral: true }); + } + + const { cooldowns } = client; + + if (!cooldowns.has(interaction.customId)) { + cooldowns.set(interaction.customId, new Collection()); + } + + const now = Date.now(); + const timestamps = cooldowns.get(interaction.customId); + const cooldownAmount = (modal.options?.cooldown ?? 5) * 1000; + + if (timestamps.has(user.id)) { + const expirationTime = timestamps.get(user.id) + cooldownAmount; + + if (now < expirationTime) { + const expiredTimestamp = Math.round(expirationTime / 1000); + + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.cooldown_modal', { lng: interaction.locale, member: user.toString(), modalId: interaction.customId, expiredTimestamp }), + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.cooldown_modal', { lng: interaction.locale, member: user.toString(), modalId: interaction.customId, expiredTimestamp }), + ephemeral: true + }); + } else { + return interaction.reply({ + content: t('common:events.Interaction.cooldown_modal', { lng: interaction.locale, member: user.toString(), modalId: interaction.customId, expiredTimestamp }), + ephemeral: true + }); + } + } + } + + timestamps.set(user.id, now); + setTimeout(() => timestamps.delete(user.id), cooldownAmount); + + return modal.execute(interaction); + } } catch (error) { console.log(error); - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ - content: `Виникла помилка \`${error.message}\` при виконанні модального вікна ${interaction.customId}`, - ephemeral: true - }).catch(() => {}); - } else { + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }) + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); + } else { return interaction.reply({ - content: `Виникла помилка \`${error.message}\` при виконанні модального вікна ${interaction.customId}`, - ephemeral: true - }).catch(() => {}); + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); } } }, diff --git a/events/Interaction/selectMenuInteraction.js b/events/Interaction/selectMenuInteraction.js index 682f730..3126dfd 100644 --- a/events/Interaction/selectMenuInteraction.js +++ b/events/Interaction/selectMenuInteraction.js @@ -1,5 +1,6 @@ -const { Events, Collection } = require("discord.js") -const { developers } = require("../../config.json"); +const { Events, Collection } = require("discord.js"); +const { developers, guildId } = require("../../config.json"); +const { t } = require("i18next"); module.exports = { name: Events.InteractionCreate, @@ -14,58 +15,74 @@ module.exports = { if (!interaction.isAnySelectMenu()) return; try { - const selectmenu = client.selectMenus.get(interaction.customId); + const selectmenus = client.components.get(interaction.customId)?.filter(component => component.type === "selectmenu"); - if (!selectmenu) return; + if (!selectmenus) return; - if (selectmenu.options && selectmenu.options?.ownerOnly && !developers.includes(user.id)) { - return interaction.reply({ content: "Це меню вибору лише для розробників бота!", ephemeral: true }); - } - - if (!cooldowns.has(interaction.customId)) { - cooldowns.set(interaction.customId, new Collection()); - } - - const now = Date.now(); - const timestamps = cooldowns.get(interaction.customId); - const cooldownAmount = (selectmenu.options?.cooldown || 0) * 1000; - - if (timestamps.has(user.id)) { - const expirationTime = timestamps.get(user.id) + cooldownAmount; - - if (now < expirationTime) { - const timeLeft = (expirationTime - now) / 1000; + for (const selectmenu of selectmenus) { + if (selectmenu.options && selectmenu.options?.ownerOnly && !developers.includes(user.id)) { + return interaction.reply({ content: t('common:events.Interaction.no_command', { lng: interaction.locale, member: user.toString() }), ephemeral: true }); + } - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ - content: `Зачекайте **${timeLeft.toFixed(1)}** секунд(и), перед повторним використанням меню ${interaction.customId}`, - ephemeral: true, - }).catch(() => {}); - } else { - return interaction.reply({ - content: `Зачекайте **${timeLeft.toFixed(1)}** секунд(и), перед повторним використанням меню ${interaction.customId}`, - ephemeral: true, - }).catch(() => {}); + if (selectmenu.options && selectmenu.options?.bot_permissions && !guild.members.me.permissions.has(selectmenu.options?.bot_permissions)) { + const permsBot = selectmenu.options?.bot_permissions?.map(x => x).join(', '); + + return interaction.reply({ content: t('common:events.Interaction.missing_permissions', { lng: interaction.locale, member: user.toString(), permissions: permsBot }), ephemeral: true }); + } + + if (!cooldowns.has(interaction.customId)) { + cooldowns.set(interaction.customId, new Collection()); + } + + const now = Date.now(); + const timestamps = cooldowns.get(interaction.customId); + const cooldownAmount = (selectmenu.options?.cooldown ?? 5) * 1000; + + if (timestamps.has(user.id)) { + const expirationTime = timestamps.get(user.id) + cooldownAmount; + + if (now < expirationTime) { + const expiredTimestamp = Math.round(expirationTime / 1000); + + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.cooldown_menu', { lng: interaction.locale, member: user.toString(), menuId: interaction.customId, expiredTimestamp }), + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.cooldown_menu', { lng: interaction.locale, member: user.toString(), menuId: interaction.customId, expiredTimestamp }), + ephemeral: true + }); + } else { + return interaction.reply({ + content: t('common:events.Interaction.cooldown_menu', { lng: interaction.locale, member: user.toString(), menuId: interaction.customId, expiredTimestamp }), + ephemeral: true + }); + } } } + + timestamps.set(user.id, now); + setTimeout(() => timestamps.delete(user.id), cooldownAmount); + + return selectmenu.execute(interaction); } - - timestamps.set(user.id, now); - setTimeout(() => timestamps.delete(user.id), cooldownAmount); - - await selectmenu.execute(interaction); } catch (error) { console.log(error); - if (interaction.deferred || interaction.replied) { - return interaction.followUp({ - content: `Виникла помилка \`${error.message}\` при виконанні меню вибору \`${interaction.customId}\``, - ephemeral: true - }).catch(() => {}); - } else { + if (interaction.deferred) { + return interaction.editReply({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }) + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); + } else { return interaction.reply({ - content: `Виникла помилка \`${error.message}\` при виконанні меню вибору \`${interaction.customId}\``, - ephemeral: true - }).catch(() => {}); + content: t('common:events.Interaction.error_occured', { lng: interaction.locale, member: user.toString() }), + ephemeral: true + }); } } }, diff --git a/index.js b/index.js index b7cf2bc..14772db 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,9 @@ const SlashUpdate = require("./handlers/SlashUpdate"); const GuildDB = require("./db/guilds"); const UserDB = require("./db/users"); +const i18next = require("i18next"); +const resources = require("./locales/resources"); + /** * @type {import("./typings").MainClient} * @description The main client of the program @@ -36,6 +39,19 @@ const client = new Client({ } }); +client.i18n = i18next.init({ + fallbackLng: 'en', + defaultNS: 'common', + interpolation: { + escapeValue: false, + }, + resources: { + en: resources.en, + uk: resources.uk, + ru: resources.ru + } +}); + client.commands = new Collection(); client.cooldowns = new Collection(); client.components = new Collection(); diff --git a/locales/en/commands.json b/locales/en/commands.json new file mode 100644 index 0000000..5cf8d64 --- /dev/null +++ b/locales/en/commands.json @@ -0,0 +1,53 @@ +{ + "info": { + "ping": { + "description": "Bot ping and latency", + "content": "Discord Websocket Ping **{{ping}}**ms\nAnswer to commands: **{{latency}}**ms" + } + }, + "sample": { + "message_sample": { + "description": "message sample", + "content": "This is the response to the message context menu command \nMessage ID: {{id}}\nMessage content: {{message}}" + }, + "sample": { + "description": "Sample Autocomplete/Button/Menu/ModalSubmit.", + "autocomplete": { + "description": "Sample Autocomplete.", + "options": { + "input": "Input" + } + }, + "button": { + "description": "Sample Button." + }, + "menu": { + "description": "Sample Menu." + }, + "modal": { + "description": "Sample modal submit." + }, + "pagination": { + "description": "Sample Pagination.", + "pageStrings": [ + "Cats are the best :)", + "Dogs are the best :)", + "Ukraine is above all!" , + "Glory to Ukraine! Glory to the Heroes!" , + "Cool bot template" + ], + "footer": "Page {{currentPage}}/{{totalPages}}" + }, + "buttonwrapper": { + "description": "Sample Button Wrapper.", + "content": "Click on the buttons below:", + "say_hello": "Click me", + "cool_template": "The best Discord bot template" + } + }, + "user_sample": { + "description": "user sample", + "content": "This is the response to the user context menu command\nUser ID: {{id}}\nMember/User: {{member}} / {{user}}" + } + } +} \ No newline at end of file diff --git a/locales/en/common.json b/locales/en/common.json new file mode 100644 index 0000000..ef08a5e --- /dev/null +++ b/locales/en/common.json @@ -0,0 +1,15 @@ +{ + "events": { + "Interaction": { + "no_command": "{{member}}, the command does not exist or something strange happened", + "only_developer": "{{member}}, this interaction is for bot developers only", + "missing_permissions": "{{member}}, I need the following permissions to perform this interaction: {{permissions}}", + "cooldown_command": "{{member}}, The `{{commandName}}` command usage limit has been reached, try again ", + "cooldown_button": "{{member}}, The `{{buttonId}}` button usage limit has been reached, try again ", + "cooldown_menu": "{{member}}, The `{{menuId}}` menu has reached its usage limit, try again ", + "cooldown_modal": "{{member}}, Modal submit `{{modalId}}` has reached its usage limit, try again ", + "error_occured": "{{member}}, My friend, it looks like there was an unforeseen error, we have already tracked it down and will fix it soon", + "something_strange": "{{member}}, Something strange is happening in the context menu. A context menu of unknown type has been received." + } + } +} \ No newline at end of file diff --git a/locales/resources.js b/locales/resources.js new file mode 100644 index 0000000..c769417 --- /dev/null +++ b/locales/resources.js @@ -0,0 +1,21 @@ +const common_en = require("./en/common.json"); +const common_uk = require("./uk/common.json"); +const common_ru = require("./ru/common.json"); +const commands_en = require("./en/commands.json"); +const commands_uk = require("./uk/commands.json"); +const commands_ru = require("./ru/commands.json"); + +module.exports = { + en: { + common: common_en, + commands: commands_en + }, + uk: { + common: common_uk, + commands: commands_uk + }, + ru: { + common: common_ru, + commands: commands_ru + } +} \ No newline at end of file diff --git a/locales/ru/commands.json b/locales/ru/commands.json new file mode 100644 index 0000000..9178974 --- /dev/null +++ b/locales/ru/commands.json @@ -0,0 +1,53 @@ +{ + "info": { + "ping": { + "description": "Пинг и Задержка бота", + "content": "Discord Websocket Пинг **{{ping}}**мс\nОтвет на команди: **{{latency}}**мс" + } + }, + "sample": { + "message_sample": { + "description": "образец сообщения", + "content": "Это ответ на команду контекстного меню сообщения\nID сообщения: {{id}}\nКонтент сообщения: {{message}}" + }, + "sample": { + "description": "Образец автозаполнения/кнопки/меню/модальной отправки.", + "autocomplete": { + "description": "Образец автозаполнения.", + "options": { + "input": "Ввести" + } + }, + "button": { + "description": "Образец кнопки." + }, + "menu": { + "description": "Образец меню." + }, + "modal": { + "description": "Образец модальной отправки." + }, + "pagination": { + "description": "Образец пагинации.", + "pageStrings": [ + "Котики самые лучшие :)", + "Собачки самые лучшие :)", + "Украина превыше всего!", + "Слава Украине! Героям Слава!", + "Крутой шаблон бота" + ], + "footer": "Страница {{currentPage}}/{{totalPages}}" + }, + "buttonwrapper": { + "description": "Образец обертки для кнопок.", + "content": "Нажимайте на кнопки внизу:", + "say_hello": "Нажми на меня", + "cool_template": "Самый лучший шаблон Discord бота" + } + }, + "user_sample": { + "description": "образец пользователя", + "content": "Это ответ на команду контекстного меню пользователя\nID пользователя: {{id}}\nУчастник/Пользователь: {{member}} / {{user}}}" + } + } +} \ No newline at end of file diff --git a/locales/ru/common.json b/locales/ru/common.json new file mode 100644 index 0000000..d8107bd --- /dev/null +++ b/locales/ru/common.json @@ -0,0 +1,16 @@ +{ + "events": { + "Interaction": { + "no_guild": "{{member}}, Это взаимодействие может быть выполнено только на сервере", + "no_command": "{{member}}, Команда не существует или что-то странное произошло", + "only_developer": "{{member}}, Это взаимодействие предназначено только для разработчиков бота", + "missing_permissions": "{{member}}, Мне нужны следующие разрешения для выполнения этого взаимодействия: {{permissions}}", + "cooldown_command": "{{member}}, Достигнут лимит использования команды `{{commandName}}`, попробуйте еще раз ", + "cooldown_button": "{{member}}, Достигнут лимит использования кнопки `{{buttonId}}`, попробуйте еще раз ", + "cooldown_menu": "{{member}}, Достигнут лимит использования меню `{{menuId}}`, попробуйте еще раз ", + "cooldown_modal": "{{member}}, Достигнут лимит использования модального представления `{{modalId}}`, попробуйте еще раз ", + "error_occured": "{{member}}, Ой друг, похоже, произошла непредсказуемая ошибка, мы уже отследили ее и скоро исправим", + "something_strange": "{{member}}, Что-то странное происходит в контекстном меню. Получено контекстное меню неизвестного типа." + } + } +} \ No newline at end of file diff --git a/locales/uk/commands.json b/locales/uk/commands.json new file mode 100644 index 0000000..91e7a54 --- /dev/null +++ b/locales/uk/commands.json @@ -0,0 +1,53 @@ +{ + "info": { + "ping": { + "description": "Пінг і Затримка бота", + "content": "Discord Websocket Пінг **{{ping}}**мс\nВідповідь на команди: **{{latency}}**мс" + } + }, + "sample": { + "message_sample": { + "description": "зразок повідомлення", + "content": "Це відповідь на команду контекстного меню повідомлення\nID повідомлення: {{id}}\nКонтент повідомлення: {{message}}" + }, + "sample": { + "description": "Приклад автозаповнення/кнопка/меню/модальние відправлення.", + "autocomplete": { + "description": "Зразок автозаповнення.", + "options": { + "input": "Введення" + } + }, + "button": { + "description": "Зразок кнопки." + }, + "menu": { + "description": "Зразок меню." + }, + "modal": { + "description": "Зразок модального відправлення." + }, + "pagination": { + "description": "Приклад пагінації.", + "pageStrings": [ + "Котики найкращі :)", + "Песики найкращі :)", + "Україна понад усе!", + "Слава Україні! Героям Слава!", + "Крутий шаблон бота" + ], + "footer": "Сторінка {{currentPage}}/{{totalPages}}" + }, + "buttonwrapper": { + "description": "Зразок обгортки для кнопок.", + "content": "Натискайте на кнопки знизу:", + "say_hello": "Натисни на мене", + "cool_template": "Найкращий шаблон Discord бота" + } + }, + "user_sample": { + "description": "зразок користувача", + "content": "Це відповідь на команду контекстного меню користувача\nID користувача: {{id}}\nУчасник/Користувач: {{member}} / {{user}}" + } + } +} \ No newline at end of file diff --git a/locales/uk/common.json b/locales/uk/common.json new file mode 100644 index 0000000..e2f8e83 --- /dev/null +++ b/locales/uk/common.json @@ -0,0 +1,15 @@ +{ + "events": { + "Interaction": { + "no_command": "{{member}}, Команда не існує або щось дивне сталось", + "only_developer": "{{member}}, Ця взаємодія призначена лише для розробників бота", + "missing_permissions": "{{member}}, Мені потрібні наступні дозволи для виконання цієї взаємодії: {{permissions}}", + "cooldown_command": "{{member}}, Досягнуто ліміт використання команди `{{commandName}}`, спробуйте ще раз ", + "cooldown_button": "{{member}}, Досягнуто ліміт використання кнопки `{{buttonId}}`, спробуйте ще раз ", + "cooldown_menu": "{{member}}, Досягнуто ліміт використання меню `{{menuId}}`, спробуйте ще раз ", + "cooldown_modal": "{{member}}, Досягнуто ліміт використання модального подання `{{modalId}}`, спробуйте ще раз ", + "error_occured": "{{member}}, Йой друже, схоже, сталася непередбачувана помилка, ми вже відстежили її і незабаром виправимо", + "something_strange": "{{member}}, Щось дивне відбувається у контекстному меню. Отримано контекстне меню невідомого типу." + } + } +} \ No newline at end of file diff --git a/utils/buttonPagination.js b/utils/buttonPagination.js index b58f7bf..32e9d3f 100644 --- a/utils/buttonPagination.js +++ b/utils/buttonPagination.js @@ -1,4 +1,5 @@ const { ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilder } = require("discord.js"); +const { t } = require("i18next"); /** * @@ -10,8 +11,8 @@ const { ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilde */ module.exports = async (interaction, pages, time = 30 * 1000, emojis = ["⏪", "🏠", "⏩"]) => { try { - if (!interaction || !pages || pages.length === 0) throw new Error("Неправильні аргументи"); - if (emojis.length < 3 || emojis.length > 3) throw new Error("Вказано менше або більше 3 емодзі в масиві"); + if (!interaction || !pages || pages.length === 0) throw new Error("Wrong arguments"); + if (emojis.length < 3 || emojis.length > 3) throw new Error("Less than or more than 3 emojis in the array are specified"); await interaction.deferReply(); @@ -42,9 +43,9 @@ module.exports = async (interaction, pages, time = 30 * 1000, emojis = ["⏪", " const buttons = new ActionRowBuilder().addComponents([prev, home, next]); let index = 0; - + const msg = await interaction.editReply({ - embeds: [pages[index].setFooter({ text: `Сторінка ${index + 1}/${pages.length}` })], + embeds: [pages[index].setFooter({ text: t('commands:sample.sample.pagination.footer', { lng: interaction.locale, currentPage: index + 1, totalPages: pages.length }) })], components: [buttons], fetchReply: true }); @@ -82,7 +83,7 @@ module.exports = async (interaction, pages, time = 30 * 1000, emojis = ["⏪", " } await msg.edit({ - embeds: [pages[index].setFooter({ text: `Сторінка ${index + 1}/${pages.length}` })], + embeds: [pages[index].setFooter({ text: t('commands:sample.sample.pagination.footer', { lng: interaction.locale, currentPage: index + 1, totalPages: pages.length }) })], components: [buttons] }); @@ -106,14 +107,18 @@ module.exports = async (interaction, pages, time = 30 * 1000, emojis = ["⏪", " return msg; } catch(err) { console.log(err); - if (interaction.deferred || interaction.replied) { - await interaction.followUp({ - content: `Виникла помилка: ${err.message}`, + if (interaction.deferred) { + return interaction.editReply({ + content: `${err.message}` + }); + } else if (interaction.replied) { + return interaction.followUp({ + content: `${err.message}`, ephemeral: true }); - } else { - await interaction.reply({ - content: `Виникла помилка: ${err.message}`, + }else { + return interaction.reply({ + content: `${err.message}`, ephemeral: true }); }