From d624c62669692e9d14f7d226e2b0078991a216b4 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Sun, 23 Oct 2022 22:32:54 +0200 Subject: [PATCH 01/15] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Give=20credits=20hel?= =?UTF-8?q?per?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/credits/modules/work/index.ts | 43 +------- .../modules/credits/modules/give/index.ts | 103 +++++------------- .../messageCreate/modules/credits/index.ts | 20 +--- src/helpers/credits/index.ts | 56 ++++++++++ 4 files changed, 88 insertions(+), 134 deletions(-) diff --git a/src/commands/credits/modules/work/index.ts b/src/commands/credits/modules/work/index.ts index bca9dc4..a4b4ba9 100644 --- a/src/commands/credits/modules/work/index.ts +++ b/src/commands/credits/modules/work/index.ts @@ -6,6 +6,7 @@ import { command as CooldownCommand } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; import deferReply from "../../../../handlers/deferReply"; import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; +import { give as CreditsGive } from "../../../../helpers/credits"; import logger from "../../../../middlewares/logger"; // 1. Export a builder function. @@ -51,51 +52,13 @@ export const execute = async (interaction: CommandInteraction) => { max: createGuild.creditsWorkRate, }); - // 7. Upsert the guildMember in the database. - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: user.id, - guildId: guild.id, - }, - }, - update: { creditsEarned: { increment: creditsEarned } }, - create: { - creditsEarned, - user: { - connectOrCreate: { - create: { - id: user.id, - }, - where: { - id: user.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - }, - include: { - user: true, - guild: true, - }, - }); - logger.silly(createGuildMember); - if (!createGuildMember) throw new Error("GuildMember not found"); + const upsertGuildMember = await CreditsGive(guild, user, creditsEarned); // 8. Send embed. await interaction.editReply({ embeds: [ EmbedSuccess.setDescription( - `You worked and earned **${creditsEarned}** credits! You now have **${createGuildMember.creditsEarned}** credits. :tada:` + `You worked and earned **${creditsEarned}** credits! You now have **${upsertGuildMember.creditsEarned}** credits. :tada:` ), ], }); diff --git a/src/commands/manage/modules/credits/modules/give/index.ts b/src/commands/manage/modules/credits/modules/give/index.ts index f2c070b..2ecc4ea 100644 --- a/src/commands/manage/modules/credits/modules/give/index.ts +++ b/src/commands/manage/modules/credits/modules/give/index.ts @@ -1,21 +1,12 @@ -// Dependencies import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, -} from "discord.js"; -import logger from "../../../../../../middlewares/logger"; -// Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; -// Helpers../../../../../../../helpers/userData -import pluralize from "../../../../../../helpers/pluralize"; -// Models -// Handlers -import prisma from "../../../../../../handlers/database"; +import { ChatInputCommandInteraction, PermissionsBitField } from "discord.js"; + import deferReply from "../../../../../../handlers/deferReply"; +import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; import checkPermission from "../../../../../../helpers/checkPermission"; -// Function +import { give as CreditsGive } from "../../../../../../helpers/credits"; +import pluralize from "../../../../../../helpers/pluralize"; + export default { builder: (command: SlashCommandSubcommandBuilder) => { return command @@ -34,81 +25,41 @@ export default { .setRequired(true) ); }, + execute: async (interaction: ChatInputCommandInteraction) => { + // 1. Defer reply as ephemeral. await deferReply(interaction, true); + // 2. Check if the user has the MANAGE_GUILD permission. checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure + // 3. Destructure interaction object. const { guild, options } = interaction; + if (!guild) + throw new Error("We could not get the current guild from discord."); + if (!options) throw new Error("We could not get the options from discord."); - const discordReceiver = options?.getUser("user"); - const creditAmount = options?.getInteger("amount"); - - // If amount option is null - if (creditAmount === null) + // 4. Get the user and amount from the options. + const discordReceiver = options.getUser("user"); + const creditsAmount = options.getInteger("amount"); + if (typeof creditsAmount !== "number") throw new Error("You need to provide a credit amount."); - - // If amount is zero or below - if (creditAmount <= 0) - throw new Error("You must provide a credit amount greater than zero"); - - if (discordReceiver === null) + if (!discordReceiver) throw new Error("We could not get the receiving user from Discord"); - if (guild === null) - throw new Error("We could not get the current guild from discord."); + // 5. Create base embeds. + const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Give"); - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: discordReceiver.id, - guildId: guild.id, - }, - }, - update: { creditsEarned: { increment: creditAmount } }, - create: { - creditsEarned: creditAmount, - user: { - connectOrCreate: { - create: { - id: discordReceiver.id, - }, - where: { - id: discordReceiver.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - }, - }); + // 6. Give the credits. + await CreditsGive(guild, discordReceiver, creditsAmount); - logger.silly(createGuildMember); - - // Save toUser - await interaction?.editReply({ + // 7. Send embed. + return await interaction.editReply({ embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Give)") - .setDescription( - `Successfully gave ${pluralize(creditAmount, "credit")}` - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embedSuccess.setDescription( + `Successfully gave ${pluralize(creditsAmount, "credit")}` + ), ], }); - return; }, }; diff --git a/src/events/messageCreate/modules/credits/index.ts b/src/events/messageCreate/modules/credits/index.ts index 2ae1ac8..39534d1 100644 --- a/src/events/messageCreate/modules/credits/index.ts +++ b/src/events/messageCreate/modules/credits/index.ts @@ -1,6 +1,7 @@ import { ChannelType, Message } from "discord.js"; import { message as CooldownMessage } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; +import { give as CreditsGive } from "../../../../helpers/credits"; import logger from "../../../../middlewares/logger"; export default { @@ -58,23 +59,6 @@ export default { ); if (isOnCooldown) return; - const updateGuildMember = await prisma.guildMember.update({ - where: { - userId_guildId: { - userId: author.id, - guildId: guild.id, - }, - }, - data: { - creditsEarned: { - increment: createGuildMember.guild.creditsRate, - }, - }, - }); - - logger.silly(updateGuildMember); - - if (!updateGuildMember) - throw new Error("Failed to update guildMember object"); + await CreditsGive(guild, author, createGuildMember.guild.creditsRate); }, }; diff --git a/src/helpers/credits/index.ts b/src/helpers/credits/index.ts index 13bb6af..bacec5d 100644 --- a/src/helpers/credits/index.ts +++ b/src/helpers/credits/index.ts @@ -115,3 +115,59 @@ export const transfer = async ( return recipient; }); }; + +// Give to guildMember +export const give = async (guild: Guild, user: User, amount: number) => { + // 1. Verify that the amount is not above 100.000.000 credits. + if (amount > 100000000) { + throw new Error("You can't give more than 1.000.000 credits."); + } + + // 2. Verify that the amount is not below 1 credits. + if (amount <= 0) { + throw new Error("You can't give below one credit."); + } + + // 3. Verify that the user is not an bot. + if (user.bot) { + throw new Error("You can't give to an bot."); + } + + // 4. Increment the user's balance by amount. + return await prisma.guildMember.upsert({ + update: { + creditsEarned: { + increment: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: amount, + }, + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + }); +}; From cf3f3449e82a9e2a478917b76f70c3b5966c1bd2 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Sun, 23 Oct 2022 22:33:16 +0200 Subject: [PATCH 02/15] =?UTF-8?q?=F0=9F=94=8A=20Added=20query=20logging=20?= =?UTF-8?q?for=20Prisma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers/database/index.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/handlers/database/index.ts b/src/handlers/database/index.ts index c4e22e4..dff0994 100644 --- a/src/handlers/database/index.ts +++ b/src/handlers/database/index.ts @@ -1,3 +1,20 @@ -import { PrismaClient } from '@prisma/client' +import { PrismaClient } from "@prisma/client"; +import logger from "../../middlewares/logger"; -export default new PrismaClient() +const prisma = new PrismaClient(); + +prisma.$use(async (params, next) => { + const before = Date.now(); + + const result = await next(params); + + const after = Date.now(); + + logger.debug( + `Query ${params.model}.${params.action} took ${after - before}ms` + ); + + return result; +}); + +export default prisma; From 976e8e7fdce19adf05fde2324d25badac1b5c249 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Sun, 23 Oct 2022 23:07:54 +0200 Subject: [PATCH 03/15] =?UTF-8?q?=F0=9F=9A=9A=20one=20file=20per=20credit?= =?UTF-8?q?=20transaction=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/credits/modules/gift/index.ts | 4 +- src/commands/credits/modules/work/index.ts | 4 +- .../modules/credits/modules/give/index.ts | 4 +- .../modules/credits/modules/set/index.ts | 125 +++---------- .../modules/credits/modules/take/index.ts | 150 +++------------ .../modules/credits/modules/transfer/index.ts | 4 +- .../messageCreate/modules/credits/index.ts | 4 +- src/helpers/credits/give.ts | 54 ++++++ src/helpers/credits/index.ts | 173 ------------------ src/helpers/credits/set.ts | 52 ++++++ src/helpers/credits/take.ts | 55 ++++++ src/helpers/credits/transactionRules.ts | 18 ++ src/helpers/credits/transfer.ts | 97 ++++++++++ 13 files changed, 335 insertions(+), 409 deletions(-) create mode 100644 src/helpers/credits/give.ts delete mode 100644 src/helpers/credits/index.ts create mode 100644 src/helpers/credits/set.ts create mode 100644 src/helpers/credits/take.ts create mode 100644 src/helpers/credits/transactionRules.ts create mode 100644 src/helpers/credits/transfer.ts diff --git a/src/commands/credits/modules/gift/index.ts b/src/commands/credits/modules/gift/index.ts index 5401cd1..bd3a46b 100644 --- a/src/commands/credits/modules/gift/index.ts +++ b/src/commands/credits/modules/gift/index.ts @@ -5,7 +5,7 @@ import { import deferReply from "../../../../handlers/deferReply"; import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; -import { transfer as CreditsTransfer } from "../../../../helpers/credits"; +import creditsTransfer from "../../../../helpers/credits/transfer"; // 1. Export a builder function. export const builder = (command: SlashCommandSubcommandBuilder) => { @@ -55,7 +55,7 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { const EmbedSuccess = await BaseEmbedSuccess(guild, "[:dollar:] Gift"); // 5. Start an transaction of the credits. - await CreditsTransfer(guild, user, target, credits); + await creditsTransfer(guild, user, target, credits); // 6. Tell the target that they have been gifted credits. await target.send({ diff --git a/src/commands/credits/modules/work/index.ts b/src/commands/credits/modules/work/index.ts index a4b4ba9..71e2c3d 100644 --- a/src/commands/credits/modules/work/index.ts +++ b/src/commands/credits/modules/work/index.ts @@ -6,7 +6,7 @@ import { command as CooldownCommand } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; import deferReply from "../../../../handlers/deferReply"; import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; -import { give as CreditsGive } from "../../../../helpers/credits"; +import creditsGive from "../../../../helpers/credits/give"; import logger from "../../../../middlewares/logger"; // 1. Export a builder function. @@ -52,7 +52,7 @@ export const execute = async (interaction: CommandInteraction) => { max: createGuild.creditsWorkRate, }); - const upsertGuildMember = await CreditsGive(guild, user, creditsEarned); + const upsertGuildMember = await creditsGive(guild, user, creditsEarned); // 8. Send embed. await interaction.editReply({ diff --git a/src/commands/manage/modules/credits/modules/give/index.ts b/src/commands/manage/modules/credits/modules/give/index.ts index 2ecc4ea..2b0e1f9 100644 --- a/src/commands/manage/modules/credits/modules/give/index.ts +++ b/src/commands/manage/modules/credits/modules/give/index.ts @@ -4,7 +4,7 @@ import { ChatInputCommandInteraction, PermissionsBitField } from "discord.js"; import deferReply from "../../../../../../handlers/deferReply"; import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; import checkPermission from "../../../../../../helpers/checkPermission"; -import { give as CreditsGive } from "../../../../../../helpers/credits"; +import creditsGive from "../../../../../../helpers/credits/give"; import pluralize from "../../../../../../helpers/pluralize"; export default { @@ -51,7 +51,7 @@ export default { const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Give"); // 6. Give the credits. - await CreditsGive(guild, discordReceiver, creditsAmount); + await creditsGive(guild, discordReceiver, creditsAmount); // 7. Send embed. return await interaction.editReply({ diff --git a/src/commands/manage/modules/credits/modules/set/index.ts b/src/commands/manage/modules/credits/modules/set/index.ts index 10dd37f..ed7deb4 100644 --- a/src/commands/manage/modules/credits/modules/set/index.ts +++ b/src/commands/manage/modules/credits/modules/set/index.ts @@ -1,21 +1,14 @@ -// Dependencies -// Helpers -// Models -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChatInputCommandInteraction, - EmbedBuilder, PermissionsBitField, + SlashCommandSubcommandBuilder, } from "discord.js"; -// Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; -// Handlers -import prisma from "../../../../../../handlers/database"; -import deferReply from "../../../../../../handlers/deferReply"; -import checkPermission from "../../../../../../helpers/checkPermission"; -import logger from "../../../../../../middlewares/logger"; -// Function +import deferReply from "../../../../../../handlers/deferReply"; +import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import creditsSet from "../../../../../../helpers/credits/set"; + export default { builder: (command: SlashCommandSubcommandBuilder) => { return command @@ -35,107 +28,35 @@ export default { ); }, execute: async (interaction: ChatInputCommandInteraction) => { + // 1. Defer reply as ephemeral. await deferReply(interaction, true); + // 2. Check if the user has the permission to manage the guild. checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); + // 3. Destructure interaction object. const { options, guild } = interaction; + if (!guild) throw new Error(`We could not find this guild.`); + if (!options) throw new Error(`We could not find the options.`); + // 4. Get the user and amount from the options. const discordUser = options.getUser("user"); const creditAmount = options.getInteger("amount"); + if (typeof creditAmount !== "number") throw new Error("Amount is not set."); + if (!discordUser) throw new Error("User is not specified"); - // If amount is null - if (creditAmount === null) { - logger?.silly(`Amount is null`); + // 5. Set the credits. + await creditsSet(guild, discordUser, creditAmount); - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription(`You must provide an amount.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } + // 6. Create base embeds. + const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Set"); - if (discordUser === null) { - logger?.silly(`User is null`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription(`You must provide a user.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - if (guild === null) { - logger?.silly(`Guild is null`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription(`You must provide a guild.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: discordUser.id, - guildId: guild.id, - }, - }, - update: { creditsEarned: creditAmount }, - create: { - creditsEarned: creditAmount, - user: { - connectOrCreate: { - create: { - id: discordUser.id, - }, - where: { - id: discordUser.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - }, - }); - - logger.silly(createGuildMember); - - return interaction?.editReply({ + // 7. Send embed. + return await interaction.editReply({ embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Set)") - .setDescription( - `Set **${discordUser}**'s credits to **${creditAmount}**.` - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embedSuccess.setDescription( + `Set **${discordUser}**'s credits to **${creditAmount}**.` + ), ], }); }, diff --git a/src/commands/manage/modules/credits/modules/take/index.ts b/src/commands/manage/modules/credits/modules/take/index.ts index 9e808af..340eb80 100644 --- a/src/commands/manage/modules/credits/modules/take/index.ts +++ b/src/commands/manage/modules/credits/modules/take/index.ts @@ -1,22 +1,15 @@ -// Dependencies -// Models -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChatInputCommandInteraction, - EmbedBuilder, PermissionsBitField, + SlashCommandSubcommandBuilder, } from "discord.js"; -// Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; -// Helpers../../../../../../../helpers/userData -import pluralize from "../../../../../../helpers/pluralize"; -// Handlers -import prisma from "../../../../../../handlers/database"; -import deferReply from "../../../../../../handlers/deferReply"; -import checkPermission from "../../../../../../helpers/checkPermission"; -import logger from "../../../../../../middlewares/logger"; -// Function +import deferReply from "../../../../../../handlers/deferReply"; +import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import creditsTake from "../../../../../../helpers/credits/take"; +import pluralize from "../../../../../../helpers/pluralize"; + export default { builder: (command: SlashCommandSubcommandBuilder) => { return command @@ -36,127 +29,36 @@ export default { ); }, execute: async (interaction: ChatInputCommandInteraction) => { + // 1. Defer reply as ephemeral. await deferReply(interaction, true); + // 2. Check if the user has the MANAGE_GUILD permission. checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); // Destructure + // 3. Destructure interaction object. const { guild, options } = interaction; + if (!guild) throw new Error("Invalid guild."); + if (!options) throw new Error("Invalid options."); - // User option - const discordReceiver = options?.getUser("user"); + // 4. Get the user and amount from the options. + const discordReceiver = options.getUser("user"); + const optionAmount = options.getInteger("amount"); + if (typeof optionAmount !== "number") throw new Error("Invalid amount."); + if (!discordReceiver) throw new Error("Invalid user."); - // Amount option - const optionAmount = options?.getInteger("amount"); + // 5. Create base embeds. + const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Take"); - // If amount is null - if (optionAmount === null) { - logger?.silly(`Amount is null`); + // 6. Take the credits. + await creditsTake(guild, discordReceiver, optionAmount); - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`You must provide an amount.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - // If amount is zero or below - if (optionAmount <= 0) { - logger?.silly(`Amount is zero or below`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`You must provide an amount greater than zero.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - if (discordReceiver === null) { - logger?.silly(`Discord receiver is null`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`You must provide a user.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - if (guild === null) { - logger?.silly(`Guild is null`); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription(`You must be in a guild.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: discordReceiver.id, - guildId: guild.id, - }, - }, - update: { creditsEarned: { decrement: optionAmount } }, - create: { - creditsEarned: -optionAmount, - user: { - connectOrCreate: { - create: { - id: discordReceiver.id, - }, - where: { - id: discordReceiver.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - }, - }); - - logger.silly(createGuildMember); - await interaction?.editReply({ + // 7. Send embed. + return await interaction.editReply({ embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Take)") - .setDescription( - `Took ${pluralize(optionAmount, "credit")} from ${discordReceiver}.` - ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embedSuccess.setDescription( + `Took ${pluralize(optionAmount, "credit")} from ${discordReceiver}.` + ), ], }); - return; }, }; diff --git a/src/commands/manage/modules/credits/modules/transfer/index.ts b/src/commands/manage/modules/credits/modules/transfer/index.ts index 723d76d..93f8570 100644 --- a/src/commands/manage/modules/credits/modules/transfer/index.ts +++ b/src/commands/manage/modules/credits/modules/transfer/index.ts @@ -6,7 +6,7 @@ import { EmbedBuilder, PermissionsBitField, } from "discord.js"; -import { transfer as CreditsTransfer } from "../../../../../../helpers/credits"; +import creditsTransfer from "../../../../../../helpers/credits/transfer"; // Configurations import deferReply from "../../../../../../handlers/deferReply"; import checkPermission from "../../../../../../helpers/checkPermission"; @@ -65,7 +65,7 @@ export default { if (!optionToUser) throw new Error("You must provide a user to transfer to."); - await CreditsTransfer(guild, optionFromUser, optionToUser, optionAmount); + await creditsTransfer(guild, optionFromUser, optionToUser, optionAmount); return interaction?.editReply({ embeds: [ diff --git a/src/events/messageCreate/modules/credits/index.ts b/src/events/messageCreate/modules/credits/index.ts index 39534d1..4daa57e 100644 --- a/src/events/messageCreate/modules/credits/index.ts +++ b/src/events/messageCreate/modules/credits/index.ts @@ -1,7 +1,7 @@ import { ChannelType, Message } from "discord.js"; import { message as CooldownMessage } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; -import { give as CreditsGive } from "../../../../helpers/credits"; +import creditsGive from "../../../../helpers/credits/give"; import logger from "../../../../middlewares/logger"; export default { @@ -59,6 +59,6 @@ export default { ); if (isOnCooldown) return; - await CreditsGive(guild, author, createGuildMember.guild.creditsRate); + await creditsGive(guild, author, createGuildMember.guild.creditsRate); }, }; diff --git a/src/helpers/credits/give.ts b/src/helpers/credits/give.ts new file mode 100644 index 0000000..3882db4 --- /dev/null +++ b/src/helpers/credits/give.ts @@ -0,0 +1,54 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import transactionRules from "./transactionRules"; + +export default async (guild: Guild, user: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Check if the transaction is valid. + await transactionRules(guild, user, amount); + + // 2. Make the transaction. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: { + increment: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: amount, + }, + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + }); + + // 3. Verify that the recipient actually is created. + if (!recipient) throw new Error("No recipient available"); + + // 4. Return the recipient. + return recipient; + }); +}; diff --git a/src/helpers/credits/index.ts b/src/helpers/credits/index.ts deleted file mode 100644 index bacec5d..0000000 --- a/src/helpers/credits/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Guild, User } from "discord.js"; -import prisma from "../../handlers/database"; - -// Start an transaction between two users in a guild. -export const transfer = async ( - guild: Guild, - from: User, - to: User, - amount: number -) => { - return await prisma.$transaction(async (tx) => { - // 1. Decrement amount from the sender. - const sender = await tx.guildMember.upsert({ - update: { - creditsEarned: { - decrement: amount, - }, - }, - create: { - user: { - connectOrCreate: { - create: { - id: from.id, - }, - where: { - id: from.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - creditsEarned: -amount, - }, - where: { - userId_guildId: { - userId: from.id, - guildId: guild.id, - }, - }, - }); - - // 2. Verify that the sender actually is created. - if (!sender) throw new Error("No sender available"); - - // 3. Verify that the sender's balance exists. - if (!sender.creditsEarned) throw new Error("No credits available"); - - // 4. Verify that the sender's balance didn't go below zero. - if (sender.creditsEarned < 0) { - throw new Error(`${from} doesn't have enough to send ${amount}`); - } - - // 5. Verify that the sender is not trying to send less that one credits. - if (amount <= 0) { - throw new Error("You can't transfer below one credit."); - } - - // 6. Verify that the sender is not trying to send more than 100.000.000 credits. - if (amount > 100000000) { - throw new Error("You can't transfer more than 100.000.000 credits."); - } - - // 7. Verify that recipient are not an bot. - if (to.bot) throw new Error("You can't transfer to an bot."); - - // 8. Verify that sender and recipient are not the same user. - if (from.id === to.id) throw new Error("You can't transfer to yourself."); - - // 9. Increment the recipient's balance by amount. - const recipient = await tx.guildMember.upsert({ - update: { - creditsEarned: { - increment: amount, - }, - }, - create: { - user: { - connectOrCreate: { - create: { - id: to.id, - }, - where: { - id: to.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - creditsEarned: amount, - }, - where: { - userId_guildId: { - userId: to.id, - guildId: guild.id, - }, - }, - }); - - return recipient; - }); -}; - -// Give to guildMember -export const give = async (guild: Guild, user: User, amount: number) => { - // 1. Verify that the amount is not above 100.000.000 credits. - if (amount > 100000000) { - throw new Error("You can't give more than 1.000.000 credits."); - } - - // 2. Verify that the amount is not below 1 credits. - if (amount <= 0) { - throw new Error("You can't give below one credit."); - } - - // 3. Verify that the user is not an bot. - if (user.bot) { - throw new Error("You can't give to an bot."); - } - - // 4. Increment the user's balance by amount. - return await prisma.guildMember.upsert({ - update: { - creditsEarned: { - increment: amount, - }, - }, - create: { - user: { - connectOrCreate: { - create: { - id: user.id, - }, - where: { - id: user.id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - creditsEarned: amount, - }, - where: { - userId_guildId: { - userId: user.id, - guildId: guild.id, - }, - }, - }); -}; diff --git a/src/helpers/credits/set.ts b/src/helpers/credits/set.ts new file mode 100644 index 0000000..2db8cf7 --- /dev/null +++ b/src/helpers/credits/set.ts @@ -0,0 +1,52 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import transactionRules from "./transactionRules"; + +export default async (guild: Guild, user: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Check if the transaction is valid. + await transactionRules(guild, user, amount); + + // 2. Make the transaction. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: amount, + }, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: amount, + }, + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + }); + + // 3. Verify that the recipient actually is created. + if (!recipient) throw new Error("No recipient available"); + + // 4. Return the recipient. + return recipient; + }); +}; diff --git a/src/helpers/credits/take.ts b/src/helpers/credits/take.ts new file mode 100644 index 0000000..ae21ed6 --- /dev/null +++ b/src/helpers/credits/take.ts @@ -0,0 +1,55 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import transactionRules from "./transactionRules"; + +export default async (guild: Guild, user: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Check if the transaction is valid. + await transactionRules(guild, user, amount); + + // 2. Make the transaction. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: { + decrement: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: -amount, + }, + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + }); + + // 3. Verify that the recipient credits are not below zero. + if (recipient.creditsEarned < -100) + throw new Error("User do not have enough credits"); + + // 4. Return the recipient. + return recipient; + }); +}; diff --git a/src/helpers/credits/transactionRules.ts b/src/helpers/credits/transactionRules.ts new file mode 100644 index 0000000..021448f --- /dev/null +++ b/src/helpers/credits/transactionRules.ts @@ -0,0 +1,18 @@ +import { Guild, User } from "discord.js"; + +export default async (guild: Guild, user: User, amount: number) => { + // 1. Verify that the amount is not above 100.000.000 credits. + if (amount > 100000000) { + throw new Error("You can't give more than 1.000.000 credits."); + } + + // 2. Verify that the amount is not below 1 credits. + if (amount <= 0) { + throw new Error("You can't give below one credit."); + } + + // 3. Verify that the user is not an bot. + if (user.bot) { + throw new Error("You can't give to an bot."); + } +}; diff --git a/src/helpers/credits/transfer.ts b/src/helpers/credits/transfer.ts new file mode 100644 index 0000000..7177328 --- /dev/null +++ b/src/helpers/credits/transfer.ts @@ -0,0 +1,97 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import transactionRules from "./transactionRules"; + +export default async (guild: Guild, from: User, to: User, amount: number) => { + return await prisma.$transaction(async (tx) => { + // 1. Decrement amount from the sender. + const sender = await tx.guildMember.upsert({ + update: { + creditsEarned: { + decrement: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: from.id, + }, + where: { + id: from.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: -amount, + }, + where: { + userId_guildId: { + userId: from.id, + guildId: guild.id, + }, + }, + }); + + // 4. Verify that the sender's balance didn't go below zero. + if (sender.creditsEarned < 0) { + throw new Error(`${from} doesn't have enough to send ${amount}`); + } + + // 5. Check if the transactions is valid. + await transactionRules(guild, from, amount); + await transactionRules(guild, to, amount); + + // 6. Verify that sender and recipient are not the same user. + if (from.id === to.id) throw new Error("You can't transfer to yourself."); + + // 7. Increment the recipient's balance by amount. + const recipient = await tx.guildMember.upsert({ + update: { + creditsEarned: { + increment: amount, + }, + }, + create: { + user: { + connectOrCreate: { + create: { + id: to.id, + }, + where: { + id: to.id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + creditsEarned: amount, + }, + where: { + userId_guildId: { + userId: to.id, + guildId: guild.id, + }, + }, + }); + + return recipient; + }); +}; From c8fb97562367d57f39bc239b9211313819cc010c Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Sun, 23 Oct 2022 23:22:13 +0200 Subject: [PATCH 04/15] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20replaced=20*=20coold?= =?UTF-8?q?own=20functions=20with=201=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/credits/modules/work/index.ts | 6 +- src/commands/fun/modules/meme/index.ts | 8 +- src/commands/reputation/modules/give/index.ts | 10 +- .../handlers/button/index.ts | 23 +- .../handlers/command/index.ts | 19 -- .../messageCreate/modules/credits/index.ts | 11 +- .../messageCreate/modules/points/index.ts | 11 +- src/handlers/cooldown/index.ts | 277 ------------------ src/index.ts | 1 + src/middlewares/cooldown/index.ts | 101 +++++++ 10 files changed, 129 insertions(+), 338 deletions(-) delete mode 100644 src/handlers/cooldown/index.ts create mode 100644 src/middlewares/cooldown/index.ts diff --git a/src/commands/credits/modules/work/index.ts b/src/commands/credits/modules/work/index.ts index 71e2c3d..ec6ed6c 100644 --- a/src/commands/credits/modules/work/index.ts +++ b/src/commands/credits/modules/work/index.ts @@ -2,11 +2,11 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import Chance from "chance"; import { CommandInteraction } from "discord.js"; -import { command as CooldownCommand } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; import deferReply from "../../../../handlers/deferReply"; import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; import creditsGive from "../../../../helpers/credits/give"; +import cooldown from "../../../../middlewares/cooldown"; import logger from "../../../../middlewares/logger"; // 1. Export a builder function. @@ -20,7 +20,7 @@ export const execute = async (interaction: CommandInteraction) => { await deferReply(interaction, true); // 2. Destructure interaction object. - const { guild, user } = interaction; + const { guild, user, commandId } = interaction; if (!guild) throw new Error("Guild not found"); if (!user) throw new Error("User not found"); @@ -44,7 +44,7 @@ export const execute = async (interaction: CommandInteraction) => { if (!createGuild) throw new Error("Guild not found"); // 6. Create a cooldown for the user. - await CooldownCommand(interaction, createGuild.creditsWorkTimeout); + await cooldown(guild, user, commandId, createGuild.creditsWorkTimeout); // 6. Generate a random number between 0 and creditsWorkRate. const creditsEarned = chance.integer({ diff --git a/src/commands/fun/modules/meme/index.ts b/src/commands/fun/modules/meme/index.ts index 1ed1ea0..77e7e23 100644 --- a/src/commands/fun/modules/meme/index.ts +++ b/src/commands/fun/modules/meme/index.ts @@ -1,9 +1,9 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import axios from "axios"; import { CommandInteraction, EmbedBuilder } from "discord.js"; -import { command as CooldownCommand } from "../../../../handlers/cooldown"; import deferReply from "../../../../handlers/deferReply"; import getEmbedConfig from "../../../../helpers/getEmbedData"; +import cooldown from "../../../../middlewares/cooldown"; export default { builder: (command: SlashCommandSubcommandBuilder) => { @@ -13,9 +13,11 @@ export default { execute: async (interaction: CommandInteraction) => { await deferReply(interaction, false); - await CooldownCommand(interaction, 15); + const { guild, user, commandId } = interaction; + if (!guild) throw new Error("Guild not found"); + if (!user) throw new Error("User not found"); - const { guild } = interaction; + await cooldown(guild, user, commandId, 15); const embedConfig = await getEmbedConfig(guild); diff --git a/src/commands/reputation/modules/give/index.ts b/src/commands/reputation/modules/give/index.ts index 7adf7be..64e476c 100644 --- a/src/commands/reputation/modules/give/index.ts +++ b/src/commands/reputation/modules/give/index.ts @@ -1,12 +1,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChatInputCommandInteraction, EmbedBuilder } from "discord.js"; -import { command as CooldownCommand } from "../../../../handlers/cooldown"; import getEmbedConfig from "../../../../helpers/getEmbedData"; import logger from "../../../../middlewares/logger"; import noSelfReputation from "./components/noSelfReputation"; import prisma from "../../../../handlers/database"; import deferReply from "../../../../handlers/deferReply"; +import cooldown from "../../../../middlewares/cooldown"; export default { builder: (command: SlashCommandSubcommandBuilder) => { @@ -36,7 +36,7 @@ export default { execute: async (interaction: ChatInputCommandInteraction) => { await deferReply(interaction, true); - const { options, user, guild } = interaction; + const { options, user, guild, commandId } = interaction; const { successColor, footerText, footerIcon } = await getEmbedConfig( guild @@ -52,8 +52,10 @@ export default { noSelfReputation(optionTarget, user); // Check if user is on cooldown otherwise create one - await CooldownCommand( - interaction, + await cooldown( + guild, + user, + commandId, parseInt(process.env.REPUTATION_TIMEOUT) ); diff --git a/src/events/interactionCreate/handlers/button/index.ts b/src/events/interactionCreate/handlers/button/index.ts index e00365e..e3e8281 100644 --- a/src/events/interactionCreate/handlers/button/index.ts +++ b/src/events/interactionCreate/handlers/button/index.ts @@ -1,35 +1,14 @@ // Dependencies import { BaseInteraction } from "discord.js"; -import { button as CooldownButton } from "../../../../handlers/cooldown"; -import deferReply from "../../../../handlers/deferReply"; export default async (interaction: BaseInteraction) => { if (!interaction.isButton()) return; - const { guild, customId, memberPermissions } = interaction; + const { customId } = interaction; const currentButton = await import(`../../../buttons/${customId}`); if (!currentButton) throw new Error(`Unknown button ${customId}`); - const metadata = currentButton.metadata; - - await deferReply(interaction, metadata.ephemeral || false); - - if (metadata.guildOnly && !guild) - throw new Error("This command is guild only."); - - if ( - metadata.permissions && - metadata.guildOnly && - !memberPermissions?.has(metadata.permissions) - ) - throw new Error("You don't have the required permissions"); - - if (metadata.dmOnly && guild) - throw new Error("This command is only available in DM"); - - if (metadata.cooldown) await CooldownButton(interaction, metadata.cooldown); - await currentButton.execute(interaction); }; diff --git a/src/events/interactionCreate/handlers/command/index.ts b/src/events/interactionCreate/handlers/command/index.ts index 0e79da8..c49f7d2 100644 --- a/src/events/interactionCreate/handlers/command/index.ts +++ b/src/events/interactionCreate/handlers/command/index.ts @@ -8,24 +8,5 @@ export default async (interaction: ChatInputCommandInteraction) => { const currentCommand = client.commands.get(commandName); if (!currentCommand) throw new Error(`Unknown command ${commandName}`); - // const metadata = await getCommandMetadata(interaction, currentCommand); - // await deferReply(interaction, metadata.ephemeral || false); - - // if (metadata.guildOnly && !interaction.guild) - // throw new Error("This command is guild only."); - - // if (metadata.dmOnly && interaction.guild) - // throw new Error("This command is only available in DM"); - - // if ( - // metadata.permissions && - // metadata.guildOnly && - // !interaction.memberPermissions?.has(metadata.permissions) - // ) - // throw new Error("You don't have the required permissions"); - - // if (metadata.cooldown) { - // await CooldownCommand(interaction, metadata.cooldown); - // } await currentCommand.execute(interaction); }; diff --git a/src/events/messageCreate/modules/credits/index.ts b/src/events/messageCreate/modules/credits/index.ts index 4daa57e..b38ad47 100644 --- a/src/events/messageCreate/modules/credits/index.ts +++ b/src/events/messageCreate/modules/credits/index.ts @@ -1,7 +1,7 @@ import { ChannelType, Message } from "discord.js"; -import { message as CooldownMessage } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; import creditsGive from "../../../../helpers/credits/give"; +import cooldown from "../../../../middlewares/cooldown"; import logger from "../../../../middlewares/logger"; export default { @@ -52,12 +52,13 @@ export default { if (content.length < createGuildMember.guild.creditsMinimumLength) return; - const isOnCooldown = await CooldownMessage( - message, + await cooldown( + guild, + author, + "event-messageCreate-credits", createGuildMember.guild.creditsTimeout, - "messageCreate-credits" + true ); - if (isOnCooldown) return; await creditsGive(guild, author, createGuildMember.guild.creditsRate); }, diff --git a/src/events/messageCreate/modules/points/index.ts b/src/events/messageCreate/modules/points/index.ts index 583b6fa..d8f1494 100644 --- a/src/events/messageCreate/modules/points/index.ts +++ b/src/events/messageCreate/modules/points/index.ts @@ -1,6 +1,6 @@ import { ChannelType, Message } from "discord.js"; -import { message as CooldownMessage } from "../../../../handlers/cooldown"; import prisma from "../../../../handlers/database"; +import cooldown from "../../../../middlewares/cooldown"; import logger from "../../../../middlewares/logger"; export default { @@ -51,12 +51,13 @@ export default { if (content.length < createGuildMember.guild.pointsMinimumLength) return; - const isOnCooldown = await CooldownMessage( - message, + await cooldown( + guild, + author, + "event-messageCreate-points", createGuildMember.guild.pointsTimeout, - "messageCreate-points" + true ); - if (isOnCooldown) return; const updateGuildMember = await prisma.guildMember.update({ where: { diff --git a/src/handlers/cooldown/index.ts b/src/handlers/cooldown/index.ts deleted file mode 100644 index 6721ccd..0000000 --- a/src/handlers/cooldown/index.ts +++ /dev/null @@ -1,277 +0,0 @@ -// Dependencies -import { ButtonInteraction, CommandInteraction, Message } from "discord.js"; -import addSeconds from "../../helpers/addSeconds"; -import logger from "../../middlewares/logger"; -import prisma from "../database"; - -// Command cooldown -export const command = async (i: CommandInteraction, cooldown: number) => { - const { guild, user, commandId } = i; - - if (!guild) throw new Error("Guild not found"); - - // Check if user has a timeout - const hasTimeout = await prisma.cooldown.findUnique({ - where: { - guildId_userId_timeoutId: { - guildId: guild.id, - userId: user.id, - timeoutId: commandId, - }, - }, - }); - - logger.silly(hasTimeout); - - // If user is not on timeout - if (hasTimeout) { - const { userId, timeoutId, createdAt } = hasTimeout; - const overDue = addSeconds(cooldown, createdAt) < new Date(); - - if (!overDue) { - const diff = Math.round( - (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 - ); - - throw new Error( - `You must wait ${diff} seconds before using this command.` - ); - } - - // Delete timeout - const deleteCooldown = await prisma.cooldown.delete({ - where: { - guildId_userId_timeoutId: { - guildId: guild.id, - userId: user.id, - timeoutId: commandId, - }, - }, - }); - - logger.silly(deleteCooldown); - - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` - ); - } - - // Create timeout - const createCooldown = await prisma.cooldown.upsert({ - where: { - guildId_userId_timeoutId: { - userId: user.id, - guildId: guild.id, - timeoutId: commandId, - }, - }, - update: {}, - create: { - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - user: { - connectOrCreate: { - create: { - id: user.id, - }, - where: { - id: user.id, - }, - }, - }, - timeoutId: commandId, - cooldown, - }, - }); - - logger.silly(createCooldown); -}; - -// Button cooldown -export const button = async (i: ButtonInteraction, cooldown: number) => { - const { guild, user, customId } = i; - - if (!guild) throw new Error("Guild not found"); - - // Check if user has a timeout - const hasTimeout = await prisma.cooldown.findUnique({ - where: { - guildId_userId_timeoutId: { - guildId: guild.id, - userId: user.id, - timeoutId: customId, - }, - }, - }); - - logger.silly(hasTimeout); - - // If user is not on timeout - if (hasTimeout) { - const { userId, timeoutId, createdAt } = hasTimeout; - const overDue = addSeconds(cooldown, createdAt) < new Date(); - - if (!overDue) { - const diff = Math.round( - (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 - ); - - throw new Error( - `You must wait ${diff} seconds before using this command.` - ); - } - - // Delete timeout - const deleteCooldown = await prisma.cooldown.delete({ - where: { - guildId_userId_timeoutId: { - guildId: guild.id, - userId: user.id, - timeoutId: customId, - }, - }, - }); - - logger.silly(deleteCooldown); - - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` - ); - } - - // Create timeout - const createCooldown = await prisma.cooldown.upsert({ - where: { - guildId_userId_timeoutId: { - userId: user.id, - guildId: guild.id, - timeoutId: customId, - }, - }, - update: {}, - create: { - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - user: { - connectOrCreate: { - create: { - id: user.id, - }, - where: { - id: user.id, - }, - }, - }, - timeoutId: customId, - cooldown, - }, - }); - - logger.silly(createCooldown); -}; - -// Message cooldown -export const message = async (msg: Message, cooldown: number, id: string) => { - const { guild, member } = msg; - - if (!guild) throw new Error("Guild not found"); - if (!member) throw new Error("Member is undefined"); - - // Check if user has a timeout - const hasTimeout = await prisma.cooldown.findUnique({ - where: { - guildId_userId_timeoutId: { - guildId: guild.id, - userId: member.id, - timeoutId: id, - }, - }, - }); - - logger.silly(hasTimeout); - - // If user is not on timeout - if (hasTimeout) { - const { userId, timeoutId, createdAt } = hasTimeout; - const overDue = addSeconds(cooldown, createdAt) < new Date(); - - if (!overDue) { - const diff = Math.round( - (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 - ); - - return `User: ${userId} on timeout-id: ${id} with cooldown: ${cooldown} secs with remaining: ${diff} secs.`; - } - - // Delete timeout - const deleteCooldown = await prisma.cooldown.delete({ - where: { - guildId_userId_timeoutId: { - guildId: guild.id, - userId: member.id, - timeoutId: id, - }, - }, - }); - - logger.silly(deleteCooldown); - - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` - ); - } - - // Create timeout - const createCooldown = await prisma.cooldown.upsert({ - where: { - guildId_userId_timeoutId: { - userId: member.id, - guildId: guild.id, - timeoutId: id, - }, - }, - update: {}, - create: { - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - user: { - connectOrCreate: { - create: { - id: member.id, - }, - where: { - id: member.id, - }, - }, - }, - timeoutId: id, - cooldown, - }, - }); - - logger.silly(createCooldown); -}; diff --git a/src/index.ts b/src/index.ts index b6d54c9..2169eb4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { Client, Collection, GatewayIntentBits } from "discord.js"; // discord.js import "dotenv/config"; + import { register as commandRegister } from "./handlers/command"; import { register as eventRegister } from "./handlers/event"; import { start as scheduleStart } from "./handlers/schedule"; diff --git a/src/middlewares/cooldown/index.ts b/src/middlewares/cooldown/index.ts new file mode 100644 index 0000000..232a083 --- /dev/null +++ b/src/middlewares/cooldown/index.ts @@ -0,0 +1,101 @@ +import { Guild, User } from "discord.js"; +import prisma from "../../handlers/database"; +import addSeconds from "../../helpers/addSeconds"; +import logger from "../logger"; + +export default async ( + guild: Guild, + user: User, + id: string, + cooldown: number, + silent?: boolean +) => { + // Check if user has a timeout + const hasTimeout = await prisma.cooldown.findUnique({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: user.id, + timeoutId: id, + }, + }, + }); + + logger.silly(hasTimeout); + + // If user is not on timeout + if (hasTimeout) { + const { userId, timeoutId, createdAt } = hasTimeout; + const overDue = addSeconds(cooldown, createdAt) < new Date(); + + if (!overDue) { + const diff = Math.round( + (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 + ); + + if (silent) + return logger.verbose( + `User ${userId} is on cooldown for ${timeoutId} for ${diff} seconds` + ); + + throw new Error( + `You must wait ${diff} seconds before using this command.` + ); + } + + // Delete timeout + const deleteCooldown = await prisma.cooldown.delete({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: user.id, + timeoutId: id, + }, + }, + }); + + logger.silly(deleteCooldown); + + logger.debug( + `Timeout document ${timeoutId} has been deleted from user ${userId}.` + ); + } + + // Create timeout + const createCooldown = await prisma.cooldown.upsert({ + where: { + guildId_userId_timeoutId: { + userId: user.id, + guildId: guild.id, + timeoutId: id, + }, + }, + update: {}, + create: { + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + user: { + connectOrCreate: { + create: { + id: user.id, + }, + where: { + id: user.id, + }, + }, + }, + timeoutId: id, + cooldown, + }, + }); + + logger.silly(createCooldown); +}; From 0239b6c4dc3707782d6068224de5c5b14a353f5d Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 00:17:04 +0200 Subject: [PATCH 05/15] =?UTF-8?q?=F0=9F=9A=B8=20=F0=9F=8E=A8=20=E2=99=BB?= =?UTF-8?q?=EF=B8=8F=20Cooldown=20Middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 19 +++++++++ package.json | 2 + src/index.ts | 1 - src/middlewares/cooldown/index.ts | 64 ++++++++++++++----------------- 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae8bebb..4da390f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "chance": "^1.1.8", "common": "^0.2.5", "crypto": "^1.0.1", + "date-fns": "^2.29.3", "discord-api-types": "^0.37.0", "discord.js": "^14.0.0", "dotenv": "^16.0.1", @@ -25,6 +26,7 @@ "i18next-fs-backend": "^1.1.4", "i18next-http-backend": "^1.4.0", "i18next-resources-to-backend": "^1.0.0", + "moment": "^2.29.4", "mongoose": "^6.2.3", "node-schedule": "^2.1.0", "ts-node": "^10.7.0", @@ -2647,6 +2649,18 @@ "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." }, + "node_modules/date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -9696,6 +9710,11 @@ "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" }, + "date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index dee70e9..413cd18 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "chance": "^1.1.8", "common": "^0.2.5", "crypto": "^1.0.1", + "date-fns": "^2.29.3", "discord-api-types": "^0.37.0", "discord.js": "^14.0.0", "dotenv": "^16.0.1", @@ -49,6 +50,7 @@ "i18next-fs-backend": "^1.1.4", "i18next-http-backend": "^1.4.0", "i18next-resources-to-backend": "^1.0.0", + "moment": "^2.29.4", "mongoose": "^6.2.3", "node-schedule": "^2.1.0", "ts-node": "^10.7.0", diff --git a/src/index.ts b/src/index.ts index 2169eb4..b58112b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,6 @@ import "dotenv/config"; import { register as commandRegister } from "./handlers/command"; import { register as eventRegister } from "./handlers/event"; import { start as scheduleStart } from "./handlers/schedule"; - // Main process that starts all other sub processes const main = async () => { // Initiate client object diff --git a/src/middlewares/cooldown/index.ts b/src/middlewares/cooldown/index.ts index 232a083..a188521 100644 --- a/src/middlewares/cooldown/index.ts +++ b/src/middlewares/cooldown/index.ts @@ -1,6 +1,6 @@ +import { add, formatDuration, intervalToDuration, isPast } from "date-fns"; import { Guild, User } from "discord.js"; import prisma from "../../handlers/database"; -import addSeconds from "../../helpers/addSeconds"; import logger from "../logger"; export default async ( @@ -11,7 +11,7 @@ export default async ( silent?: boolean ) => { // Check if user has a timeout - const hasTimeout = await prisma.cooldown.findUnique({ + const isOnCooldown = await prisma.cooldown.findUnique({ where: { guildId_userId_timeoutId: { guildId: guild.id, @@ -20,48 +20,40 @@ export default async ( }, }, }); + logger.silly(isOnCooldown); - logger.silly(hasTimeout); + if (isOnCooldown) { + const { userId, timeoutId, createdAt } = isOnCooldown; + const dueDate = add(createdAt, { seconds: cooldown }); - // If user is not on timeout - if (hasTimeout) { - const { userId, timeoutId, createdAt } = hasTimeout; - const overDue = addSeconds(cooldown, createdAt) < new Date(); + const duration = formatDuration( + intervalToDuration({ + start: new Date(), + end: dueDate, + }) + ); - if (!overDue) { - const diff = Math.round( - (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 - ); - - if (silent) - return logger.verbose( - `User ${userId} is on cooldown for ${timeoutId} for ${diff} seconds` - ); - - throw new Error( - `You must wait ${diff} seconds before using this command.` - ); + if (isPast(dueDate)) { + return await prisma.cooldown.delete({ + where: { + guildId_userId_timeoutId: { + guildId: guild.id, + userId: user.id, + timeoutId: id, + }, + }, + }); } - // Delete timeout - const deleteCooldown = await prisma.cooldown.delete({ - where: { - guildId_userId_timeoutId: { - guildId: guild.id, - userId: user.id, - timeoutId: id, - }, - }, - }); + if (!silent) { + throw new Error(`You are still on cooldown for ${duration}`); + } - logger.silly(deleteCooldown); - - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` + return logger.verbose( + `User ${userId} is on cooldown for ${timeoutId}, it ends in ${duration}.` ); } - // Create timeout const createCooldown = await prisma.cooldown.upsert({ where: { guildId_userId_timeoutId: { @@ -98,4 +90,6 @@ export default async ( }); logger.silly(createCooldown); + + return createCooldown; }; From 5f7031587fd56926ee463b231f88a539d730173f Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 00:43:00 +0200 Subject: [PATCH 06/15] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Cooldown=20middlewar?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/helpers/addSeconds/index.ts | 4 --- src/middlewares/cooldown/index.ts | 10 +++---- src/schedules/cooldowns/index.ts | 43 +++++++++++++++++++++++++++++++ src/schedules/timeouts/index.ts | 40 ---------------------------- 4 files changed, 48 insertions(+), 49 deletions(-) delete mode 100644 src/helpers/addSeconds/index.ts create mode 100644 src/schedules/cooldowns/index.ts delete mode 100644 src/schedules/timeouts/index.ts diff --git a/src/helpers/addSeconds/index.ts b/src/helpers/addSeconds/index.ts deleted file mode 100644 index baa7d83..0000000 --- a/src/helpers/addSeconds/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default (seconds: number, date: Date) => { - date.setSeconds(date.getSeconds() + seconds); - return date; -}; diff --git a/src/middlewares/cooldown/index.ts b/src/middlewares/cooldown/index.ts index a188521..edfe2ea 100644 --- a/src/middlewares/cooldown/index.ts +++ b/src/middlewares/cooldown/index.ts @@ -45,13 +45,13 @@ export default async ( }); } - if (!silent) { - throw new Error(`You are still on cooldown for ${duration}`); + if (silent) { + return logger.verbose( + `User ${userId} is on cooldown for ${timeoutId}, it ends in ${duration}.` + ); } - return logger.verbose( - `User ${userId} is on cooldown for ${timeoutId}, it ends in ${duration}.` - ); + throw new Error(`You are still on cooldown for ${duration}`); } const createCooldown = await prisma.cooldown.upsert({ diff --git a/src/schedules/cooldowns/index.ts b/src/schedules/cooldowns/index.ts new file mode 100644 index 0000000..c825cc9 --- /dev/null +++ b/src/schedules/cooldowns/index.ts @@ -0,0 +1,43 @@ +/* eslint-disable no-loops/no-loops */ +import { add, formatDuration, intervalToDuration, isPast } from "date-fns"; + +import prisma from "../../handlers/database"; +import logger from "../../middlewares/logger"; + +export const options = { + schedule: "*/30 * * * *", // https://crontab.guru/ +}; + +// Execute the job +export const execute = async () => { + const cooldownsObj = await prisma.cooldown.findMany(); + + for await (const cooldownObj of cooldownsObj) { + const { guildId, userId, timeoutId, cooldown, createdAt } = cooldownObj; + + const dueDate = add(createdAt, { seconds: cooldown }); + if (!isPast(dueDate)) return; + + const duration = formatDuration( + intervalToDuration({ + start: new Date(), + end: dueDate, + }) + ); + + const deleteCooldown = await prisma.cooldown.delete({ + where: { + guildId_userId_timeoutId: { + guildId, + userId, + timeoutId, + }, + }, + }); + logger.silly(deleteCooldown); + + logger.verbose( + `User ${userId} is on cooldown for ${timeoutId}, it ends in ${duration}.` + ); + } +}; diff --git a/src/schedules/timeouts/index.ts b/src/schedules/timeouts/index.ts deleted file mode 100644 index 9964892..0000000 --- a/src/schedules/timeouts/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-loops/no-loops */ -import logger from "../../middlewares/logger"; - -import addSeconds from "../../helpers/addSeconds"; - -import prisma from "../../handlers/database"; - -export const options = { - schedule: "*/30 * * * *", // https://crontab.guru/ -}; - -// Execute the job -export const execute = async () => { - const getCooldown = await prisma.cooldown.findMany(); - - for await (const timeout of getCooldown) { - const { guildId, userId, timeoutId, cooldown, createdAt } = timeout; - - const overDue = addSeconds(cooldown, createdAt) < new Date(); - - if (overDue) { - logger.info(timeout); - const deleteCooldown = await prisma.cooldown.delete({ - where: { - guildId_userId_timeoutId: { - guildId, - userId, - timeoutId, - }, - }, - }); - - logger.silly(deleteCooldown); - - logger.debug( - `Timeout document ${timeoutId} has been deleted from user ${userId}.` - ); - } - } -}; From 29391d76ff3cf145460d2de703282d30e73f97b9 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 00:55:33 +0200 Subject: [PATCH 07/15] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Event=20Handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers/event/index.ts | 88 +++++++++---------------------------- 1 file changed, 21 insertions(+), 67 deletions(-) diff --git a/src/handlers/event/index.ts b/src/handlers/event/index.ts index 4d0fabc..16af910 100644 --- a/src/handlers/event/index.ts +++ b/src/handlers/event/index.ts @@ -1,96 +1,50 @@ /* eslint-disable no-loops/no-loops */ import { Client } from "discord.js"; -import listDir from "../../helpers/checkDirectory"; +import checkDirectory from "../../helpers/checkDirectory"; import { IEvent } from "../../interfaces/Event"; import logger from "../../middlewares/logger"; // Registers all available events export const register = async (client: Client) => { - const eventNames = await listDir("events"); - if (!eventNames) throw new Error("📦 No events available"); + logger.info("📡 Started event management"); - const amountOfEvents = eventNames.length; - let importedEventAmount = 0; - logger.info(`📦 Trying to load ${amountOfEvents} events`); + const eventNames = await checkDirectory("events"); + if (!eventNames) return logger.warn("No available events found"); - const importEvent = async (eventName: string) => { - // Import event from events - const event: IEvent = await import(`../../events/${eventName}`); - if (!event) - throw new Error(`📦 No event found while importing "${eventName}"`); - if (!event.options) - throw new Error( - `📦 No event options found while importing "${eventName}"` - ); - if (!event.execute) - throw new Error( - `📦 No event execute found while importing "${eventName}"` - ); + const totalEvents = eventNames.length; + let loadedEvents = 0; + + logger.info(`📡 Loading ${totalEvents} events`); + + const importEvent = async (name: string) => { + const event: IEvent = await import(`../../events/${name}`); - // Register event const eventExecutor = async (...args: Promise[]) => { - await event.execute(...args).catch((err) => { - logger.error(`${err}`); - }); + await event.execute(...args); }; - if (!event.options?.type) - throw new Error(`📦 No event type found while importing "${eventName}"`); - switch (event.options.type) { case "once": - client.once(eventName, eventExecutor); + client.once(name, eventExecutor); break; case "on": - client.on(eventName, eventExecutor); + client.on(name, eventExecutor); break; default: - logger.error(`${eventName} does not have a valid type`); - } - importedEventAmount += 1; - }; - - // Send log message when it's done loading events - const doneImporting = () => { - if (importedEventAmount !== amountOfEvents) { - return logger.warn( - `📦 Failed importing ${ - amountOfEvents - importedEventAmount - } of ${amountOfEvents} events` - ); + throw new Error(`📡 Invalid event type for event: ${name}`); } - return logger.info(`📦 Managed to load all events`); + loadedEvents++; }; - eventNames.forEach(async (eventName: string, index: number) => { + for await (const eventName of eventNames) { await importEvent(eventName).then(() => { - logger.debug(`📦 Imported the "${eventName}" event`); + logger.verbose(`📡 Loaded event "${eventName}"`); }); - // If done importing - if (index + 1 === amountOfEvents) { - await doneImporting(); + if (loadedEvents === totalEvents) { + logger.info("📡 All events loaded"); } - }); - - // for await (const eventName of eventNames) { - // const event: IEvent = await import(`../../events/${eventName}`); - // const eventExecutor = async (...args: Promise[]) => - // event.execute(...args).catch(async (err) => { - // logger.error(`${err}`); - // }); - // if (!event.options?.type) return; - - // switch (event.options.type) { - // case "once": - // client.once(eventName, eventExecutor); - // break; - - // case "on": - // client.on(eventName, eventExecutor); - // break; - // } - // } + } }; From 092b4be7b488bb7bca0b8220390841260c1f2acc Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 00:56:15 +0200 Subject: [PATCH 08/15] =?UTF-8?q?=F0=9F=94=8A=20add=20clock=20to=20no=20jo?= =?UTF-8?q?bs=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers/schedule/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/schedule/index.ts b/src/handlers/schedule/index.ts index da2c37d..6fcbaf3 100644 --- a/src/handlers/schedule/index.ts +++ b/src/handlers/schedule/index.ts @@ -9,7 +9,7 @@ export const start = async (client: Client) => { logger.info("⏰ Started job management"); const jobNames = await checkDirectory("schedules"); - if (!jobNames) return logger.warn("No available jobs found"); + if (!jobNames) return logger.warn("⏰ No available jobs found"); await Promise.all( jobNames.map(async (jobName) => { From c352a08295ccf49e42a41e4c2217ed512db3c13d Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 01:05:04 +0200 Subject: [PATCH 09/15] =?UTF-8?q?=F0=9F=92=A1=20=F0=9F=94=8A=20?= =?UTF-8?q?=F0=9F=8E=A8=20=E2=99=BB=EF=B8=8F=20updatePresence=20Handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers/updatePresence/index.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/handlers/updatePresence/index.ts b/src/handlers/updatePresence/index.ts index 55a7d95..9fdb486 100644 --- a/src/handlers/updatePresence/index.ts +++ b/src/handlers/updatePresence/index.ts @@ -4,17 +4,26 @@ import logger from "../../middlewares/logger"; // Function export default (client: Client) => { - if (!client?.user) throw new Error("Client's user is undefined."); - const { guilds } = client; + // 1. Destructure the client. + const { guilds, user } = client; + if (!user) throw new Error("No user found"); + // 2. Get the total number of guilds and members. const memberCount = guilds.cache.reduce((a, g) => a + g.memberCount, 0); const guildCount = guilds.cache.size; - const status = `${memberCount} users in ${guildCount} guilds.`; - client.user.setPresence({ - activities: [{ type: ActivityType.Watching, name: status }], - status: "online", + // 3. Set the presence. + user.setPresence({ + activities: [ + { + name: `${guildCount} guilds | ${memberCount} members`, + type: ActivityType.Watching, + }, + ], }); - logger.info(`Client's presence is set to "${status}"`); + // 4. Log the presence. + return logger.info( + `👀 Presence set to "${guildCount} guilds | ${memberCount} members"` + ); }; From 5d1f8e456940b56058f4feb36aeff168219613ba Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 01:51:43 +0200 Subject: [PATCH 10/15] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20deployCommands=20Han?= =?UTF-8?q?dler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/events/ready/index.ts | 2 - src/handlers/deployCommands/index.ts | 55 ++++++++++++++-------------- src/handlers/devMode/index.ts | 14 ------- 3 files changed, 28 insertions(+), 43 deletions(-) delete mode 100644 src/handlers/devMode/index.ts diff --git a/src/events/ready/index.ts b/src/events/ready/index.ts index b91a36a..72cc060 100644 --- a/src/events/ready/index.ts +++ b/src/events/ready/index.ts @@ -2,7 +2,6 @@ import { Client } from "discord.js"; // Helpers import deployCommands from "../../handlers/deployCommands"; -import devMode from "../../handlers/devMode"; import updatePresence from "../../handlers/updatePresence"; import { IEventOptions } from "../../interfaces/EventOptions"; import logger from "../../middlewares/logger"; @@ -16,6 +15,5 @@ export const execute = async (client: Client) => { logger.info("Discord's API client is ready!"); updatePresence(client); - await devMode(client); await deployCommands(client); }; diff --git a/src/handlers/deployCommands/index.ts b/src/handlers/deployCommands/index.ts index 7e63c4e..4969b6a 100644 --- a/src/handlers/deployCommands/index.ts +++ b/src/handlers/deployCommands/index.ts @@ -1,38 +1,39 @@ -import { RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10"; -import { Client } from "discord.js"; +import { Client, RESTPostAPIApplicationCommandsJSONBody } from "discord.js"; import { ICommand } from "../../interfaces/Command"; import logger from "../../middlewares/logger"; export default async (client: Client) => { - const commandList: Array = []; + // 1. Destructure the client. + const { application } = client; + if (!application) throw new Error("No application found"); - if (!client.commands) { - throw new Error("client.commands is not defined"); - } + // 2. Log that we are starting the command management. + logger.info("🔧 Started command deployment"); - logger.info("Gathering command list"); + // 3. Get the commands. + const commands: Array = []; + client.commands.forEach((command: ICommand) => { + commands.push(command.builder.toJSON()); - await Promise.all( - client.commands.map((commandData: ICommand) => { - commandList.push(commandData.builder.toJSON()); - - logger.verbose(`${commandData.builder.name} pushed to list`); - }) - ) - .then(() => { - logger.info(`Finished gathering command list.`); - }) - .catch((error) => { - throw new Error(`Could not gather command list: ${error}`); - }); - - await client.application?.commands.set(commandList).then(() => { - logger.info(`Finished updating command list.`); + logger.verbose(`🔧 Loaded command "${command.builder.name}"`); }); - if (process.env.NODE_ENV !== "production") { - await client.application?.commands - .set(commandList, process.env.DISCORD_GUILD_ID) - .then(() => logger.info(`Finished updating guild command list.`)); + // 4. Set the commands. + await application.commands.set(commands).then(() => { + logger.info("🔧 Deployed commands globally"); + }); + + // 5. Tell the user that development mode is enabled. + if (process.env.NODE_ENV === "development") { + logger.info("🔧 Development mode enabled"); + + await application.commands + .set(commands, process.env.DISCORD_GUILD_ID) + .then(() => { + logger.verbose(`🔧 Deployed commands to guild`); + }); } + + // 6. Log that we are done with the command management. + logger.info("🔧 Finished command deployment"); }; diff --git a/src/handlers/devMode/index.ts b/src/handlers/devMode/index.ts deleted file mode 100644 index 1c69b66..0000000 --- a/src/handlers/devMode/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Client } from "discord.js"; -import logger from "../../middlewares/logger"; - -export default async (client: Client) => { - if (process.env.NODE_ENV !== "production") { - await client?.application?.commands - ?.set([], process.env.DISCORD_GUILD_ID) - .then(() => { - return logger.verbose(`Development mode is disabled.`); - }); - } - - return logger.info(`Development mode is enabled.`); -}; From 048f8bade9332b8559ff7ae6dfc36605b35514b4 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 01:51:51 +0200 Subject: [PATCH 11/15] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Command=20Handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers/command/index.ts | 56 +++++++++++------------------------ 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/src/handlers/command/index.ts b/src/handlers/command/index.ts index 09d05e2..00293fa 100644 --- a/src/handlers/command/index.ts +++ b/src/handlers/command/index.ts @@ -1,54 +1,34 @@ +/* eslint-disable no-loops/no-loops */ import { Client } from "discord.js"; -import listDir from "../../helpers/checkDirectory"; +import checkDirectory from "../../helpers/checkDirectory"; import { ICommand } from "../../interfaces/Command"; import logger from "../../middlewares/logger"; export const register = async (client: Client) => { - // Get name of directories containing commands - const commandNames = await listDir("commands"); - if (!commandNames) throw new Error("📦 No commands available"); + logger.info("🔧 Started command management"); - const amountOfCommands = commandNames.length; - let importedCommandAmount = 0; - logger.info(`📦 Trying to load ${amountOfCommands} commands`); + const commandNames = await checkDirectory("commands"); + if (!commandNames) return logger.warn("No available commands found"); - const importCommand = async (commandName: string) => { - // Import command from commands - const command: ICommand = await import(`../../commands/${commandName}`); - if (!command) - throw new Error(`📦 No command found while importing "${commandName}"`); - if (!command.builder) - throw new Error( - `📦 No command builder found while importing "${commandName}"` - ); + const totalCommands = commandNames.length; + let loadedCommands = 0; + + logger.info(`🔧 Loading ${totalCommands} commands`); + + const importCommand = async (name: string) => { + const command: ICommand = await import(`../../commands/${name}`); - // Add command to collection client.commands.set(command.builder.name, command); - importedCommandAmount += 1; + loadedCommands++; }; - // Send log message when it's done loading commands - const doneImporting = () => { - if (importedCommandAmount !== amountOfCommands) { - return logger.warn( - `📦 Failed importing ${ - amountOfCommands - importedCommandAmount - } of ${amountOfCommands} commands` - ); - } - - return logger.info(`📦 Managed to load all commands`); - }; - - // Start importing commands - commandNames.forEach(async (commandName: string, index: number) => { + for await (const commandName of commandNames) { await importCommand(commandName).then(() => { - logger.debug(`📦 Imported the "${commandName}" command`); + logger.verbose(`🔧 Loaded command "${commandName}"`); }); - // If done importing - if (index + 1 === amountOfCommands) { - await doneImporting(); + if (loadedCommands === totalCommands) { + logger.info("🔧 All commands loaded"); } - }); + } }; From 77bfe48613246d5356c4b128e366cd058a6d8bb5 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 01:54:53 +0200 Subject: [PATCH 12/15] =?UTF-8?q?=F0=9F=94=A8=20Set=20NODE=5FENV=20to=20de?= =?UTF-8?q?velopment=20for=20npm=20run=20dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/handlers/deployCommands/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 413cd18..a68e894 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Earn credits while chatting! And more", "main": "dist/index.js", "scripts": { - "dev": "tsc --watch & nodemon dist", + "dev": "tsc --watch & NODE_ENV=development nodemon dist", "build": "tsc -p .", "prisma:generate": "prisma generate", "test": "jest", diff --git a/src/handlers/deployCommands/index.ts b/src/handlers/deployCommands/index.ts index 4969b6a..94feecf 100644 --- a/src/handlers/deployCommands/index.ts +++ b/src/handlers/deployCommands/index.ts @@ -30,7 +30,7 @@ export default async (client: Client) => { await application.commands .set(commands, process.env.DISCORD_GUILD_ID) .then(() => { - logger.verbose(`🔧 Deployed commands to guild`); + logger.info(`🔧 Deployed commands to guild`); }); } From 71ff30ea87c7e41d7836281d974e1b7ca69e73f5 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 10:09:58 +0200 Subject: [PATCH 13/15] =?UTF-8?q?=F0=9F=9A=A8=20JS-0116=20JS-0376?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/helpers/credits/set.ts | 2 +- src/helpers/credits/take.ts | 2 +- src/helpers/credits/transactionRules.ts | 2 +- src/helpers/credits/transfer.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/helpers/credits/set.ts b/src/helpers/credits/set.ts index 2db8cf7..c8053e0 100644 --- a/src/helpers/credits/set.ts +++ b/src/helpers/credits/set.ts @@ -5,7 +5,7 @@ import transactionRules from "./transactionRules"; export default async (guild: Guild, user: User, amount: number) => { return await prisma.$transaction(async (tx) => { // 1. Check if the transaction is valid. - await transactionRules(guild, user, amount); + transactionRules(guild, user, amount); // 2. Make the transaction. const recipient = await tx.guildMember.upsert({ diff --git a/src/helpers/credits/take.ts b/src/helpers/credits/take.ts index ae21ed6..7b97e2b 100644 --- a/src/helpers/credits/take.ts +++ b/src/helpers/credits/take.ts @@ -5,7 +5,7 @@ import transactionRules from "./transactionRules"; export default async (guild: Guild, user: User, amount: number) => { return await prisma.$transaction(async (tx) => { // 1. Check if the transaction is valid. - await transactionRules(guild, user, amount); + transactionRules(guild, user, amount); // 2. Make the transaction. const recipient = await tx.guildMember.upsert({ diff --git a/src/helpers/credits/transactionRules.ts b/src/helpers/credits/transactionRules.ts index 021448f..ea9d7b1 100644 --- a/src/helpers/credits/transactionRules.ts +++ b/src/helpers/credits/transactionRules.ts @@ -1,6 +1,6 @@ import { Guild, User } from "discord.js"; -export default async (guild: Guild, user: User, amount: number) => { +export default (guild: Guild, user: User, amount: number) => { // 1. Verify that the amount is not above 100.000.000 credits. if (amount > 100000000) { throw new Error("You can't give more than 1.000.000 credits."); diff --git a/src/helpers/credits/transfer.ts b/src/helpers/credits/transfer.ts index 7177328..c85518e 100644 --- a/src/helpers/credits/transfer.ts +++ b/src/helpers/credits/transfer.ts @@ -48,8 +48,8 @@ export default async (guild: Guild, from: User, to: User, amount: number) => { } // 5. Check if the transactions is valid. - await transactionRules(guild, from, amount); - await transactionRules(guild, to, amount); + transactionRules(guild, from, amount); + transactionRules(guild, to, amount); // 6. Verify that sender and recipient are not the same user. if (from.id === to.id) throw new Error("You can't transfer to yourself."); From 38e5ebbade98493b6cdaeffb53f56b84e774cd53 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 11:10:05 +0200 Subject: [PATCH 14/15] =?UTF-8?q?=F0=9F=9A=A8=20JS-0294?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/helpers/credits/give.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/credits/give.ts b/src/helpers/credits/give.ts index 3882db4..04395ed 100644 --- a/src/helpers/credits/give.ts +++ b/src/helpers/credits/give.ts @@ -5,7 +5,7 @@ import transactionRules from "./transactionRules"; export default async (guild: Guild, user: User, amount: number) => { return await prisma.$transaction(async (tx) => { // 1. Check if the transaction is valid. - await transactionRules(guild, user, amount); + transactionRules(guild, user, amount); // 2. Make the transaction. const recipient = await tx.guildMember.upsert({ From 5620eb8315c3571d78ef16427784ec4686195eb1 Mon Sep 17 00:00:00 2001 From: Vermium Sifell Date: Mon, 24 Oct 2022 11:13:03 +0200 Subject: [PATCH 15/15] =?UTF-8?q?=F0=9F=92=A1=20JS-D1001?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers/command/index.ts | 2 ++ src/handlers/event/index.ts | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/handlers/command/index.ts b/src/handlers/command/index.ts index 00293fa..a9500a1 100644 --- a/src/handlers/command/index.ts +++ b/src/handlers/command/index.ts @@ -4,6 +4,7 @@ import checkDirectory from "../../helpers/checkDirectory"; import { ICommand } from "../../interfaces/Command"; import logger from "../../middlewares/logger"; +// Register the commands. export const register = async (client: Client) => { logger.info("🔧 Started command management"); @@ -15,6 +16,7 @@ export const register = async (client: Client) => { logger.info(`🔧 Loading ${totalCommands} commands`); + // Import an command. const importCommand = async (name: string) => { const command: ICommand = await import(`../../commands/${name}`); diff --git a/src/handlers/event/index.ts b/src/handlers/event/index.ts index 16af910..e2a27b3 100644 --- a/src/handlers/event/index.ts +++ b/src/handlers/event/index.ts @@ -4,7 +4,7 @@ import checkDirectory from "../../helpers/checkDirectory"; import { IEvent } from "../../interfaces/Event"; import logger from "../../middlewares/logger"; -// Registers all available events +// Registers all available events. export const register = async (client: Client) => { logger.info("📡 Started event management"); @@ -16,9 +16,11 @@ export const register = async (client: Client) => { logger.info(`📡 Loading ${totalEvents} events`); + // Import an event. const importEvent = async (name: string) => { const event: IEvent = await import(`../../events/${name}`); + // Create a new event execute function. const eventExecutor = async (...args: Promise[]) => { await event.execute(...args); };