diff --git a/src/commands/credits/index.ts b/src/commands/credits/index.ts index 777d32c..96eea7c 100644 --- a/src/commands/credits/index.ts +++ b/src/commands/credits/index.ts @@ -1,12 +1,13 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { ChatInputCommandInteraction } from "discord.js"; -import logger from "../../middlewares/logger"; +import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; // Modules -import moduleBalance from "./modules/balance"; -import moduleGift from "./modules/gift"; -import moduleTop from "./modules/top"; -import moduleWork from "./modules/work"; +import { + builder as BalanceBuilder, + execute as BalanceExecute, +} from "./modules/balance"; +import { builder as GiftBuilder, execute as GiftExecute } from "./modules/gift"; +import { builder as TopBuilder, execute as TopExecute } from "./modules/top"; +import { builder as WorkBuilder, execute as WorkExecute } from "./modules/work"; export const builder = new SlashCommandBuilder() .setName("credits") @@ -14,29 +15,27 @@ export const builder = new SlashCommandBuilder() .setDMPermission(false) // Modules - .addSubcommand(moduleBalance.builder) - .addSubcommand(moduleGift.builder) - .addSubcommand(moduleTop.builder) - .addSubcommand(moduleWork.builder); + .addSubcommand(BalanceBuilder) + .addSubcommand(GiftBuilder) + .addSubcommand(TopBuilder) + .addSubcommand(WorkBuilder); // Execute function export const execute = async (interaction: ChatInputCommandInteraction) => { - const { options } = interaction; - - switch (options.getSubcommand()) { + switch (interaction.options.getSubcommand()) { case "balance": - await moduleBalance.execute(interaction); + await BalanceExecute(interaction); break; case "gift": - await moduleGift.execute(interaction); + await GiftExecute(interaction); break; case "top": - await moduleTop.execute(interaction); + await TopExecute(interaction); break; case "work": - await moduleWork.execute(interaction); + await WorkExecute(interaction); break; default: - logger.silly(`Unknown subcommand ${options.getSubcommand()}`); + throw new Error("Subcommand not found"); } }; diff --git a/src/commands/credits/modules/balance/index.ts b/src/commands/credits/modules/balance/index.ts index 9034f63..bbe287c 100644 --- a/src/commands/credits/modules/balance/index.ts +++ b/src/commands/credits/modules/balance/index.ts @@ -1,95 +1,84 @@ -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { CommandInteraction, EmbedBuilder } from "discord.js"; +import { CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js"; + import prisma from "../../../../handlers/database"; import deferReply from "../../../../handlers/deferReply"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; +import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; import logger from "../../../../middlewares/logger"; -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("balance") - .setDescription(`View a user's balance`) - .addUserOption((option) => - option - .setName("user") - .setDescription(`The user whose balance you want to view`) - ); - }, - execute: async (interaction: CommandInteraction) => { - await deferReply(interaction, true); - - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { options, user, guild } = interaction; - - const discordUser = options.getUser("user"); - - const embed = new EmbedBuilder() - .setTitle("[:dollar:] Balance") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - if (guild === null) { - logger.silly(`Guild is null`); - - return interaction.editReply({ - embeds: [ - embed.setDescription("Guild is not found").setColor(errorColor), - ], - }); - } - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: (discordUser || user).id, - guildId: guild.id, - }, - }, - update: {}, - create: { - user: { - connectOrCreate: { - create: { - id: (discordUser || user).id, - }, - where: { - id: (discordUser || 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("No guild member exists."); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - `${discordUser || user} currently has ${ - createGuildMember.creditsEarned - } credits.` - ) - .setColor(successColor), - ], - }); - }, +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("balance") + .setDescription(`View a user's balance`) + .addUserOption((option) => + option + .setName("target") + .setDescription(`The user whose balance you want to view`) + ); +}; + +export const execute = async (interaction: CommandInteraction) => { + // 1. Defer reply as ephemeral. + await deferReply(interaction, true); + + // 2. Destructure interaction object. + const { options, user, guild } = interaction; + if (!guild) throw new Error("Guild not found"); + if (!user) throw new Error("User not found"); + if (!options) throw new Error("Options not found"); + + // 3. Get options from interaction. + const target = options.getUser("target"); + + // 4. Create base embeds. + const EmbedSuccess = await BaseEmbedSuccess(guild, "[:dollar:] Balance"); + + // 5. Upsert the user in the database. + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: (target || user).id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: (target || user).id, + }, + where: { + id: (target || 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("No guild member exists."); + + // 6. Send embed. + await interaction.editReply({ + embeds: [ + EmbedSuccess.setDescription( + target + ? `${target} has ${createGuildMember.creditsEarned} credits.` + : `You have ${createGuildMember.creditsEarned} credits.` + ), + ], + }); }; diff --git a/src/commands/credits/modules/gift/index.ts b/src/commands/credits/modules/gift/index.ts index db3435c..325cdf5 100644 --- a/src/commands/credits/modules/gift/index.ts +++ b/src/commands/credits/modules/gift/index.ts @@ -1,89 +1,77 @@ -// Dependencies -// Models import { ChatInputCommandInteraction, - EmbedBuilder, SlashCommandSubcommandBuilder, } from "discord.js"; -import transferCredits from "../../../../helpers/transferCredits"; -// Configurations + import deferReply from "../../../../handlers/deferReply"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -// Handlers +import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; +import transferCredits from "../../../../helpers/transferCredits"; -// Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("gift") - .setDescription(`Gift a user credits`) - .addUserOption((option) => - option - .setName("user") - .setDescription("The user you want to gift credits to.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("The amount of credits you want to gift.") - .setRequired(true) - ) - .addStringOption((option) => - option.setName("reason").setDescription("Your reason.") - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("gift") + .setDescription(`Gift a user credits`) + .addUserOption((option) => + option + .setName("target") + .setDescription("The user you want to gift credits to.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("credits") + .setDescription("The amount of credits you want to gift.") + .setRequired(true) + ) + .addStringOption((option) => + option.setName("reason").setDescription("Your reason.") ); - const { options, user, guild, client } = interaction; - - const optionUser = options.getUser("user"); - const optionAmount = options.getInteger("amount"); - const optionReason = options.getString("reason"); - - const embed = new EmbedBuilder() - .setTitle("[:dollar:] Gift") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - if (!guild) throw new Error("Guild not found"); - if (!optionUser) throw new Error("No receiver found"); - if (optionAmount === null) throw new Error("Amount not found"); - - await transferCredits(guild, user, optionUser, optionAmount); - - // Get DM user object - const dmUser = client.users.cache.get(optionUser.id); - - if (!dmUser) throw new Error("User not found"); - - // Send DM to user - await dmUser.send({ - embeds: [ - embed - .setDescription( - `${user.tag} has gifted you ${optionAmount} credits with reason: ${ - optionReason || "unspecified" - }` - ) - .setColor(successColor), - ], - }); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - `Successfully gifted ${optionAmount} credits to ${optionUser} with reason: ${ - optionReason || "unspecified" - }` - ) - .setColor(successColor), - ], - }); - }, +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + // 1. Defer reply as ephemeral. + await deferReply(interaction, true); + + // 2. Destructure interaction object. + const { options, user, guild, client } = interaction; + if (!guild) throw new Error("Guild not found"); + if (!user) throw new Error("User not found"); + if (!client) throw new Error("Client not found"); + if (!options) throw new Error("Options not found"); + + // 3. Get options from interaction. + const target = options.getUser("target"); + const credits = options.getInteger("credits"); + const reason = options.getString("reason"); + if (!target) throw new Error("Target user not found"); + if (typeof credits !== "number") + throw new Error("You need to specify a number of credits you want to gift"); + + // 4. Create base embeds. + const EmbedSuccess = await BaseEmbedSuccess(guild, "[:dollar:] Gift"); + + // 5. Start an transaction of the credits. + await transferCredits(guild, user, target, credits); + + // 6. Tell the target that they have been gifted credits. + await target.send({ + embeds: [ + EmbedSuccess.setDescription( + reason + ? `You received ${credits} credits from ${user} for the reason: ${reason}.` + : `You received ${credits} credits from ${user}.` + ), + ], + }); + + // 7. Tell the sender that they have gifted the credits. + await interaction.editReply({ + embeds: [ + EmbedSuccess.setDescription( + reason + ? `You have successfully gifted ${credits} credits to ${target} with reason: ${reason}.` + : `You have successfully gifted ${credits} credits to ${target}.` + ), + ], + }); }; diff --git a/src/commands/credits/modules/top/index.ts b/src/commands/credits/modules/top/index.ts index 075c599..a498122 100644 --- a/src/commands/credits/modules/top/index.ts +++ b/src/commands/credits/modules/top/index.ts @@ -1,73 +1,57 @@ -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { GuildMember } from "@prisma/client"; -import { CommandInteraction, EmbedBuilder } from "discord.js"; +import { + CommandInteraction, + SlashCommandSubcommandBuilder, + userMention, +} from "discord.js"; + import prisma from "../../../../handlers/database"; import deferReply from "../../../../handlers/deferReply"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; +import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; import logger from "../../../../middlewares/logger"; -export default { - metadata: { guildOnly: true, ephemeral: false }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command.setName("top").setDescription(`View the top users`); - }, - execute: async (interaction: CommandInteraction) => { - await deferReply(interaction, false); - - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const { guild } = interaction; - - const embed = new EmbedBuilder() - .setTitle("[:dollar:] Top") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - if (guild === null) { - logger.silly(`Guild is null`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - "Guild is not found. Please try again with a valid guild." - ) - .setColor(errorColor), - ], - }); - } - - // const usersDB = await userSchema.find({ guildId: guild.id }); - - const topTen = await prisma.guildMember.findMany({ - where: { - guildId: guild.id, - }, - orderBy: { - creditsEarned: "desc", - }, - take: 10, - }); - - logger.silly(topTen); - - // Create entry object - const entry = (guildMember: GuildMember, index: number) => - `${index + 1}. <@${guildMember.userId}> - ${ - guildMember.creditsEarned - } credits`; - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - `Below are the top ten members in this guild. - - ${topTen.map(entry).join("\n")}` - ) - .setColor(successColor), - ], - }); - }, +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command.setName("top").setDescription(`View the top users`); +}; + +export const execute = async (interaction: CommandInteraction) => { + // 1. Defer reply as permanent. + await deferReply(interaction, false); + + // 2. Destructure interaction object. + const { guild, client } = interaction; + if (!guild) throw new Error("Guild not found"); + if (!client) throw new Error("Client not found"); + + // 3. Create base embeds. + const EmbedSuccess = await BaseEmbedSuccess(guild, "[:dollar:] Top"); + + // 4. Get the top 10 users. + const topTen = await prisma.guildMember.findMany({ + where: { + guildId: guild.id, + }, + orderBy: { + creditsEarned: "desc", + }, + take: 10, + }); + logger.silly(topTen); + + // 5. Create the top 10 list. + const entry = (guildMember: GuildMember, index: number) => + `${index + 1}. ${userMention(guildMember.userId)} | :coin: ${ + guildMember.creditsEarned + }`; + + // 6. Send embed + return interaction.editReply({ + embeds: [ + EmbedSuccess.setDescription( + `The top 10 users in this server are:\n\n${topTen + .map(entry) + .join("\n")}` + ), + ], + }); }; diff --git a/src/commands/credits/modules/work/index.ts b/src/commands/credits/modules/work/index.ts index a69d155..55b3856 100644 --- a/src/commands/credits/modules/work/index.ts +++ b/src/commands/credits/modules/work/index.ts @@ -1,108 +1,100 @@ -// Dependencies import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import Chance from "chance"; -import { CommandInteraction, EmbedBuilder } from "discord.js"; -// Models +import { CommandInteraction } from "discord.js"; + import { command as CooldownCommand } from "../../../../handlers/cooldown"; -// Configurations -import getEmbedConfig from "../../../../helpers/getEmbedData"; -// Helpers -// Handlers import prisma from "../../../../handlers/database"; import deferReply from "../../../../handlers/deferReply"; +import { success as BaseEmbedSuccess } from "../../../../helpers/baseEmbeds"; import logger from "../../../../middlewares/logger"; -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command.setName("work").setDescription(`Work to earn credits`); - }, - execute: async (interaction: CommandInteraction) => { - await deferReply(interaction, true); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure member - const { guild, user } = interaction; - - const embed = new EmbedBuilder() - .setTitle("[:dollar:] Work") - .setTimestamp(new Date()) - .setFooter({ - text: footerText, - iconURL: footerIcon, - }); - - // Chance module - const chance = new Chance(); - - if (guild === null) { - return logger?.silly(`Guild is null`); - } - - const createGuild = await prisma.guild.upsert({ - where: { - id: guild.id, - }, - update: {}, - create: { - id: guild.id, - }, - }); - - logger.silly(createGuild); - - await CooldownCommand(interaction, createGuild.creditsWorkTimeout); - - const creditsEarned = chance.integer({ - min: 0, - max: createGuild.creditsWorkRate, - }); - - 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); - - return interaction.editReply({ - embeds: [ - embed - .setDescription(`You worked and earned ${creditsEarned} credits.`) - .setColor(successColor), - ], - }); - }, +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command.setName("work").setDescription(`Work to earn credits`); +}; + +export const execute = async (interaction: CommandInteraction) => { + // 1. Defer reply as ephemeral. + await deferReply(interaction, true); + + // 2. Destructure interaction object. + const { guild, user } = interaction; + if (!guild) throw new Error("Guild not found"); + if (!user) throw new Error("User not found"); + + // 3. Create base embeds. + const EmbedSuccess = await BaseEmbedSuccess(guild, "[:dollar:] Work"); + + // 4. Create new Chance instance. + const chance = new Chance(); + + // 5. Upsert the guild in the database. + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: {}, + create: { + id: guild.id, + }, + }); + logger.silly(createGuild); + if (!createGuild) throw new Error("Guild not found"); + + // 6. Create a cooldown for the user. + await CooldownCommand(interaction, createGuild.creditsWorkTimeout); + + // 6. Generate a random number between 0 and creditsWorkRate. + const creditsEarned = chance.integer({ + min: 0, + 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"); + + // 8. Send embed. + await interaction.editReply({ + embeds: [ + EmbedSuccess.setDescription( + `You worked and earned **${creditsEarned}** credits! You now have **${createGuildMember.creditsEarned}** credits. :tada:` + ), + ], + }); };