diff --git a/src/commands/config/index.ts b/src/commands/config/index.ts index 815aa7c..3fddfaf 100644 --- a/src/commands/config/index.ts +++ b/src/commands/config/index.ts @@ -1,51 +1,72 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; -// Modules -import moduleAudits from "./modules/audits"; -import moduleCpgg from "./modules/cpgg"; -import moduleCredits from "./modules/credits"; -import moduleEmbeds from "./modules/embeds"; -import modulePoints from "./modules/points"; -import moduleShop from "./modules/shop"; -import moduleWelcome from "./modules/welcome"; +// Subcommands +import { + builder as AuditsBuilder, + execute as AuditsExecute, +} from "./subcommands/audits"; +import { + builder as CpggBuilder, + execute as CpggExecute, +} from "./subcommands/cpgg"; +import { + builder as CreditsBuilder, + execute as CreditsExecute, +} from "./subcommands/credits"; +import { + builder as EmbedsBuilder, + execute as EmbedsExecute, +} from "./subcommands/embeds"; +import { + builder as PointsBuilder, + execute as PointsExecute, +} from "./subcommands/points"; +import { + builder as ShopBuilder, + execute as ShopExecute, +} from "./subcommands/shop"; +import { + builder as WelcomeBuilder, + execute as WelcomeExecute, +} from "./subcommands/welcome"; export const builder = new SlashCommandBuilder() .setName("config") .setDescription("Manage guild configurations.") .setDMPermission(false) - // Modules - .addSubcommand(moduleAudits.builder) - .addSubcommand(moduleCpgg.builder) - .addSubcommand(moduleCredits.builder) - .addSubcommand(moduleEmbeds.builder) - .addSubcommand(modulePoints.builder) - .addSubcommand(moduleShop.builder) - .addSubcommand(moduleWelcome.builder); + // Subcommands + .addSubcommand(AuditsBuilder) + .addSubcommand(CpggBuilder) + .addSubcommand(CreditsBuilder) + .addSubcommand(EmbedsBuilder) + .addSubcommand(PointsBuilder) + .addSubcommand(ShopBuilder) + .addSubcommand(WelcomeBuilder); // Execute function export const execute = async (interaction: ChatInputCommandInteraction) => { switch (interaction.options.getSubcommand()) { case "audits": - await moduleAudits.execute(interaction); + await AuditsExecute(interaction); break; case "cpgg": - await moduleCpgg.execute(interaction); + await CpggExecute(interaction); break; case "credits": - await moduleCredits.execute(interaction); + await CreditsExecute(interaction); break; case "embeds": - await moduleEmbeds.execute(interaction); + await EmbedsExecute(interaction); break; case "points": - await modulePoints.execute(interaction); + await PointsExecute(interaction); break; case "shop": - await moduleShop.execute(interaction); + await ShopExecute(interaction); break; case "welcome": - await moduleWelcome.execute(interaction); + await WelcomeExecute(interaction); break; default: throw new Error("No module found for that specific command."); diff --git a/src/commands/config/modules/audits/index.ts b/src/commands/config/modules/audits/index.ts deleted file mode 100644 index ef8d46c..0000000 --- a/src/commands/config/modules/audits/index.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - ChannelType, - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import prisma from "../../../../handlers/database"; -import deferReply from "../../../../handlers/deferReply"; -import checkPermission from "../../../../helpers/checkPermission"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -import logger from "../../../../middlewares/logger"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("audits") - .setDescription("Audits") - .addBooleanOption((option) => - option - .setName("status") - .setDescription("Should audits be enabled?") - .setRequired(true) - ) - .addChannelOption((option) => - option - .setName("channel") - .setDescription("Channel for audit messages.") - .addChannelTypes(ChannelType.GuildText) - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { guild, options } = interaction; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - guild - ); - const status = options.getBoolean("status"); - const channel = options.getChannel("channel"); - - if (!guild) throw new Error("Guild not found."); - if (!channel) throw new Error("Channel not found."); - if (status === null) throw new Error("Status not found."); - - const createGuild = await prisma.guild.upsert({ - where: { - id: guild.id, - }, - update: { - auditsEnabled: status, - auditsChannelId: channel.id, - }, - create: { - id: guild.id, - auditsEnabled: status, - auditsChannelId: channel.id, - }, - }); - - logger.silly(createGuild); - - const embedSuccess = new EmbedBuilder() - .setTitle("[:hammer:] Audits") - .setDescription("Guild configuration updated successfully.") - .setColor(successColor) - .addFields( - { - name: "🤖 Status", - value: `${ - createGuild.auditsEnabled - ? ":white_check_mark: Enabled" - : ":x: Disabled" - }`, - inline: true, - }, - { - name: "🌊 Channel", - value: `<#${createGuild.auditsChannelId}>`, - inline: true, - } - ) - .setTimestamp() - .setFooter({ - iconURL: footerIcon, - text: footerText, - }); - - await interaction.editReply({ - embeds: [embedSuccess], - }); - return; - }, -}; diff --git a/src/commands/config/modules/cpgg/index.ts b/src/commands/config/modules/cpgg/index.ts deleted file mode 100644 index 616b9bc..0000000 --- a/src/commands/config/modules/cpgg/index.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import prisma from "../../../../handlers/database"; -import deferReply from "../../../../handlers/deferReply"; -import checkPermission from "../../../../helpers/checkPermission"; -import encryption from "../../../../helpers/encryption"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -import logger from "../../../../middlewares/logger"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("cpgg") - .setDescription("Controlpanel.gg") - .addStringOption((option) => - option - .setName("scheme") - .setDescription(`Controlpanel.gg Scheme`) - .setRequired(true) - .setChoices( - { name: "HTTPS (secure)", value: "https" }, - { name: "HTTP (insecure)", value: "http" } - ) - ) - .addStringOption((option) => - option - .setName("domain") - .setDescription(`Controlpanel.gg Domain`) - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("token") - .setDescription(`Controlpanel.gg Application API`) - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const tokenData = options.getString("token"); - const scheme = options.getString("scheme"); - const domain = options.getString("domain"); - const token = tokenData && encryption.encrypt(tokenData); - const url = scheme && domain && encryption.encrypt(`${scheme}://${domain}`); - - if (!guild) throw new Error("No guild found"); - if (!token) throw new Error("Token not found"); - if (!url) throw new Error("URL not found"); - - const createGuild = await prisma.guild.upsert({ - where: { - id: guild.id, - }, - update: { - apiCpggTokenIv: token.iv, - apiCpggTokenContent: token.content, - apiCpggUrlIv: url.iv, - apiCpggUrlContent: url.content, - }, - create: { - id: guild.id, - apiCpggTokenIv: token.iv, - apiCpggTokenContent: token.content, - apiCpggUrlIv: url.iv, - apiCpggUrlContent: url.content, - }, - }); - - logger.silly(createGuild); - - logger?.silly(`Updated API credentials.`); - - const interactionEmbed = new EmbedBuilder() - .setTitle("[:tools:] CPGG") - .setDescription( - `The following configuration will be used. - -**Scheme**: ${scheme} -**Domain**: ${domain} -**Token**: ends with ${tokenData?.slice(-4)}` - ) - .setColor(successColor) - .setTimestamp() - .setFooter({ - iconURL: footerIcon, - text: footerText, - }); - - await interaction?.editReply({ - embeds: [interactionEmbed], - }); - return; - }, -}; diff --git a/src/commands/config/modules/credits/index.ts b/src/commands/config/modules/credits/index.ts deleted file mode 100644 index 9d9360c..0000000 --- a/src/commands/config/modules/credits/index.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import prisma from "../../../../handlers/database"; -import deferReply from "../../../../handlers/deferReply"; -import checkPermission from "../../../../helpers/checkPermission"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -import logger from "../../../../middlewares/logger"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("credits") - .setDescription(`Configure this guild's credits module.`) - .addBooleanOption((option) => - option - .setName("enabled") - .setDescription("Do you want to activate the credit module?") - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("rate") - .setDescription("Credit rate per message.") - .setRequired(true) - .setMinValue(1) - ) - .addNumberOption((option) => - option - .setName("minimum-length") - .setDescription("Minimum message length to receive credit.") - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("work-rate") - .setDescription( - "The maximum amount of credit that can be obtained within a working day." - ) - .setRequired(true) - .setMinValue(1) - ) - .addNumberOption((option) => - option - .setName("work-timeout") - .setDescription( - "How long you need to wait before you can work again provided in seconds." - ) - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("timeout") - .setDescription( - "How long you need to wait before you can earn more credits." - ) - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { guild, options } = interaction; - - const enabled = options.getBoolean("enabled"); - const rate = options.getNumber("rate"); - const timeout = options.getNumber("timeout"); - const minimumLength = options.getNumber("minimum-length"); - const workRate = options.getNumber("work-rate"); - const workTimeout = options.getNumber("work-timeout"); - - if (!guild) throw new Error("Guild not found."); - if (typeof enabled !== "boolean") - throw new Error("Enabled option is not a boolean."); - if (typeof rate !== "number") throw new Error("Rate is not a number."); - if (typeof workRate !== "number") - throw new Error("Work rate is not a number."); - if (typeof workTimeout !== "number") - throw new Error("Work timeout is not a number."); - if (typeof timeout !== "number") - throw new Error("Timeout is not a number."); - if (typeof minimumLength !== "number") - throw new Error("Minimum length is not a number."); - - const createGuild = await prisma.guild.upsert({ - where: { - id: guild.id, - }, - update: { - creditsEnabled: enabled, - creditsRate: rate, - creditsTimeout: timeout, - creditsWorkRate: workRate, - creditsWorkTimeout: workTimeout, - creditsMinimumLength: minimumLength, - }, - create: { - id: guild.id, - creditsEnabled: enabled, - creditsRate: rate, - creditsTimeout: timeout, - creditsWorkRate: workRate, - creditsWorkTimeout: workTimeout, - creditsMinimumLength: minimumLength, - }, - }); - - logger.silly(createGuild); - - const interactionEmbed = new EmbedBuilder() - .setTitle("[:tools:] Credits") - .setDescription("Credits settings updated") - .setColor(successColor) - .addFields( - { - name: "🤖 Enabled?", - value: `${createGuild.creditsEnabled}`, - inline: true, - }, - { - name: "📈 Rate", - value: `${createGuild.creditsRate}`, - inline: true, - }, - { - name: "📈 Work Rate", - value: `${createGuild.creditsWorkRate}`, - inline: true, - }, - { - name: "🔨 Minimum Length", - value: `${createGuild.creditsMinimumLength}`, - inline: true, - }, - { - name: "⏰ Timeout", - value: `${createGuild.creditsTimeout}`, - inline: true, - }, - { - name: "⏰ Work Timeout", - value: `${createGuild.creditsWorkTimeout}`, - inline: true, - } - ) - .setTimestamp() - .setFooter({ - iconURL: footerIcon, - text: footerText, - }); - - await interaction.editReply({ - embeds: [interactionEmbed], - }); - return; - }, -}; diff --git a/src/commands/config/modules/embeds/index.ts b/src/commands/config/modules/embeds/index.ts deleted file mode 100644 index acff478..0000000 --- a/src/commands/config/modules/embeds/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; - -import deferReply from "../../../../handlers/deferReply"; -import checkPermission from "../../../../helpers/checkPermission"; -import getValues from "./components/getValues"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("embeds") - .setDescription(`Embeds`) - .addStringOption((option) => - option - .setName("success-color") - .setDescription("No provided description") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("wait-color") - .setDescription("No provided description") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("error-color") - .setDescription("No provided description") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("footer-icon") - .setDescription("No provided description") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("footer-text") - .setDescription("No provided description") - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { guild } = interaction; - if (!guild) throw new Error("Guild not found"); - - const { successColor, waitColor, errorColor, footerText, footerIcon } = - await getValues(interaction); - - const embed = new EmbedBuilder() - .setTitle("[:tools:] Embeds") - .setFooter({ text: footerText, iconURL: footerIcon }) - .setTimestamp(new Date()); - - embed - .setDescription("Following embed configuration will be used.") - .setColor(successColor) - .addFields([ - { - name: "🟢 Success Color", - value: `${successColor}`, - inline: true, - }, - { - name: "🟡 Wait Color", - value: `${waitColor}`, - inline: true, - }, - { - name: "🔴 Error Color", - value: `${errorColor}`, - inline: true, - }, - { - name: "🖼️ Footer Icon", - value: `${footerIcon}`, - inline: true, - }, - { - name: "📄 Footer Text", - value: `${footerText}`, - inline: true, - }, - ]); - - await interaction.editReply({ - embeds: [embed], - }); - return; - }, -}; diff --git a/src/commands/config/modules/points/index.ts b/src/commands/config/modules/points/index.ts deleted file mode 100644 index 4a411f7..0000000 --- a/src/commands/config/modules/points/index.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import prisma from "../../../../handlers/database"; -import deferReply from "../../../../handlers/deferReply"; -import checkPermission from "../../../../helpers/checkPermission"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -import logger from "../../../../middlewares/logger"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("points") - .setDescription("Points") - .addBooleanOption((option) => - option - .setName("status") - .setDescription("Should credits be enabled?") - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("rate") - .setDescription("Amount of credits per message.") - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("minimum-length") - .setDescription("Minimum length of message to earn credits.") - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("timeout") - .setDescription("Timeout between earning credits (milliseconds).") - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - const { options, guild } = interaction; - - const status = options?.getBoolean("status"); - const rate = options?.getNumber("rate"); - const timeout = options?.getNumber("timeout"); - const minimumLength = options?.getNumber("minimum-length"); - - if (!guild) throw new Error("Guild is required"); - if (status === null) throw new Error("Status must be specified"); - if (!rate) throw new Error("Rate must be specified"); - if (!timeout) throw new Error("Timeout must be specified"); - if (!minimumLength) throw new Error("Minimum length must be specified"); - - const createGuild = await prisma.guild.upsert({ - where: { - id: guild.id, - }, - update: { - pointsEnabled: status, - pointsRate: rate, - pointsTimeout: timeout, - pointsMinimumLength: minimumLength, - }, - create: { - id: guild.id, - pointsEnabled: status, - pointsRate: rate, - pointsTimeout: timeout, - pointsMinimumLength: minimumLength, - }, - }); - - logger.silly(createGuild); - - const interactionEmbed = new EmbedBuilder() - .setTitle("[:tools:] Points") - .setDescription("Points settings updated") - .setColor(successColor) - .addFields( - { - name: "🤖 Status", - value: `${createGuild.pointsEnabled}`, - inline: true, - }, - { - name: "📈 Rate", - value: `${createGuild.pointsRate}`, - inline: true, - }, - { - name: "🔨 Minimum Length", - value: `${createGuild.pointsMinimumLength}`, - inline: true, - }, - { - name: "⏰ Timeout", - value: `${createGuild.pointsTimeout}`, - inline: true, - } - ) - .setTimestamp() - .setFooter({ - iconURL: footerIcon, - text: footerText, - }); - - await interaction?.editReply({ - embeds: [interactionEmbed], - }); - return; - }, -}; diff --git a/src/commands/config/modules/shop/index.ts b/src/commands/config/modules/shop/index.ts deleted file mode 100644 index 21c1b27..0000000 --- a/src/commands/config/modules/shop/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import prisma from "../../../../handlers/database"; -import deferReply from "../../../../handlers/deferReply"; -import checkPermission from "../../../../helpers/checkPermission"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -import logger from "../../../../middlewares/logger"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("shop") - .setDescription("Shop") - .addBooleanOption((option) => - option - .setName("roles-status") - .setDescription("Should roles be enabled?") - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("roles-price-per-hour") - .setDescription("Price per hour for roles.") - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const rolesStatus = options?.getBoolean("roles-status"); - const rolesPricePerHour = options?.getNumber("roles-price-per-hour"); - - if (!guild) throw new Error("Guild not found"); - if (rolesStatus === null) throw new Error("Status must be provided"); - if (!rolesPricePerHour) - throw new Error("Roles price per hour must be provided"); - - const createGuild = await prisma.guild.upsert({ - where: { - id: guild.id, - }, - update: { - shopRolesEnabled: rolesStatus, - shopRolesPricePerHour: rolesPricePerHour, - }, - create: { - id: guild.id, - shopRolesEnabled: rolesStatus, - shopRolesPricePerHour: rolesPricePerHour, - }, - }); - - logger.silly(createGuild); - - const interactionEmbed = new EmbedBuilder() - .setTitle("[:tools:] Shop") - .setDescription("Shop settings updated") - .setColor(successColor) - .addFields( - { - name: "🤖 Roles Status", - value: `${createGuild.shopRolesEnabled}`, - inline: true, - }, - { - name: "🌊 Roles Price Per Hour", - value: `${createGuild.shopRolesPricePerHour}`, - inline: true, - } - ) - .setTimestamp() - .setFooter({ - iconURL: footerIcon, - text: footerText, - }); - - await interaction?.editReply({ - embeds: [interactionEmbed], - }); - return; - }, -}; diff --git a/src/commands/config/modules/welcome/index.ts b/src/commands/config/modules/welcome/index.ts deleted file mode 100644 index 73efefe..0000000 --- a/src/commands/config/modules/welcome/index.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { - ChannelType, - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import prisma from "../../../../handlers/database"; -import deferReply from "../../../../handlers/deferReply"; -import checkPermission from "../../../../helpers/checkPermission"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -import logger from "../../../../middlewares/logger"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("welcome") - .setDescription("Welcome") - .addBooleanOption((option) => - option - .setName("status") - .setDescription("Should welcome be enabled?") - .setRequired(true) - ) - .addChannelOption((option) => - option - .setName("join-channel") - .setDescription("Channel for join messages.") - .addChannelTypes(ChannelType.GuildText) - .setRequired(true) - ) - - .addChannelOption((option) => - option - .setName("leave-channel") - .setDescription("Channel for leave messages.") - .addChannelTypes(ChannelType.GuildText) - .setRequired(true) - ) - - .addStringOption((option) => - option - .setName("leave-message") - .setDescription("Message for leave messages.") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("join-message") - .setDescription("Message for join messages.") - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const status = options?.getBoolean("status"); - const joinChannel = options?.getChannel("join-channel"); - const leaveChannel = options?.getChannel("leave-channel"); - const joinChannelMessage = options?.getString("join-message"); - const leaveChannelMessage = options?.getString("leave-message"); - - if (!guild) throw new Error("Guild not found"); - if (status === null) throw new Error("Status not specified"); - if (!joinChannel) throw new Error("Join channel not specified"); - if (!joinChannelMessage) - throw new Error("Join channel message not specified"); - if (!leaveChannel) throw new Error("Leave channel not specified"); - if (!leaveChannelMessage) - throw new Error("Leave channel message not specified"); - - const createGuild = await prisma.guild.upsert({ - where: { - id: guild.id, - }, - update: { - welcomeEnabled: status, - welcomeJoinChannelId: joinChannel.id, - welcomeJoinChannelMessage: joinChannelMessage, - welcomeLeaveChannelId: leaveChannel.id, - welcomeLeaveChannelMessage: leaveChannelMessage, - }, - create: { - id: guild.id, - welcomeEnabled: status, - welcomeJoinChannelId: joinChannel.id, - welcomeJoinChannelMessage: joinChannelMessage, - welcomeLeaveChannelId: leaveChannel.id, - welcomeLeaveChannelMessage: leaveChannelMessage, - }, - }); - - logger.silly(createGuild); - - const interactionEmbedDisabled = new EmbedBuilder() - .setTitle("[:tools:] Welcome") - .setDescription( - "This module is currently disabled, please enable it to continue." - ) - .setColor(successColor) - .setTimestamp() - .setFooter({ - iconURL: footerIcon, - text: footerText, - }); - - if (!createGuild.welcomeEnabled) { - return interaction?.editReply({ - embeds: [interactionEmbedDisabled], - }); - } - - const interactionEmbed = new EmbedBuilder() - .setTitle("[:tools:] Welcome") - .setDescription( - `The following configuration will be used. - - [👋] **Welcome** - - ㅤ**Channel**: <#${createGuild.welcomeJoinChannelId}> - ㅤ**Message**: ${createGuild.welcomeJoinChannelMessage} - - [🚪] **Leave** - - ㅤ**Channel**: <#${createGuild.welcomeLeaveChannelId}> - ㅤ**Message**: ${createGuild.welcomeLeaveChannelMessage}` - ) - .setColor(successColor) - .setTimestamp() - .setFooter({ - iconURL: footerIcon, - text: footerText, - }); - - await interaction?.editReply({ - embeds: [interactionEmbed], - }); - return true; - }, -}; diff --git a/src/commands/config/subcommands/audits/index.ts b/src/commands/config/subcommands/audits/index.ts new file mode 100644 index 0000000..843c2db --- /dev/null +++ b/src/commands/config/subcommands/audits/index.ts @@ -0,0 +1,94 @@ +import { + ChannelType, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("audits") + .setDescription("Audits") + .addBooleanOption((option) => + option + .setName("status") + .setDescription("Should audits be enabled?") + .setRequired(true) + ) + .addChannelOption((option) => + option + .setName("channel") + .setDescription("Channel for audit messages.") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { guild, options } = interaction; + const { successColor, footerText, footerIcon } = await getEmbedConfig(guild); + const status = options.getBoolean("status"); + const channel = options.getChannel("channel"); + + if (!guild) throw new Error("Guild not found."); + if (!channel) throw new Error("Channel not found."); + if (status === null) throw new Error("Status not found."); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + auditsEnabled: status, + auditsChannelId: channel.id, + }, + create: { + id: guild.id, + auditsEnabled: status, + auditsChannelId: channel.id, + }, + }); + + logger.silly(createGuild); + + const embedSuccess = new EmbedBuilder() + .setTitle("[:hammer:] Audits") + .setDescription("Guild configuration updated successfully.") + .setColor(successColor) + .addFields( + { + name: "🤖 Status", + value: `${ + createGuild.auditsEnabled + ? ":white_check_mark: Enabled" + : ":x: Disabled" + }`, + inline: true, + }, + { + name: "🌊 Channel", + value: `<#${createGuild.auditsChannelId}>`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction.editReply({ + embeds: [embedSuccess], + }); + return; +}; diff --git a/src/commands/config/subcommands/cpgg/index.ts b/src/commands/config/subcommands/cpgg/index.ts new file mode 100644 index 0000000..04928b0 --- /dev/null +++ b/src/commands/config/subcommands/cpgg/index.ts @@ -0,0 +1,105 @@ +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import encryption from "../../../../helpers/encryption"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("cpgg") + .setDescription("Controlpanel.gg") + .addStringOption((option) => + option + .setName("scheme") + .setDescription(`Controlpanel.gg Scheme`) + .setRequired(true) + .setChoices( + { name: "HTTPS (secure)", value: "https" }, + { name: "HTTP (insecure)", value: "http" } + ) + ) + .addStringOption((option) => + option + .setName("domain") + .setDescription(`Controlpanel.gg Domain`) + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("token") + .setDescription(`Controlpanel.gg Application API`) + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const tokenData = options.getString("token"); + const scheme = options.getString("scheme"); + const domain = options.getString("domain"); + const token = tokenData && encryption.encrypt(tokenData); + const url = scheme && domain && encryption.encrypt(`${scheme}://${domain}`); + + if (!guild) throw new Error("No guild found"); + if (!token) throw new Error("Token not found"); + if (!url) throw new Error("URL not found"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + apiCpggTokenIv: token.iv, + apiCpggTokenContent: token.content, + apiCpggUrlIv: url.iv, + apiCpggUrlContent: url.content, + }, + create: { + id: guild.id, + apiCpggTokenIv: token.iv, + apiCpggTokenContent: token.content, + apiCpggUrlIv: url.iv, + apiCpggUrlContent: url.content, + }, + }); + + logger.silly(createGuild); + + logger?.silly(`Updated API credentials.`); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] CPGG") + .setDescription( + `The following configuration will be used. + +**Scheme**: ${scheme} +**Domain**: ${domain} +**Token**: ends with ${tokenData?.slice(-4)}` + ) + .setColor(successColor) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return; +}; diff --git a/src/commands/config/subcommands/credits/index.ts b/src/commands/config/subcommands/credits/index.ts new file mode 100644 index 0000000..f69f208 --- /dev/null +++ b/src/commands/config/subcommands/credits/index.ts @@ -0,0 +1,163 @@ +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("credits") + .setDescription(`Configure this guild's credits module.`) + .addBooleanOption((option) => + option + .setName("enabled") + .setDescription("Do you want to activate the credit module?") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("rate") + .setDescription("Credit rate per message.") + .setRequired(true) + .setMinValue(1) + ) + .addNumberOption((option) => + option + .setName("minimum-length") + .setDescription("Minimum message length to receive credit.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("work-rate") + .setDescription( + "The maximum amount of credit that can be obtained within a working day." + ) + .setRequired(true) + .setMinValue(1) + ) + .addNumberOption((option) => + option + .setName("work-timeout") + .setDescription( + "How long you need to wait before you can work again provided in seconds." + ) + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("timeout") + .setDescription( + "How long you need to wait before you can earn more credits." + ) + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { guild, options } = interaction; + + const enabled = options.getBoolean("enabled"); + const rate = options.getNumber("rate"); + const timeout = options.getNumber("timeout"); + const minimumLength = options.getNumber("minimum-length"); + const workRate = options.getNumber("work-rate"); + const workTimeout = options.getNumber("work-timeout"); + + if (!guild) throw new Error("Guild not found."); + if (typeof enabled !== "boolean") + throw new Error("Enabled option is not a boolean."); + if (typeof rate !== "number") throw new Error("Rate is not a number."); + if (typeof workRate !== "number") + throw new Error("Work rate is not a number."); + if (typeof workTimeout !== "number") + throw new Error("Work timeout is not a number."); + if (typeof timeout !== "number") throw new Error("Timeout is not a number."); + if (typeof minimumLength !== "number") + throw new Error("Minimum length is not a number."); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + creditsEnabled: enabled, + creditsRate: rate, + creditsTimeout: timeout, + creditsWorkRate: workRate, + creditsWorkTimeout: workTimeout, + creditsMinimumLength: minimumLength, + }, + create: { + id: guild.id, + creditsEnabled: enabled, + creditsRate: rate, + creditsTimeout: timeout, + creditsWorkRate: workRate, + creditsWorkTimeout: workTimeout, + creditsMinimumLength: minimumLength, + }, + }); + + logger.silly(createGuild); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Credits") + .setDescription("Credits settings updated") + .setColor(successColor) + .addFields( + { + name: "🤖 Enabled?", + value: `${createGuild.creditsEnabled}`, + inline: true, + }, + { + name: "📈 Rate", + value: `${createGuild.creditsRate}`, + inline: true, + }, + { + name: "📈 Work Rate", + value: `${createGuild.creditsWorkRate}`, + inline: true, + }, + { + name: "🔨 Minimum Length", + value: `${createGuild.creditsMinimumLength}`, + inline: true, + }, + { + name: "⏰ Timeout", + value: `${createGuild.creditsTimeout}`, + inline: true, + }, + { + name: "⏰ Work Timeout", + value: `${createGuild.creditsWorkTimeout}`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction.editReply({ + embeds: [interactionEmbed], + }); + return; +}; diff --git a/src/commands/config/modules/embeds/components/getValues/index.ts b/src/commands/config/subcommands/embeds/components/getValues/index.ts similarity index 100% rename from src/commands/config/modules/embeds/components/getValues/index.ts rename to src/commands/config/subcommands/embeds/components/getValues/index.ts diff --git a/src/commands/config/subcommands/embeds/index.ts b/src/commands/config/subcommands/embeds/index.ts new file mode 100644 index 0000000..f861b70 --- /dev/null +++ b/src/commands/config/subcommands/embeds/index.ts @@ -0,0 +1,99 @@ +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; + +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getValues from "./components/getValues"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("embeds") + .setDescription(`Embeds`) + .addStringOption((option) => + option + .setName("success-color") + .setDescription("No provided description") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("wait-color") + .setDescription("No provided description") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("error-color") + .setDescription("No provided description") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("footer-icon") + .setDescription("No provided description") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("footer-text") + .setDescription("No provided description") + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { guild } = interaction; + if (!guild) throw new Error("Guild not found"); + + const { successColor, waitColor, errorColor, footerText, footerIcon } = + await getValues(interaction); + + const embed = new EmbedBuilder() + .setTitle("[:tools:] Embeds") + .setFooter({ text: footerText, iconURL: footerIcon }) + .setTimestamp(new Date()); + + embed + .setDescription("Following embed configuration will be used.") + .setColor(successColor) + .addFields([ + { + name: "🟢 Success Color", + value: `${successColor}`, + inline: true, + }, + { + name: "🟡 Wait Color", + value: `${waitColor}`, + inline: true, + }, + { + name: "🔴 Error Color", + value: `${errorColor}`, + inline: true, + }, + { + name: "🖼️ Footer Icon", + value: `${footerIcon}`, + inline: true, + }, + { + name: "📄 Footer Text", + value: `${footerText}`, + inline: true, + }, + ]); + + await interaction.editReply({ + embeds: [embed], + }); + return; +}; diff --git a/src/commands/config/subcommands/points/index.ts b/src/commands/config/subcommands/points/index.ts new file mode 100644 index 0000000..e7d206b --- /dev/null +++ b/src/commands/config/subcommands/points/index.ts @@ -0,0 +1,122 @@ +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("points") + .setDescription("Points") + .addBooleanOption((option) => + option + .setName("status") + .setDescription("Should credits be enabled?") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("rate") + .setDescription("Amount of credits per message.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("minimum-length") + .setDescription("Minimum length of message to earn credits.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("timeout") + .setDescription("Timeout between earning credits (milliseconds).") + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const { options, guild } = interaction; + + const status = options?.getBoolean("status"); + const rate = options?.getNumber("rate"); + const timeout = options?.getNumber("timeout"); + const minimumLength = options?.getNumber("minimum-length"); + + if (!guild) throw new Error("Guild is required"); + if (status === null) throw new Error("Status must be specified"); + if (!rate) throw new Error("Rate must be specified"); + if (!timeout) throw new Error("Timeout must be specified"); + if (!minimumLength) throw new Error("Minimum length must be specified"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + pointsEnabled: status, + pointsRate: rate, + pointsTimeout: timeout, + pointsMinimumLength: minimumLength, + }, + create: { + id: guild.id, + pointsEnabled: status, + pointsRate: rate, + pointsTimeout: timeout, + pointsMinimumLength: minimumLength, + }, + }); + + logger.silly(createGuild); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Points") + .setDescription("Points settings updated") + .setColor(successColor) + .addFields( + { + name: "🤖 Status", + value: `${createGuild.pointsEnabled}`, + inline: true, + }, + { + name: "📈 Rate", + value: `${createGuild.pointsRate}`, + inline: true, + }, + { + name: "🔨 Minimum Length", + value: `${createGuild.pointsMinimumLength}`, + inline: true, + }, + { + name: "⏰ Timeout", + value: `${createGuild.pointsTimeout}`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return; +}; diff --git a/src/commands/config/subcommands/shop/index.ts b/src/commands/config/subcommands/shop/index.ts new file mode 100644 index 0000000..d3f6ec1 --- /dev/null +++ b/src/commands/config/subcommands/shop/index.ts @@ -0,0 +1,92 @@ +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("shop") + .setDescription("Shop") + .addBooleanOption((option) => + option + .setName("roles-status") + .setDescription("Should roles be enabled?") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("roles-price-per-hour") + .setDescription("Price per hour for roles.") + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const rolesStatus = options?.getBoolean("roles-status"); + const rolesPricePerHour = options?.getNumber("roles-price-per-hour"); + + if (!guild) throw new Error("Guild not found"); + if (rolesStatus === null) throw new Error("Status must be provided"); + if (!rolesPricePerHour) + throw new Error("Roles price per hour must be provided"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + shopRolesEnabled: rolesStatus, + shopRolesPricePerHour: rolesPricePerHour, + }, + create: { + id: guild.id, + shopRolesEnabled: rolesStatus, + shopRolesPricePerHour: rolesPricePerHour, + }, + }); + + logger.silly(createGuild); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Shop") + .setDescription("Shop settings updated") + .setColor(successColor) + .addFields( + { + name: "🤖 Roles Status", + value: `${createGuild.shopRolesEnabled}`, + inline: true, + }, + { + name: "🌊 Roles Price Per Hour", + value: `${createGuild.shopRolesPricePerHour}`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return; +}; diff --git a/src/commands/config/subcommands/welcome/index.ts b/src/commands/config/subcommands/welcome/index.ts new file mode 100644 index 0000000..73c4262 --- /dev/null +++ b/src/commands/config/subcommands/welcome/index.ts @@ -0,0 +1,146 @@ +import { + ChannelType, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("welcome") + .setDescription("Welcome") + .addBooleanOption((option) => + option + .setName("status") + .setDescription("Should welcome be enabled?") + .setRequired(true) + ) + .addChannelOption((option) => + option + .setName("join-channel") + .setDescription("Channel for join messages.") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ) + + .addChannelOption((option) => + option + .setName("leave-channel") + .setDescription("Channel for leave messages.") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ) + + .addStringOption((option) => + option + .setName("leave-message") + .setDescription("Message for leave messages.") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("join-message") + .setDescription("Message for join messages.") + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const status = options?.getBoolean("status"); + const joinChannel = options?.getChannel("join-channel"); + const leaveChannel = options?.getChannel("leave-channel"); + const joinChannelMessage = options?.getString("join-message"); + const leaveChannelMessage = options?.getString("leave-message"); + + if (!guild) throw new Error("Guild not found"); + if (status === null) throw new Error("Status not specified"); + if (!joinChannel) throw new Error("Join channel not specified"); + if (!joinChannelMessage) + throw new Error("Join channel message not specified"); + if (!leaveChannel) throw new Error("Leave channel not specified"); + if (!leaveChannelMessage) + throw new Error("Leave channel message not specified"); + + const createGuild = await prisma.guild.upsert({ + where: { + id: guild.id, + }, + update: { + welcomeEnabled: status, + welcomeJoinChannelId: joinChannel.id, + welcomeJoinChannelMessage: joinChannelMessage, + welcomeLeaveChannelId: leaveChannel.id, + welcomeLeaveChannelMessage: leaveChannelMessage, + }, + create: { + id: guild.id, + welcomeEnabled: status, + welcomeJoinChannelId: joinChannel.id, + welcomeJoinChannelMessage: joinChannelMessage, + welcomeLeaveChannelId: leaveChannel.id, + welcomeLeaveChannelMessage: leaveChannelMessage, + }, + }); + + logger.silly(createGuild); + + const interactionEmbedDisabled = new EmbedBuilder() + .setTitle("[:tools:] Welcome") + .setDescription( + "This module is currently disabled, please enable it to continue." + ) + .setColor(successColor) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + if (!createGuild.welcomeEnabled) { + return interaction?.editReply({ + embeds: [interactionEmbedDisabled], + }); + } + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:tools:] Welcome") + .setDescription( + `The following configuration will be used. + + [👋] **Welcome** + + ㅤ**Channel**: <#${createGuild.welcomeJoinChannelId}> + ㅤ**Message**: ${createGuild.welcomeJoinChannelMessage} + + [🚪] **Leave** + + ㅤ**Channel**: <#${createGuild.welcomeLeaveChannelId}> + ㅤ**Message**: ${createGuild.welcomeLeaveChannelMessage}` + ) + .setColor(successColor) + .setTimestamp() + .setFooter({ + iconURL: footerIcon, + text: footerText, + }); + + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + return true; +}; diff --git a/src/commands/counters/index.ts b/src/commands/counters/index.ts index bcb2991..3f2ff3f 100644 --- a/src/commands/counters/index.ts +++ b/src/commands/counters/index.ts @@ -1,7 +1,10 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; -// Modules -import { builder as ViewBuilder, execute as ViewExecute } from "./modules/view"; +// Subcommands +import { + builder as ViewBuilder, + execute as ViewExecute, +} from "./subcommands/view"; // export const builder = new SlashCommandBuilder() @@ -9,7 +12,7 @@ export const builder = new SlashCommandBuilder() .setDescription("View guild counters") .setDMPermission(false) - // Modules + // Subcommands .addSubcommand(ViewBuilder); // Execute function diff --git a/src/commands/counters/modules/view/index.ts b/src/commands/counters/subcommands/view/index.ts similarity index 100% rename from src/commands/counters/modules/view/index.ts rename to src/commands/counters/subcommands/view/index.ts diff --git a/src/commands/credits/index.ts b/src/commands/credits/index.ts index 1a103ba..9e20213 100644 --- a/src/commands/credits/index.ts +++ b/src/commands/credits/index.ts @@ -1,13 +1,22 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; -// Modules +// Subcommands 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"; +} from "./subcommands/balance"; +import { + builder as GiftBuilder, + execute as GiftExecute, +} from "./subcommands/gift"; +import { + builder as TopBuilder, + execute as TopExecute, +} from "./subcommands/top"; +import { + builder as WorkBuilder, + execute as WorkExecute, +} from "./subcommands/work"; // 1. Export a builder function. export const builder = new SlashCommandBuilder() diff --git a/src/commands/credits/modules/balance/index.ts b/src/commands/credits/subcommands/balance/index.ts similarity index 100% rename from src/commands/credits/modules/balance/index.ts rename to src/commands/credits/subcommands/balance/index.ts diff --git a/src/commands/credits/modules/gift/index.ts b/src/commands/credits/subcommands/gift/index.ts similarity index 100% rename from src/commands/credits/modules/gift/index.ts rename to src/commands/credits/subcommands/gift/index.ts diff --git a/src/commands/credits/modules/top/index.ts b/src/commands/credits/subcommands/top/index.ts similarity index 100% rename from src/commands/credits/modules/top/index.ts rename to src/commands/credits/subcommands/top/index.ts diff --git a/src/commands/credits/modules/work/index.ts b/src/commands/credits/subcommands/work/index.ts similarity index 100% rename from src/commands/credits/modules/work/index.ts rename to src/commands/credits/subcommands/work/index.ts diff --git a/src/commands/dns/index.ts b/src/commands/dns/index.ts index b252bfe..64039fe 100644 --- a/src/commands/dns/index.ts +++ b/src/commands/dns/index.ts @@ -1,20 +1,23 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; -// Modules -import moduleLookup from "./modules/lookup"; +// Subcommands +import { + builder as LookupBuilder, + execute as LookupExecute, +} from "./subcommands/lookup"; export const builder = new SlashCommandBuilder() .setName("dns") .setDescription("DNS commands.") - // Modules - .addSubcommand(moduleLookup.builder); + // Subcommands + .addSubcommand(LookupBuilder); // Execute the command export const execute = async (interaction: ChatInputCommandInteraction) => { switch (interaction.options.getSubcommand()) { case "lookup": - await moduleLookup.execute(interaction); + await LookupExecute(interaction); break; default: throw new Error( diff --git a/src/commands/dns/modules/lookup/index.ts b/src/commands/dns/modules/lookup/index.ts deleted file mode 100644 index b4fe4bd..0000000 --- a/src/commands/dns/modules/lookup/index.ts +++ /dev/null @@ -1,133 +0,0 @@ -import axios from "axios"; -import { - ChatInputCommandInteraction, - EmbedBuilder, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import deferReply from "../../../../handlers/deferReply"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("lookup") - .setDescription( - "Lookup a domain or ip. (Request sent over HTTP, proceed with caution!)" - ) - .addStringOption((option) => - option - .setName("query") - .setDescription("The query you want to look up.") - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, false); - - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const embedTitle = "[:hammer:] Utility (Lookup)"; - - const { options } = interaction; - const query = options.getString("query"); - - await axios - .get(`http://ip-api.com/json/${query}`) - .then(async (response) => { - if (response.data.status !== "success") { - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle(embedTitle) - .setFooter({ - text: footerText, - iconURL: footerIcon, - }) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }) - .setDescription( - `${response?.data?.message}: ${response?.data?.query}` - ), - ], - }); - return; - } - - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle(embedTitle) - .setFooter({ - text: footerText, - iconURL: footerIcon, - }) - .setTimestamp(new Date()) - .setColor(successColor) - .setFields([ - { - name: ":classical_building: AS", - value: `${response.data.as || "Unknown"}`, - inline: true, - }, - { - name: ":classical_building: ISP", - value: `${response.data.isp || "Unknown"}`, - inline: true, - }, - { - name: ":classical_building: Organization", - value: `${response.data.org || "Unknown"}`, - inline: true, - }, - { - name: ":compass: Latitude", - value: `${response.data.lat || "Unknown"}`, - inline: true, - }, - { - name: ":compass: Longitude", - value: `${response.data.lon || "Unknown"}`, - inline: true, - }, - { - name: ":clock4: Timezone", - value: `${response.data.timezone || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: Country", - value: `${response.data.country || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: Region", - value: `${response.data.regionName || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: City", - value: `${response.data.city || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: Country Code", - value: `${response.data.countryCode || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: Region Code", - value: `${response.data.region || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: ZIP", - value: `${response.data.zip || "Unknown"}`, - inline: true, - }, - ]), - ], - }); - }); - }, -}; diff --git a/src/commands/dns/subcommands/lookup/index.ts b/src/commands/dns/subcommands/lookup/index.ts new file mode 100644 index 0000000..e67b1a2 --- /dev/null +++ b/src/commands/dns/subcommands/lookup/index.ts @@ -0,0 +1,130 @@ +import axios from "axios"; +import { + ChatInputCommandInteraction, + EmbedBuilder, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("lookup") + .setDescription( + "Lookup a domain or ip. (Request sent over HTTP, proceed with caution!)" + ) + .addStringOption((option) => + option + .setName("query") + .setDescription("The query you want to look up.") + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, false); + + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); + const embedTitle = "[:hammer:] Utility (Lookup)"; + + const { options } = interaction; + const query = options.getString("query"); + + await axios.get(`http://ip-api.com/json/${query}`).then(async (response) => { + if (response.data.status !== "success") { + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle(embedTitle) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }) + .setDescription( + `${response?.data?.message}: ${response?.data?.query}` + ), + ], + }); + return; + } + + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle(embedTitle) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }) + .setTimestamp(new Date()) + .setColor(successColor) + .setFields([ + { + name: ":classical_building: AS", + value: `${response.data.as || "Unknown"}`, + inline: true, + }, + { + name: ":classical_building: ISP", + value: `${response.data.isp || "Unknown"}`, + inline: true, + }, + { + name: ":classical_building: Organization", + value: `${response.data.org || "Unknown"}`, + inline: true, + }, + { + name: ":compass: Latitude", + value: `${response.data.lat || "Unknown"}`, + inline: true, + }, + { + name: ":compass: Longitude", + value: `${response.data.lon || "Unknown"}`, + inline: true, + }, + { + name: ":clock4: Timezone", + value: `${response.data.timezone || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: Country", + value: `${response.data.country || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: Region", + value: `${response.data.regionName || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: City", + value: `${response.data.city || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: Country Code", + value: `${response.data.countryCode || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: Region Code", + value: `${response.data.region || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: ZIP", + value: `${response.data.zip || "Unknown"}`, + inline: true, + }, + ]), + ], + }); + }); +}; diff --git a/src/commands/fun/index.ts b/src/commands/fun/index.ts index f2ab5b8..4147d41 100644 --- a/src/commands/fun/index.ts +++ b/src/commands/fun/index.ts @@ -1,21 +1,24 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; import logger from "../../middlewares/logger"; -// Modules -import moduleMeme from "./modules/meme"; +// Subcommands +import { + builder as MemeBuilder, + execute as MemeExecute, +} from "./subcommands/meme"; export const builder = new SlashCommandBuilder() .setName("fun") .setDescription("Fun commands.") - .addSubcommand(moduleMeme.builder); + .addSubcommand(MemeBuilder); // Execute function export const execute = async (interaction: ChatInputCommandInteraction) => { const { options } = interaction; if (options.getSubcommand() === "meme") { - await moduleMeme.execute(interaction); + await MemeExecute(interaction); } else { logger.silly(`Unknown subcommand ${options.getSubcommand()}`); } diff --git a/src/commands/fun/modules/meme/index.ts b/src/commands/fun/modules/meme/index.ts deleted file mode 100644 index 01089a5..0000000 --- a/src/commands/fun/modules/meme/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -import axios from "axios"; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - CommandInteraction, - EmbedBuilder, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import deferReply from "../../../../handlers/deferReply"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -import cooldown from "../../../../middlewares/cooldown"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command.setName("meme").setDescription("Random memes from r/memes"); - }, - - execute: async (interaction: CommandInteraction) => { - await deferReply(interaction, false); - - const { guild, user, commandId } = interaction; - if (!guild) throw new Error("Server unavailable"); - if (!user) throw new Error("User unavailable"); - - await cooldown(guild, user, commandId, 15); - - const embedConfig = await getEmbedConfig(guild); - - await axios - .get("https://www.reddit.com/r/memes/random/.json") - .then(async (res) => { - const response = res.data[0].data.children; - const content = response[0].data; - - const buttons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("View post") - .setStyle(ButtonStyle.Link) - .setEmoji("🔗") - .setURL(`https://reddit.com${content.permalink}`) - ); - - const embed = new EmbedBuilder() - .setTitle(`😆︱Meme`) - .setDescription(`**${content.title}**`) - .setTimestamp(new Date()) - .setImage(content.url) - .setFooter({ - text: `👍 ${content.ups}︱👎 ${content.downs}`, - }) - .setColor(embedConfig.successColor); - - await interaction.editReply({ embeds: [embed], components: [buttons] }); - return; - }) - .catch((error) => { - throw new Error(error.message); - }); - }, -}; diff --git a/src/commands/fun/subcommands/meme/index.ts b/src/commands/fun/subcommands/meme/index.ts new file mode 100644 index 0000000..394d2c9 --- /dev/null +++ b/src/commands/fun/subcommands/meme/index.ts @@ -0,0 +1,59 @@ +import axios from "axios"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + CommandInteraction, + EmbedBuilder, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import cooldown from "../../../../middlewares/cooldown"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command.setName("meme").setDescription("Random memes from r/memes"); +}; + +export const execute = async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + + const { guild, user, commandId } = interaction; + if (!guild) throw new Error("Server unavailable"); + if (!user) throw new Error("User unavailable"); + + await cooldown(guild, user, commandId, 15); + + const embedConfig = await getEmbedConfig(guild); + + await axios + .get("https://www.reddit.com/r/memes/random/.json") + .then(async (res) => { + const response = res.data[0].data.children; + const content = response[0].data; + + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("View post") + .setStyle(ButtonStyle.Link) + .setEmoji("🔗") + .setURL(`https://reddit.com${content.permalink}`) + ); + + const embed = new EmbedBuilder() + .setTitle(`😆︱Meme`) + .setDescription(`**${content.title}**`) + .setTimestamp(new Date()) + .setImage(content.url) + .setFooter({ + text: `👍 ${content.ups}︱👎 ${content.downs}`, + }) + .setColor(embedConfig.successColor); + + await interaction.editReply({ embeds: [embed], components: [buttons] }); + return; + }) + .catch((error) => { + throw new Error(error.message); + }); +}; diff --git a/src/commands/manage/groups/counters/index.ts b/src/commands/manage/groups/counters/index.ts new file mode 100644 index 0000000..ef3f236 --- /dev/null +++ b/src/commands/manage/groups/counters/index.ts @@ -0,0 +1,41 @@ +// Dependencies +import { + ChatInputCommandInteraction, + SlashCommandSubcommandGroupBuilder, +} from "discord.js"; + +// Modules +import { + builder as AddBuilder, + execute as AddExecute, +} from "./subcommands/add"; +import { + builder as RemoveBuilder, + execute as RemoveExecute, +} from "./subcommands/remove"; + +export const builder = (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("counters") + .setDescription("Manage guild counters.") + .addSubcommand(AddBuilder) + .addSubcommand(RemoveBuilder); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + const { options } = interaction; + + switch (options.getSubcommand()) { + case "add": { + await AddExecute(interaction); + break; + } + case "remove": { + await RemoveExecute(interaction); + break; + } + default: { + throw new Error("Could not found a module for that command."); + } + } +}; diff --git a/src/commands/manage/groups/counters/subcommands/add/index.ts b/src/commands/manage/groups/counters/subcommands/add/index.ts new file mode 100644 index 0000000..6915c3f --- /dev/null +++ b/src/commands/manage/groups/counters/subcommands/add/index.ts @@ -0,0 +1,112 @@ +// Dependencies +import { + ChannelType, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +// Configurations +import prisma from "../../../../../../handlers/database"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; +import logger from "../../../../../../middlewares/logger"; + +// Function +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("add") + .setDescription("Add a counter to your guild.") + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The channel to send the counter to.") + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ) + .addStringOption((option) => + option + .setName("word") + .setDescription("The word to use for the counter.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("start") + .setDescription("The starting value of the counter.") + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const discordChannel = options?.getChannel("channel"); + const triggerWord = options?.getString("word"); + const startValue = options?.getNumber("start"); + + if (!guild) throw new Error("We could not find a guild"); + if (!discordChannel) throw new Error("We could not find a channel"); + if (!triggerWord) throw new Error("We could not find a word"); + + const channelCounter = await prisma.guildCounter.findUnique({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }, + }); + + if (channelCounter) + throw new Error("A counter already exists for this channel."); + + const createGuildCounter = await prisma.guildCounter.upsert({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }, + update: {}, + create: { + channelId: discordChannel.id, + triggerWord, + count: startValue || 0, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + }); + + logger.silly(createGuildCounter); + + if (createGuildCounter) { + const embed = new EmbedBuilder() + .setTitle("[:toolbox:] Counters - Add") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction?.editReply({ + embeds: [ + embed + .setDescription(":white_check_mark: Counter created successfully.") + .setColor(successColor), + ], + }); + } +}; diff --git a/src/commands/manage/groups/counters/subcommands/remove/index.ts b/src/commands/manage/groups/counters/subcommands/remove/index.ts new file mode 100644 index 0000000..ce83c43 --- /dev/null +++ b/src/commands/manage/groups/counters/subcommands/remove/index.ts @@ -0,0 +1,81 @@ +// Dependencies +// Models +import { + ChannelType, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +// Configurations +import prisma from "../../../../../../handlers/database"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; + +// Function +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("remove") + .setDescription(`Delete a counter from your guild.`) + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The channel to delete the counter from.") + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const discordChannel = options?.getChannel("channel"); + + if (!guild) throw new Error("We could not find a guild"); + if (!discordChannel) throw new Error("We could not find a channel"); + + const embed = new EmbedBuilder() + .setTitle("[:toolbox:] Counters - Remove") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + const channelCounter = await prisma.guildCounter.findUnique({ + where: { + guildId_channelId: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }, + }); + + if (!channelCounter) + throw new Error( + "There is no counter sin this channel, please add one first." + ); + + const deleteGuildCounter = await prisma.guildCounter.deleteMany({ + where: { + guildId: guild.id, + channelId: discordChannel.id, + }, + }); + + if (!deleteGuildCounter) + throw new Error("We could not find a counter for this guild"); + + await interaction?.editReply({ + embeds: [ + embed + .setDescription(":white_check_mark: Counter deleted successfully.") + .setColor(successColor), + ], + }); +}; diff --git a/src/commands/manage/groups/credits/index.ts b/src/commands/manage/groups/credits/index.ts new file mode 100644 index 0000000..c60bda6 --- /dev/null +++ b/src/commands/manage/groups/credits/index.ts @@ -0,0 +1,58 @@ +import { + ChatInputCommandInteraction, + SlashCommandSubcommandGroupBuilder, +} from "discord.js"; + +// Modules +import { + builder as GiveBuilder, + execute as GiveExecute, +} from "./subcommands/give"; +import { + builder as GiveawayBuilder, + execute as GiveawayExecute, +} from "./subcommands/giveaway"; +import { + builder as SetBuilder, + execute as SetExecute, +} from "./subcommands/set"; +import { + builder as TakeBuilder, + execute as TakeExecute, +} from "./subcommands/take"; +import { + builder as TransferBuilder, + execute as TransferExecute, +} from "./subcommands/transfer"; + +export const builder = (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("credits") + .setDescription("Manage the credits of a user.") + .addSubcommand(GiveBuilder) + .addSubcommand(SetBuilder) + .addSubcommand(TakeBuilder) + .addSubcommand(TransferBuilder) + .addSubcommand(GiveawayBuilder); +}; +export const execute = async (interaction: ChatInputCommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "give": + await GiveExecute(interaction); + break; + case "set": + await SetExecute(interaction); + break; + case "take": + await TakeExecute(interaction); + break; + case "transfer": + await TransferExecute(interaction); + break; + case "giveaway": + await GiveawayExecute(interaction); + break; + default: + throw new Error("No module found for that specific command"); + } +}; diff --git a/src/commands/manage/groups/credits/subcommands/give/index.ts b/src/commands/manage/groups/credits/subcommands/give/index.ts new file mode 100644 index 0000000..5596e5b --- /dev/null +++ b/src/commands/manage/groups/credits/subcommands/give/index.ts @@ -0,0 +1,70 @@ +// Dependencies +import { + ChatInputCommandInteraction, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +// Configurations +// Helpers../../../../../../../helpers/userData +import pluralize from "../../../../../../helpers/pluralize"; +// Models +// Handlers +import deferReply from "../../../../../../handlers/deferReply"; +import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import creditsGive from "../../../../../../helpers/credits/give"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("give") + .setDescription("Give credits to a user.") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user to give credits to.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription(`The amount of credits to give.`) + .setRequired(true) + ); +}; + +export const 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); + + // 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."); + + // 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 (!discordReceiver) + throw new Error("We could not get the receiving user from Discord"); + + // 5. Create base embeds. + const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Give"); + + // 6. Give the credits. + await creditsGive(guild, discordReceiver, creditsAmount); + + // 7. Send embed. + return await interaction.editReply({ + embeds: [ + embedSuccess.setDescription( + `Successfully gave ${pluralize(creditsAmount, "credit")}` + ), + ], + }); +}; diff --git a/src/commands/manage/groups/credits/subcommands/giveaway/index.ts b/src/commands/manage/groups/credits/subcommands/giveaway/index.ts new file mode 100644 index 0000000..dff104a --- /dev/null +++ b/src/commands/manage/groups/credits/subcommands/giveaway/index.ts @@ -0,0 +1,188 @@ +// Dependencies +import axios from "axios"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ChannelType, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import { v4 as uuidv4 } from "uuid"; +import encryption from "../../../../../../helpers/encryption"; +// Configurations +import prisma from "../../../../../../handlers/database"; +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; +import logger from "../../../../../../middlewares/logger"; + +// Function +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("giveaway") + .setDescription("Giveaway some credits for specified amount of users.") + .addIntegerOption((option) => + option + .setName("uses") + .setDescription("How many users should be able to use this.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("credit") + .setDescription(`How much credits provided per use.`) + .setRequired(true) + ) + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The channel to send the message to.") + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure + const { guild, user, options } = interaction; + + const uses = options?.getInteger("uses"); + const creditAmount = options?.getInteger("credit"); + const channel = options?.getChannel("channel"); + + if (!uses) throw new Error("Amount of uses is required."); + if (!creditAmount) throw new Error("Amount of credits is required."); + if (!channel) throw new Error("Channel is required."); + if (!guild) throw new Error("Guild is required."); + + const embed = new EmbedBuilder() + .setTitle("[:toolbox:] Giveaway") + .setFooter({ text: footerText, iconURL: footerIcon }); + + const code = uuidv4(); + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + 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.guild.apiCpggUrlIv || + !createGuildMember.guild.apiCpggUrlContent + ) + throw new Error("No API url available"); + + if ( + !createGuildMember.guild.apiCpggTokenIv || + !createGuildMember.guild.apiCpggTokenContent + ) + throw new Error("No API token available"); + + const url = encryption.decrypt({ + iv: createGuildMember.guild.apiCpggUrlIv, + content: createGuildMember.guild.apiCpggUrlContent, + }); + const api = axios?.create({ + baseURL: `${url}/api/`, + headers: { + Authorization: `Bearer ${encryption.decrypt({ + iv: createGuildMember.guild.apiCpggTokenIv, + content: createGuildMember.guild.apiCpggTokenContent, + })}`, + }, + }); + + const shopUrl = `${url}/store`; + + await api + .post("vouchers", { + uses, + code, + credits: creditAmount, + memo: `[GIVEAWAY] ${interaction?.createdTimestamp} - ${interaction?.user?.id}`, + }) + .then(async () => { + await interaction.editReply({ + embeds: [ + embed + .setColor(successColor) + .setDescription(`Successfully created code: ${code}`), + ], + }); + + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Redeem it here") + .setStyle(ButtonStyle.Link) + .setEmoji("🏦") + .setURL(`${shopUrl}?voucher=${code}`) + ); + + const discordChannel = guild?.channels.cache.get(channel.id); + + if (!discordChannel) return; + + if (discordChannel.type !== ChannelType.GuildText) return; + + discordChannel.send({ + embeds: [ + new EmbedBuilder() + .setTitle("[:parachute:] Credits!") + .addFields([ + { + name: "💶 Credits", + value: `${creditAmount}`, + inline: true, + }, + ]) + .setDescription( + `${interaction.user} dropped a voucher for a maximum **${uses}** members!` + ) + .setColor(successColor), + ], + components: [buttons], + }); + }); +}; diff --git a/src/commands/manage/groups/credits/subcommands/set/index.ts b/src/commands/manage/groups/credits/subcommands/set/index.ts new file mode 100644 index 0000000..fc12cae --- /dev/null +++ b/src/commands/manage/groups/credits/subcommands/set/index.ts @@ -0,0 +1,65 @@ +// Dependencies +// Helpers +// Models +import { + ChatInputCommandInteraction, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; + +import deferReply from "../../../../../../handlers/deferReply"; +import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import creditsSet from "../../../../../../helpers/credits/set"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("set") + .setDescription("Set the amount of credits a user has.") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user to set the amount of credits for.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription(`The amount of credits to set.`) + .setRequired(true) + ); +}; + +export const 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); + + // 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"); + + // 5. Set the credits. + await creditsSet(guild, discordUser, creditAmount); + + // 6. Create base embeds. + const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Set"); + + // 7. Send embed. + return await interaction.editReply({ + embeds: [ + embedSuccess.setDescription( + `Set **${discordUser}**'s credits to **${creditAmount}**.` + ), + ], + }); +}; diff --git a/src/commands/manage/groups/credits/subcommands/take/index.ts b/src/commands/manage/groups/credits/subcommands/take/index.ts new file mode 100644 index 0000000..835176a --- /dev/null +++ b/src/commands/manage/groups/credits/subcommands/take/index.ts @@ -0,0 +1,65 @@ +// Dependencies +// Models +import { + ChatInputCommandInteraction, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; + +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 const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("take") + .setDescription("Take credits from a user.") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user to take credits from.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription(`The amount of credits to take.`) + .setRequired(true) + ); +}; + +export const 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); + + // 3. Destructure interaction object. + const { guild, options } = interaction; + if (!guild) throw new Error("Invalid guild."); + if (!options) throw new Error("Invalid options."); + + // 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."); + + // 5. Create base embeds. + const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Take"); + + // 6. Take the credits. + await creditsTake(guild, discordReceiver, optionAmount); + + // 7. Send embed. + return await interaction.editReply({ + embeds: [ + embedSuccess.setDescription( + `Took ${pluralize(optionAmount, "credit")} from ${discordReceiver}.` + ), + ], + }); +}; diff --git a/src/commands/manage/groups/credits/subcommands/transfer/index.ts b/src/commands/manage/groups/credits/subcommands/transfer/index.ts new file mode 100644 index 0000000..be8b1ef --- /dev/null +++ b/src/commands/manage/groups/credits/subcommands/transfer/index.ts @@ -0,0 +1,79 @@ +// Dependencies +// Models +import { + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import creditsTransfer from "../../../../../../helpers/credits/transfer"; +// Configurations +import deferReply from "../../../../../../handlers/deferReply"; +import checkPermission from "../../../../../../helpers/checkPermission"; +import getEmbedConfig from "../../../../../../helpers/getEmbedData"; + +// Function +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("transfer") + .setDescription("Transfer credits from one user to another.") + .addUserOption((option) => + option + .setName("from") + .setDescription("The user to transfer credits from.") + .setRequired(true) + ) + .addUserOption((option) => + option + .setName("to") + .setDescription("The user to transfer credits to.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription(`The amount of credits to transfer.`) + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure member + const { guild, options } = interaction; + + // Get options + const optionFromUser = options?.getUser("from"); + const optionToUser = options?.getUser("to"); + const optionAmount = options?.getInteger("amount"); + + if (optionAmount === null) throw new Error("Amount is not specified"); + + if (optionAmount <= 0) + throw new Error("You need to set amount above zero to transfer."); + + if (!guild) throw new Error(`We could not find this guild.`); + + if (!optionFromUser) + throw new Error("You must provide a user to transfer from."); + + if (!optionToUser) throw new Error("You must provide a user to transfer to."); + + await creditsTransfer(guild, optionFromUser, optionToUser, optionAmount); + + return interaction?.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription(`Transferred ${optionAmount} credits.`) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); +}; diff --git a/src/commands/manage/index.ts b/src/commands/manage/index.ts index d532b0a..231d66e 100644 --- a/src/commands/manage/index.ts +++ b/src/commands/manage/index.ts @@ -2,8 +2,14 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; // Modules -import moduleCounters from "./modules/counters"; -import moduleCredits from "./modules/credits"; +import { + builder as CountersBuilder, + execute as CountersExecute, +} from "./groups/counters"; +import { + builder as CreditsBuilder, + execute as CreditsExecute, +} from "./groups/credits"; // Function export const builder = new SlashCommandBuilder() @@ -12,8 +18,8 @@ export const builder = new SlashCommandBuilder() .setDMPermission(false) // Modules - .addSubcommandGroup(moduleCounters.builder) - .addSubcommandGroup(moduleCredits.builder); + .addSubcommandGroup(CountersBuilder) + .addSubcommandGroup(CreditsBuilder); export const execute = async (interaction: ChatInputCommandInteraction) => { // Destructure @@ -21,11 +27,11 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { switch (options.getSubcommandGroup()) { case "credits": { - await moduleCredits.execute(interaction); + await CreditsExecute(interaction); break; } case "counters": { - await moduleCounters.execute(interaction); + await CountersExecute(interaction); break; } default: { diff --git a/src/commands/manage/modules/counters/index.ts b/src/commands/manage/modules/counters/index.ts deleted file mode 100644 index 1074ad5..0000000 --- a/src/commands/manage/modules/counters/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Dependencies -import { - ChatInputCommandInteraction, - SlashCommandSubcommandGroupBuilder, -} from "discord.js"; - -// Modules -import moduleAdd from "./modules/add"; -import moduleRemove from "./modules/remove"; - -export default { - builder: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("counters") - .setDescription("Manage guild counters.") - .addSubcommand(moduleAdd.builder) - .addSubcommand(moduleRemove.builder); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - const { options } = interaction; - - switch (options.getSubcommand()) { - case "add": { - await moduleAdd.execute(interaction); - break; - } - case "remove": { - await moduleRemove.execute(interaction); - break; - } - default: { - throw new Error("Could not found a module for that command."); - } - } - }, -}; diff --git a/src/commands/manage/modules/counters/modules/add/index.ts b/src/commands/manage/modules/counters/modules/add/index.ts deleted file mode 100644 index 30e3d6a..0000000 --- a/src/commands/manage/modules/counters/modules/add/index.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Dependencies -import { - ChannelType, - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import deferReply from "../../../../../../handlers/deferReply"; -import checkPermission from "../../../../../../helpers/checkPermission"; -// Configurations -import prisma from "../../../../../../handlers/database"; -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; -import logger from "../../../../../../middlewares/logger"; - -// Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("add") - .setDescription("Add a counter to your guild.") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to send the counter to.") - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) - ) - .addStringOption((option) => - option - .setName("word") - .setDescription("The word to use for the counter.") - .setRequired(true) - ) - .addNumberOption((option) => - option - .setName("start") - .setDescription("The starting value of the counter.") - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const discordChannel = options?.getChannel("channel"); - const triggerWord = options?.getString("word"); - const startValue = options?.getNumber("start"); - - if (!guild) throw new Error("We could not find a guild"); - if (!discordChannel) throw new Error("We could not find a channel"); - if (!triggerWord) throw new Error("We could not find a word"); - - const channelCounter = await prisma.guildCounter.findUnique({ - where: { - guildId_channelId: { - guildId: guild.id, - channelId: discordChannel.id, - }, - }, - }); - - if (channelCounter) - throw new Error("A counter already exists for this channel."); - - const createGuildCounter = await prisma.guildCounter.upsert({ - where: { - guildId_channelId: { - guildId: guild.id, - channelId: discordChannel.id, - }, - }, - update: {}, - create: { - channelId: discordChannel.id, - triggerWord, - count: startValue || 0, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - }, - }); - - logger.silly(createGuildCounter); - - if (createGuildCounter) { - const embed = new EmbedBuilder() - .setTitle("[:toolbox:] Counters - Add") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - await interaction?.editReply({ - embeds: [ - embed - .setDescription(":white_check_mark: Counter created successfully.") - .setColor(successColor), - ], - }); - } - }, -}; diff --git a/src/commands/manage/modules/counters/modules/remove/index.ts b/src/commands/manage/modules/counters/modules/remove/index.ts deleted file mode 100644 index 8b542e6..0000000 --- a/src/commands/manage/modules/counters/modules/remove/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Dependencies -// Models -import { - ChannelType, - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import deferReply from "../../../../../../handlers/deferReply"; -import checkPermission from "../../../../../../helpers/checkPermission"; -// Configurations -import prisma from "../../../../../../handlers/database"; -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; - -// Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("remove") - .setDescription(`Delete a counter from your guild.`) - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to delete the counter from.") - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const discordChannel = options?.getChannel("channel"); - - if (!guild) throw new Error("We could not find a guild"); - if (!discordChannel) throw new Error("We could not find a channel"); - - const embed = new EmbedBuilder() - .setTitle("[:toolbox:] Counters - Remove") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - const channelCounter = await prisma.guildCounter.findUnique({ - where: { - guildId_channelId: { - guildId: guild.id, - channelId: discordChannel.id, - }, - }, - }); - - if (!channelCounter) - throw new Error( - "There is no counter sin this channel, please add one first." - ); - - const deleteGuildCounter = await prisma.guildCounter.deleteMany({ - where: { - guildId: guild.id, - channelId: discordChannel.id, - }, - }); - - if (!deleteGuildCounter) - throw new Error("We could not find a counter for this guild"); - - await interaction?.editReply({ - embeds: [ - embed - .setDescription(":white_check_mark: Counter deleted successfully.") - .setColor(successColor), - ], - }); - }, -}; diff --git a/src/commands/manage/modules/credits/index.ts b/src/commands/manage/modules/credits/index.ts deleted file mode 100644 index 4089408..0000000 --- a/src/commands/manage/modules/credits/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - ChatInputCommandInteraction, - SlashCommandSubcommandGroupBuilder, -} from "discord.js"; - -// Modules -import moduleGive from "./modules/give"; -import moduleGiveaway from "./modules/giveaway"; -import moduleSet from "./modules/set"; -import moduleTake from "./modules/take"; -import moduleTransfer from "./modules/transfer"; - -export default { - builder: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("credits") - .setDescription("Manage the credits of a user.") - .addSubcommand(moduleGive.builder) - .addSubcommand(moduleSet.builder) - .addSubcommand(moduleTake.builder) - .addSubcommand(moduleTransfer.builder) - .addSubcommand(moduleGiveaway.builder); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - switch (interaction.options.getSubcommand()) { - case "give": - await moduleGive.execute(interaction); - break; - case "set": - await moduleSet.execute(interaction); - break; - case "take": - await moduleTake.execute(interaction); - break; - case "transfer": - await moduleTransfer.execute(interaction); - break; - case "giveaway": - await moduleGiveaway.execute(interaction); - break; - default: - throw new Error("No module found for that specific command"); - } - }, -}; diff --git a/src/commands/manage/modules/credits/modules/give/index.ts b/src/commands/manage/modules/credits/modules/give/index.ts deleted file mode 100644 index 61ad2ef..0000000 --- a/src/commands/manage/modules/credits/modules/give/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Dependencies -import { - ChatInputCommandInteraction, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -// Configurations -// Helpers../../../../../../../helpers/userData -import pluralize from "../../../../../../helpers/pluralize"; -// Models -// Handlers -import deferReply from "../../../../../../handlers/deferReply"; -import { success as baseEmbedSuccess } from "../../../../../../helpers/baseEmbeds"; -import checkPermission from "../../../../../../helpers/checkPermission"; -import creditsGive from "../../../../../../helpers/credits/give"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("give") - .setDescription("Give credits to a user.") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user to give credits to.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription(`The amount of credits to give.`) - .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); - - // 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."); - - // 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 (!discordReceiver) - throw new Error("We could not get the receiving user from Discord"); - - // 5. Create base embeds. - const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Give"); - - // 6. Give the credits. - await creditsGive(guild, discordReceiver, creditsAmount); - - // 7. Send embed. - return await interaction.editReply({ - embeds: [ - embedSuccess.setDescription( - `Successfully gave ${pluralize(creditsAmount, "credit")}` - ), - ], - }); - }, -}; diff --git a/src/commands/manage/modules/credits/modules/giveaway/index.ts b/src/commands/manage/modules/credits/modules/giveaway/index.ts deleted file mode 100644 index 15a5c83..0000000 --- a/src/commands/manage/modules/credits/modules/giveaway/index.ts +++ /dev/null @@ -1,189 +0,0 @@ -// Dependencies -import axios from "axios"; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ChannelType, - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import { v4 as uuidv4 } from "uuid"; -import encryption from "../../../../../../helpers/encryption"; -// Configurations -import prisma from "../../../../../../handlers/database"; -import deferReply from "../../../../../../handlers/deferReply"; -import checkPermission from "../../../../../../helpers/checkPermission"; -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; -import logger from "../../../../../../middlewares/logger"; - -// Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("giveaway") - .setDescription("Giveaway some credits for specified amount of users.") - .addIntegerOption((option) => - option - .setName("uses") - .setDescription("How many users should be able to use this.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("credit") - .setDescription(`How much credits provided per use.`) - .setRequired(true) - ) - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to send the message to.") - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure - const { guild, user, options } = interaction; - - const uses = options?.getInteger("uses"); - const creditAmount = options?.getInteger("credit"); - const channel = options?.getChannel("channel"); - - if (!uses) throw new Error("Amount of uses is required."); - if (!creditAmount) throw new Error("Amount of credits is required."); - if (!channel) throw new Error("Channel is required."); - if (!guild) throw new Error("Guild is required."); - - const embed = new EmbedBuilder() - .setTitle("[:toolbox:] Giveaway") - .setFooter({ text: footerText, iconURL: footerIcon }); - - const code = uuidv4(); - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: user.id, - guildId: guild.id, - }, - }, - update: {}, - create: { - 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.guild.apiCpggUrlIv || - !createGuildMember.guild.apiCpggUrlContent - ) - throw new Error("No API url available"); - - if ( - !createGuildMember.guild.apiCpggTokenIv || - !createGuildMember.guild.apiCpggTokenContent - ) - throw new Error("No API token available"); - - const url = encryption.decrypt({ - iv: createGuildMember.guild.apiCpggUrlIv, - content: createGuildMember.guild.apiCpggUrlContent, - }); - const api = axios?.create({ - baseURL: `${url}/api/`, - headers: { - Authorization: `Bearer ${encryption.decrypt({ - iv: createGuildMember.guild.apiCpggTokenIv, - content: createGuildMember.guild.apiCpggTokenContent, - })}`, - }, - }); - - const shopUrl = `${url}/store`; - - await api - .post("vouchers", { - uses, - code, - credits: creditAmount, - memo: `[GIVEAWAY] ${interaction?.createdTimestamp} - ${interaction?.user?.id}`, - }) - .then(async () => { - await interaction.editReply({ - embeds: [ - embed - .setColor(successColor) - .setDescription(`Successfully created code: ${code}`), - ], - }); - - const buttons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("Redeem it here") - .setStyle(ButtonStyle.Link) - .setEmoji("🏦") - .setURL(`${shopUrl}?voucher=${code}`) - ); - - const discordChannel = guild?.channels.cache.get(channel.id); - - if (!discordChannel) return; - - if (discordChannel.type !== ChannelType.GuildText) return; - - discordChannel.send({ - embeds: [ - new EmbedBuilder() - .setTitle("[:parachute:] Credits!") - .addFields([ - { - name: "💶 Credits", - value: `${creditAmount}`, - inline: true, - }, - ]) - .setDescription( - `${interaction.user} dropped a voucher for a maximum **${uses}** members!` - ) - .setColor(successColor), - ], - components: [buttons], - }); - }); - }, -}; diff --git a/src/commands/manage/modules/credits/modules/set/index.ts b/src/commands/manage/modules/credits/modules/set/index.ts deleted file mode 100644 index a69509b..0000000 --- a/src/commands/manage/modules/credits/modules/set/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Dependencies -// Helpers -// Models -import { - ChatInputCommandInteraction, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; - -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 - .setName("set") - .setDescription("Set the amount of credits a user has.") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user to set the amount of credits for.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription(`The amount of credits to set.`) - .setRequired(true) - ); - }, - 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); - - // 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"); - - // 5. Set the credits. - await creditsSet(guild, discordUser, creditAmount); - - // 6. Create base embeds. - const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Set"); - - // 7. Send embed. - return await interaction.editReply({ - embeds: [ - 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 deleted file mode 100644 index 9ea9eba..0000000 --- a/src/commands/manage/modules/credits/modules/take/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Dependencies -// Models -import { - ChatInputCommandInteraction, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; - -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 - .setName("take") - .setDescription("Take credits from a user.") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user to take credits from.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription(`The amount of credits to take.`) - .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); - - // 3. Destructure interaction object. - const { guild, options } = interaction; - if (!guild) throw new Error("Invalid guild."); - if (!options) throw new Error("Invalid options."); - - // 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."); - - // 5. Create base embeds. - const embedSuccess = await baseEmbedSuccess(guild, "[:toolbox:] Take"); - - // 6. Take the credits. - await creditsTake(guild, discordReceiver, optionAmount); - - // 7. Send embed. - return await interaction.editReply({ - embeds: [ - embedSuccess.setDescription( - `Took ${pluralize(optionAmount, "credit")} from ${discordReceiver}.` - ), - ], - }); - }, -}; diff --git a/src/commands/manage/modules/credits/modules/transfer/index.ts b/src/commands/manage/modules/credits/modules/transfer/index.ts deleted file mode 100644 index c140f52..0000000 --- a/src/commands/manage/modules/credits/modules/transfer/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Dependencies -// Models -import { - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import creditsTransfer from "../../../../../../helpers/credits/transfer"; -// Configurations -import deferReply from "../../../../../../handlers/deferReply"; -import checkPermission from "../../../../../../helpers/checkPermission"; -import getEmbedConfig from "../../../../../../helpers/getEmbedData"; - -// Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("transfer") - .setDescription("Transfer credits from one user to another.") - .addUserOption((option) => - option - .setName("from") - .setDescription("The user to transfer credits from.") - .setRequired(true) - ) - .addUserOption((option) => - option - .setName("to") - .setDescription("The user to transfer credits to.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription(`The amount of credits to transfer.`) - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - checkPermission(interaction, PermissionsBitField.Flags.ManageGuild); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure member - const { guild, options } = interaction; - - // Get options - const optionFromUser = options?.getUser("from"); - const optionToUser = options?.getUser("to"); - const optionAmount = options?.getInteger("amount"); - - if (optionAmount === null) throw new Error("Amount is not specified"); - - if (optionAmount <= 0) - throw new Error("You need to set amount above zero to transfer."); - - if (!guild) throw new Error(`We could not find this guild.`); - - if (!optionFromUser) - throw new Error("You must provide a user to transfer from."); - - if (!optionToUser) - throw new Error("You must provide a user to transfer to."); - - await creditsTransfer(guild, optionFromUser, optionToUser, optionAmount); - - return interaction?.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription(`Transferred ${optionAmount} credits.`) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - }, -}; diff --git a/src/commands/moderation/index.ts b/src/commands/moderation/index.ts index 263f900..88096c4 100644 --- a/src/commands/moderation/index.ts +++ b/src/commands/moderation/index.ts @@ -1,20 +1,23 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; // Modules -import modulePrune from "./modules/prune"; +import { + builder as PruneBuilder, + execute as PruneExecute, +} from "./subcommands/prune"; export const builder = new SlashCommandBuilder() .setName("moderation") .setDescription("Moderation.") .setDMPermission(false) - .addSubcommand(modulePrune.builder); + .addSubcommand(PruneBuilder); // Execute the command export const execute = async (interaction: ChatInputCommandInteraction) => { switch (interaction.options.getSubcommand()) { case "prune": { - await modulePrune.execute(interaction); + await PruneExecute(interaction); break; } default: { diff --git a/src/commands/moderation/modules/prune/index.ts b/src/commands/moderation/modules/prune/index.ts deleted file mode 100644 index e244a22..0000000 --- a/src/commands/moderation/modules/prune/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Dependencies -import { - ChannelType, - ChatInputCommandInteraction, - EmbedBuilder, - PermissionsBitField, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import deferReply from "../../../../handlers/deferReply"; -import checkPermission from "../../../../helpers/checkPermission"; -// Configurations -import getEmbedConfig from "../../../../helpers/getEmbedData"; - -// Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("prune") - .setDescription("Prune messages!") - .addIntegerOption((option) => - option - .setName("count") - .setDescription("How many messages you want to prune.") - .setRequired(true) - ) - .addBooleanOption((option) => - option.setName("bots").setDescription("Include bots.") - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, false); - - checkPermission(interaction, PermissionsBitField.Flags.ManageMessages); - - const { errorColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - const count = interaction.options.getInteger("count"); - if (count === null) return; - const bots = interaction.options.getBoolean("bots"); - - if (count < 1 || count > 100) { - const interactionEmbed = new EmbedBuilder() - .setTitle("[:police_car:] Prune") - .setDescription(`You can only prune between 1 and 100 messages.`) - .setTimestamp() - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }); - - await interaction.editReply({ - embeds: [interactionEmbed], - }); - return; - } - - if (interaction?.channel?.type !== ChannelType.GuildText) return; - await interaction.channel.messages.fetch().then(async (messages) => { - const messagesToDelete = ( - bots - ? messages.filter((m) => m?.interaction?.id !== interaction.id) - : messages.filter( - (m) => - m?.interaction?.id !== interaction.id && m?.author?.bot !== true - ) - ).first(count); - - if (interaction?.channel?.type !== ChannelType.GuildText) return; - await interaction.channel - .bulkDelete(messagesToDelete, true) - .then(async () => { - const interactionEmbed = new EmbedBuilder() - .setTitle("[:police_car:] Prune") - .setDescription(`Successfully pruned \`${count}\` messages.`) - .setTimestamp() - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }); - - await interaction.editReply({ - embeds: [interactionEmbed], - }); - }); - }); - }, -}; diff --git a/src/commands/moderation/subcommands/prune/index.ts b/src/commands/moderation/subcommands/prune/index.ts new file mode 100644 index 0000000..59d47d1 --- /dev/null +++ b/src/commands/moderation/subcommands/prune/index.ts @@ -0,0 +1,84 @@ +// Dependencies +import { + ChannelType, + ChatInputCommandInteraction, + EmbedBuilder, + PermissionsBitField, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import deferReply from "../../../../handlers/deferReply"; +import checkPermission from "../../../../helpers/checkPermission"; +// Configurations +import getEmbedConfig from "../../../../helpers/getEmbedData"; + +// Function +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("prune") + .setDescription("Prune messages!") + .addIntegerOption((option) => + option + .setName("count") + .setDescription("How many messages you want to prune.") + .setRequired(true) + ) + .addBooleanOption((option) => + option.setName("bots").setDescription("Include bots.") + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, false); + + checkPermission(interaction, PermissionsBitField.Flags.ManageMessages); + + const { errorColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const count = interaction.options.getInteger("count"); + if (count === null) return; + const bots = interaction.options.getBoolean("bots"); + + if (count < 1 || count > 100) { + const interactionEmbed = new EmbedBuilder() + .setTitle("[:police_car:] Prune") + .setDescription(`You can only prune between 1 and 100 messages.`) + .setTimestamp() + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction.editReply({ + embeds: [interactionEmbed], + }); + return; + } + + if (interaction?.channel?.type !== ChannelType.GuildText) return; + await interaction.channel.messages.fetch().then(async (messages) => { + const messagesToDelete = ( + bots + ? messages.filter((m) => m?.interaction?.id !== interaction.id) + : messages.filter( + (m) => + m?.interaction?.id !== interaction.id && m?.author?.bot !== true + ) + ).first(count); + + if (interaction?.channel?.type !== ChannelType.GuildText) return; + await interaction.channel + .bulkDelete(messagesToDelete, true) + .then(async () => { + const interactionEmbed = new EmbedBuilder() + .setTitle("[:police_car:] Prune") + .setDescription(`Successfully pruned \`${count}\` messages.`) + .setTimestamp() + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction.editReply({ + embeds: [interactionEmbed], + }); + }); + }); +}; diff --git a/src/commands/reputation/index.ts b/src/commands/reputation/index.ts index 25291e1..c985f06 100644 --- a/src/commands/reputation/index.ts +++ b/src/commands/reputation/index.ts @@ -2,8 +2,14 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; // Modules -import moduleCheck from "./modules/check"; -import moduleRepute from "./modules/repute"; +import { + builder as CheckBuilder, + execute as CheckExecute, +} from "./subcommands/check"; +import { + builder as ReputeBuilder, + execute as ReputeExecute, +} from "./subcommands/repute"; // Function export const builder = new SlashCommandBuilder() @@ -12,17 +18,17 @@ export const builder = new SlashCommandBuilder() .setDMPermission(false) // Modules - .addSubcommand(moduleRepute.builder) - .addSubcommand(moduleCheck.builder); + .addSubcommand(ReputeBuilder) + .addSubcommand(CheckBuilder); // Execute function export const execute = async (interaction: ChatInputCommandInteraction) => { if (interaction.options.getSubcommand() === "repute") { - await moduleRepute.execute(interaction); + await ReputeExecute(interaction); return; } if (interaction.options.getSubcommand() === "check") { - await moduleCheck.execute(interaction); + await CheckExecute(interaction); return; } }; diff --git a/src/commands/reputation/modules/check/index.ts b/src/commands/reputation/modules/check/index.ts deleted file mode 100644 index 1e6008f..0000000 --- a/src/commands/reputation/modules/check/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - ChatInputCommandInteraction, - EmbedBuilder, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import prisma from "../../../../handlers/database"; -import deferReply from "../../../../handlers/deferReply"; -import getEmbedConfig from "../../../../helpers/getEmbedData"; -import logger from "../../../../middlewares/logger"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("check") - .setDescription("Check reputation") - .addUserOption((option) => - option - .setName("account") - .setDescription("The account you checking") - .setRequired(false) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - const { options, guild, user } = interaction; - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - guild - ); - - const optionAccount = options?.getUser("account"); - - if (!guild) throw new Error("Server unavailable"); - if (!user) throw new Error("User unavailable"); - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: (optionAccount || user).id, - guildId: guild.id, - }, - }, - update: {}, - create: { - user: { - connectOrCreate: { - create: { - id: (optionAccount || user).id, - }, - where: { - id: (optionAccount || user).id, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guild.id, - }, - where: { - id: guild.id, - }, - }, - }, - }, - include: { - user: true, - guild: true, - }, - }); - - logger.silly(createGuildMember); - - const reputationType = (reputation: number) => { - if (reputation < 0) return `negative reputation of ${reputation}`; - if (reputation > 0) return `positive reputation of ${reputation}`; - return "neutral reputation"; - }; - - const interactionEmbed = new EmbedBuilder() - .setTitle( - optionAccount - ? `:loudspeaker:︱Showing ${optionAccount.username}'s reputation` - : ":loudspeaker:︱Showing your reputation" - ) - .setDescription( - optionAccount - ? `${optionAccount} have a ${reputationType( - createGuildMember.user.reputationsEarned - )}` - : `You have a ${reputationType( - createGuildMember.user.reputationsEarned - )}` - ) - .setTimestamp() - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }); - - await interaction.editReply({ - embeds: [interactionEmbed], - }); - }, -}; diff --git a/src/commands/reputation/modules/repute/index.ts b/src/commands/reputation/modules/repute/index.ts deleted file mode 100644 index 42dd7d9..0000000 --- a/src/commands/reputation/modules/repute/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { - ChatInputCommandInteraction, - EmbedBuilder, - SlashCommandSubcommandBuilder, -} from "discord.js"; -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) => { - return command - .setName("repute") - .setDescription("Repute an account") - .addUserOption((option) => - option - .setName("account") - .setDescription("The account you repute") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("type") - .setDescription("Type of reputation") - .setRequired(true) - .addChoices( - { name: "Positive", value: "positive" }, - { - name: "Negative", - value: "negative", - } - ) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - const { options, user, guild, commandId } = interaction; - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - guild - ); - - const optionAccount = options?.getUser("account"); - const optionType = options?.getString("type"); - - if (!guild) throw new Error("Server unavailable"); - if (!optionAccount) throw new Error("User unavailable"); - - // Pre-checks - noSelfReputation(optionAccount, user); - - // Check if user is on cooldown otherwise create one - await cooldown( - guild, - user, - commandId, - parseInt(process.env.REPUTATION_TIMEOUT) - ); - - switch (optionType) { - case "positive": { - const createUser = await prisma.user.upsert({ - where: { - id: optionAccount.id, - }, - update: { - reputationsEarned: { - increment: 1, - }, - }, - create: { - id: optionAccount.id, - reputationsEarned: 1, - }, - }); - - logger.silly(createUser); - break; - } - case "negative": { - const createUser = await prisma.user.upsert({ - where: { - id: optionAccount.id, - }, - update: { - reputationsEarned: { - decrement: 1, - }, - }, - create: { - id: optionAccount.id, - reputationsEarned: -1, - }, - }); - - logger.silly(createUser); - break; - } - default: { - throw new Error("Invalid reputation type"); - } - } - - const interactionEmbed = new EmbedBuilder() - .setTitle(`:loudspeaker:︱Reputing ${optionAccount.username}`) - .setDescription( - `You have given a ${optionType} repute to ${optionAccount}!` - ) - .setTimestamp() - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }); - - await interaction.editReply({ - embeds: [interactionEmbed], - }); - }, -}; diff --git a/src/commands/reputation/subcommands/check/index.ts b/src/commands/reputation/subcommands/check/index.ts new file mode 100644 index 0000000..e628634 --- /dev/null +++ b/src/commands/reputation/subcommands/check/index.ts @@ -0,0 +1,101 @@ +import { + ChatInputCommandInteraction, + EmbedBuilder, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("check") + .setDescription("Check reputation") + .addUserOption((option) => + option + .setName("account") + .setDescription("The account you checking") + .setRequired(false) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { options, guild, user } = interaction; + + const { successColor, footerText, footerIcon } = await getEmbedConfig(guild); + + const optionAccount = options?.getUser("account"); + + if (!guild) throw new Error("Server unavailable"); + if (!user) throw new Error("User unavailable"); + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: (optionAccount || user).id, + guildId: guild.id, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: (optionAccount || user).id, + }, + where: { + id: (optionAccount || user).id, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guild.id, + }, + where: { + id: guild.id, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + const reputationType = (reputation: number) => { + if (reputation < 0) return `negative reputation of ${reputation}`; + if (reputation > 0) return `positive reputation of ${reputation}`; + return "neutral reputation"; + }; + + const interactionEmbed = new EmbedBuilder() + .setTitle( + optionAccount + ? `:loudspeaker:︱Showing ${optionAccount.username}'s reputation` + : ":loudspeaker:︱Showing your reputation" + ) + .setDescription( + optionAccount + ? `${optionAccount} have a ${reputationType( + createGuildMember.user.reputationsEarned + )}` + : `You have a ${reputationType( + createGuildMember.user.reputationsEarned + )}` + ) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction.editReply({ + embeds: [interactionEmbed], + }); +}; diff --git a/src/commands/reputation/modules/repute/components/noSelfReputation.ts b/src/commands/reputation/subcommands/repute/components/noSelfReputation.ts similarity index 100% rename from src/commands/reputation/modules/repute/components/noSelfReputation.ts rename to src/commands/reputation/subcommands/repute/components/noSelfReputation.ts diff --git a/src/commands/reputation/subcommands/repute/index.ts b/src/commands/reputation/subcommands/repute/index.ts new file mode 100644 index 0000000..b7d78ba --- /dev/null +++ b/src/commands/reputation/subcommands/repute/index.ts @@ -0,0 +1,119 @@ +import { + ChatInputCommandInteraction, + EmbedBuilder, + SlashCommandSubcommandBuilder, +} from "discord.js"; +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 const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("repute") + .setDescription("Repute an account") + .addUserOption((option) => + option + .setName("account") + .setDescription("The account you repute") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("type") + .setDescription("Type of reputation") + .setRequired(true) + .addChoices( + { name: "Positive", value: "positive" }, + { + name: "Negative", + value: "negative", + } + ) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { options, user, guild, commandId } = interaction; + + const { successColor, footerText, footerIcon } = await getEmbedConfig(guild); + + const optionAccount = options?.getUser("account"); + const optionType = options?.getString("type"); + + if (!guild) throw new Error("Server unavailable"); + if (!optionAccount) throw new Error("User unavailable"); + + // Pre-checks + noSelfReputation(optionAccount, user); + + // Check if user is on cooldown otherwise create one + await cooldown( + guild, + user, + commandId, + parseInt(process.env.REPUTATION_TIMEOUT) + ); + + switch (optionType) { + case "positive": { + const createUser = await prisma.user.upsert({ + where: { + id: optionAccount.id, + }, + update: { + reputationsEarned: { + increment: 1, + }, + }, + create: { + id: optionAccount.id, + reputationsEarned: 1, + }, + }); + + logger.silly(createUser); + break; + } + case "negative": { + const createUser = await prisma.user.upsert({ + where: { + id: optionAccount.id, + }, + update: { + reputationsEarned: { + decrement: 1, + }, + }, + create: { + id: optionAccount.id, + reputationsEarned: -1, + }, + }); + + logger.silly(createUser); + break; + } + default: { + throw new Error("Invalid reputation type"); + } + } + + const interactionEmbed = new EmbedBuilder() + .setTitle(`:loudspeaker:︱Reputing ${optionAccount.username}`) + .setDescription( + `You have given a ${optionType} repute to ${optionAccount}!` + ) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction.editReply({ + embeds: [interactionEmbed], + }); +}; diff --git a/src/commands/shop/groups/roles/index.ts b/src/commands/shop/groups/roles/index.ts new file mode 100644 index 0000000..7e43e0c --- /dev/null +++ b/src/commands/shop/groups/roles/index.ts @@ -0,0 +1,52 @@ +// Dependencies +import { + ChatInputCommandInteraction, + SlashCommandSubcommandGroupBuilder, +} from "discord.js"; + +// Handlers + +// Modules +import { + builder as BuyBuilder, + execute as BuyExecute, +} from "./subcommands/buy"; +import { + builder as CancelBuilder, + execute as CancelExecute, +} from "./subcommands/cancel"; + +import prisma from "../../../../handlers/database"; + +export const builder = (group: SlashCommandSubcommandGroupBuilder) => { + return ( + group + .setName("roles") + .setDescription("Shop for custom roles.") + + // Modules + .addSubcommand(BuyBuilder) + .addSubcommand(CancelBuilder) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + if (!interaction.guild) return; + const { options, guild } = interaction; + + const getGuild = await prisma.guild.findUnique({ + where: { id: guild.id }, + }); + if (!getGuild) throw new Error("Guild not found"); + + if (!getGuild.shopRolesEnabled) + throw new Error("This server has disabled shop roles."); + + if (options?.getSubcommand() === "buy") { + await BuyExecute(interaction); + } + + if (options?.getSubcommand() === "cancel") { + await CancelExecute(interaction); + } +}; diff --git a/src/commands/shop/groups/roles/subcommands/buy/index.ts b/src/commands/shop/groups/roles/subcommands/buy/index.ts new file mode 100644 index 0000000..5a59720 --- /dev/null +++ b/src/commands/shop/groups/roles/subcommands/buy/index.ts @@ -0,0 +1,177 @@ +// Dependencies +// Helpers +import { + ChatInputCommandInteraction, + ColorResolvable, + EmbedBuilder, + GuildMemberRoleManager, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import deferReply from "../../../../../../handlers/deferReply"; +import getEmbedData from "../../../../../../helpers/getEmbedData"; +import logger from "../../../../../../middlewares/logger"; +// Configurations +// import fetchUser from "../../../../../../helpers/userData"; +// Models + +import prisma from "../../../../../../handlers/database"; +import pluralize from "../../../../../../helpers/pluralize"; + +// Function +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("buy") + .setDescription("Buy a custom role.") + .addStringOption((option) => + option + .setName("name") + .setDescription("Name of the role you wish to buy.") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("color") + .setDescription("Color of the role you wish to buy.") + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { successColor, footerText, footerIcon } = await getEmbedData( + interaction.guild + ); + const { options, guild, user, member } = interaction; + const optionName = options?.getString("name"); + const optionColor = options?.getString("color"); + // If amount is null + if (optionName === null) + throw new Error("We could not read your requested name"); + await guild?.roles + .create({ + name: optionName, + color: optionColor as ColorResolvable, + reason: `${user?.id} bought from shop`, + }) + .then(async (role) => { + const userId = "SNOWFLKAE"; + const guildId = "SNOWFLAKE"; + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId, + guildId, + }, + }, + update: {}, + create: { + user: { + connectOrCreate: { + create: { + id: userId, + }, + where: { + id: userId, + }, + }, + }, + guild: { + connectOrCreate: { + create: { + id: guildId, + }, + where: { + id: guildId, + }, + }, + }, + }, + include: { + user: true, + guild: true, + }, + }); + + logger.silly(createGuildMember); + + // Get guild object + const pricePerHour = createGuildMember.guild.shopRolesPricePerHour; + + const updateGuildMember = await prisma.guildMember.update({ + where: { + userId_guildId: { + userId, + guildId, + }, + }, + data: { + creditsEarned: { decrement: pricePerHour }, + }, + }); + + logger.silly(updateGuildMember); + + const createShopRole = await prisma.guildShopRoles.upsert({ + where: { + guildId_userId_roleId: { + guildId: guild.id, + userId: user.id, + roleId: role.id, + }, + }, + update: {}, + create: { + roleId: role.id, + lastPayed: new Date(), + 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(createShopRole); + + await (member?.roles as GuildMemberRoleManager)?.add(role?.id); + logger?.silly(`Role ${role?.name} was bought by ${user?.tag}`); + const interactionEmbed = new EmbedBuilder() + .setTitle("[:shopping_cart:] Buy") + .setDescription( + `You bought **${optionName}** for **${pluralize( + pricePerHour, + "credit" + )}**.` + ) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + return interaction?.editReply({ + embeds: [interactionEmbed], + }); + }) + .catch(() => { + throw new Error("Failed creating role."); + }); +}; diff --git a/src/commands/shop/groups/roles/subcommands/cancel/index.ts b/src/commands/shop/groups/roles/subcommands/cancel/index.ts new file mode 100644 index 0000000..80ff1cd --- /dev/null +++ b/src/commands/shop/groups/roles/subcommands/cancel/index.ts @@ -0,0 +1,126 @@ +// Dependencies +// Helpers +import { + ChatInputCommandInteraction, + EmbedBuilder, + GuildMemberRoleManager, + SlashCommandSubcommandBuilder, +} from "discord.js"; +// Configurations +// Models +import deferReply from "../../../../../../handlers/deferReply"; +import logger from "../../../../../../middlewares/logger"; +// Configurations +// Models + +import prisma from "../../../../../../handlers/database"; +import getEmbedData from "../../../../../../helpers/getEmbedData"; +import pluralize from "../../../../../../helpers/pluralize"; + +// Function +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("cancel") + .setDescription("Cancel a purchase.") + .addRoleOption((option) => + option + .setName("role") + .setDescription("Role you wish to cancel.") + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { successColor, footerText, footerIcon } = await getEmbedData( + interaction.guild + ); + const { options, guild, user, member } = interaction; + const optionRole = options.getRole("role"); + if (optionRole === null) + throw new Error("We could not read your requested role."); + if (!guild) throw new Error("No guild specified"); + if (!user) throw new Error("No user specified"); + + const roleExist = await prisma.guildShopRoles.findUnique({ + where: { + guildId_userId_roleId: { + guildId: guild.id, + userId: user.id, + roleId: optionRole.id, + }, + }, + }); + if (roleExist === null) return; + await (member?.roles as GuildMemberRoleManager)?.remove(optionRole?.id); + await guild?.roles + .delete(optionRole?.id, `${user?.id} canceled from shop`) + .then(async () => { + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + 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("Guild member not created"); + + const deleteShopRole = await prisma.guildShopRoles.delete({ + where: { + guildId_userId_roleId: { + guildId: guild?.id, + userId: user?.id, + roleId: optionRole?.id, + }, + }, + }); + + logger.silly(deleteShopRole); + + const interactionEmbed = new EmbedBuilder() + .setTitle("[:shopping_cart:] Cancel") + .setDescription(`You have canceled ${optionRole.name}.`) + .setTimestamp() + .setColor(successColor) + .addFields({ + name: "Your balance", + value: `${pluralize(createGuildMember.creditsEarned, "credit")}`, + }) + .setFooter({ text: footerText, iconURL: footerIcon }); + return interaction?.editReply({ + embeds: [interactionEmbed], + }); + }); +}; diff --git a/src/commands/shop/index.ts b/src/commands/shop/index.ts index 0df13dd..8d80477 100644 --- a/src/commands/shop/index.ts +++ b/src/commands/shop/index.ts @@ -2,8 +2,14 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; // Modules -import moduleCpgg from "./modules/cpgg"; -import moduleRoles from "./modules/roles"; +import { + builder as RolesBuilder, + execute as RolesExecute, +} from "./groups/roles"; +import { + builder as CpggBuilder, + execute as CpggExecute, +} from "./subcommands/cpgg"; // Function export const builder = new SlashCommandBuilder() @@ -12,8 +18,8 @@ export const builder = new SlashCommandBuilder() .setDMPermission(false) // Modules - .addSubcommand(moduleCpgg.builder) - .addSubcommandGroup(moduleRoles.builder); + .addSubcommand(CpggBuilder) + .addSubcommandGroup(RolesBuilder); // Execute the command export const execute = async (interaction: ChatInputCommandInteraction) => { @@ -21,7 +27,7 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { switch (options.getSubcommand()) { case "cpgg": { - await moduleCpgg.execute(interaction); + await CpggExecute(interaction); break; } default: { @@ -31,7 +37,7 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { switch (options.getSubcommandGroup()) { case "roles": { - await moduleRoles.execute(interaction); + await RolesExecute(interaction); break; } default: { diff --git a/src/commands/shop/modules/cpgg/index.ts b/src/commands/shop/modules/cpgg/index.ts deleted file mode 100644 index 29600d3..0000000 --- a/src/commands/shop/modules/cpgg/index.ts +++ /dev/null @@ -1,195 +0,0 @@ -import axios from "axios"; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ChatInputCommandInteraction, - EmbedBuilder, - Message, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import { v4 as uuidv4 } from "uuid"; -import prisma from "../../../../handlers/database"; -import deferReply from "../../../../handlers/deferReply"; -import encryption from "../../../../helpers/encryption"; -import getEmbedData from "../../../../helpers/getEmbedData"; -import logger from "../../../../middlewares/logger"; - -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("cpgg") - .setDescription("Buy cpgg power.") - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("How much credits you want to withdraw.") - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedData(interaction.guild); - const { options, guild, user, client } = interaction; - const optionAmount = options?.getInteger("amount"); - if (optionAmount === null) { - logger?.silly(`Amount is null.`); - const interactionEmbed = new EmbedBuilder() - .setTitle("[:dollar:] Gift") - .setDescription("We could not read your requested amount.") - .setTimestamp() - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }); - return interaction?.editReply({ - embeds: [interactionEmbed], - }); - } - if (!guild) throw new Error("Guild not found"); - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: user.id, - guildId: guild.id, - }, - }, - update: {}, - create: { - 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); - - const dmUser = client?.users?.cache?.get(user?.id); - - if ((optionAmount || createGuildMember.creditsEarned) < 100) - throw new Error("You can't withdraw to CPGG below 100 credits."); - - if ((optionAmount || createGuildMember.creditsEarned) > 1000000) - throw new Error("Amount or user credits is above 1.000.000."); - - if (createGuildMember.creditsEarned < optionAmount) - throw new Error("You can't withdraw more than you have on your account."); - - if ( - !createGuildMember.guild.apiCpggUrlIv || - !createGuildMember.guild.apiCpggUrlContent - ) - throw new Error("No API url available"); - - if ( - !createGuildMember.guild.apiCpggTokenIv || - !createGuildMember.guild.apiCpggTokenContent - ) - throw new Error("No API token available"); - - const code = uuidv4(); - const url = encryption.decrypt({ - iv: createGuildMember.guild.apiCpggUrlIv, - content: createGuildMember.guild.apiCpggUrlContent, - }); - const api = axios?.create({ - baseURL: `${url}/api/`, - headers: { - Authorization: `Bearer ${encryption.decrypt({ - iv: createGuildMember.guild.apiCpggTokenIv, - content: createGuildMember.guild.apiCpggTokenContent, - })}`, - }, - }); - const shopUrl = `${url}/store`; - const buttons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("Redeem it here") - .setStyle(ButtonStyle.Link) - .setEmoji("🏦") - .setURL(`${shopUrl}?voucher=${code}`) - ); - await api - ?.post("vouchers", { - uses: 1, - code, - credits: optionAmount || createGuildMember.creditsEarned, - memo: `${interaction?.createdTimestamp} - ${interaction?.user?.id}`, - }) - ?.then(async () => { - logger?.silly(`Successfully created voucher.`); - createGuildMember.creditsEarned -= - optionAmount || createGuildMember.creditsEarned; - - const updateGuildMember = await prisma.guildMember.update({ - where: { - userId_guildId: { - userId: user.id, - guildId: guild.id, - }, - }, - data: { - creditsEarned: { - decrement: optionAmount || createGuildMember.creditsEarned, - }, - }, - }); - - logger.silly(updateGuildMember); - - if (!interaction.guild) throw new Error("Guild is undefined"); - const dmEmbed = new EmbedBuilder() - .setTitle("[:shopping_cart:] CPGG") - .setDescription( - `This voucher comes from **${interaction.guild.name}**.` - ) - .setTimestamp() - .addFields({ - name: "💶 Credits", - value: `${optionAmount || createGuildMember.creditsEarned}`, - inline: true, - }) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }); - await dmUser - ?.send({ - embeds: [dmEmbed], - components: [buttons], - }) - .then(async (msg: Message) => { - const interactionEmbed = new EmbedBuilder() - .setTitle("[:shopping_cart:] CPGG") - .setDescription(`I have sent you the code in [DM](${msg.url})!`) - .setTimestamp() - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }); - await interaction?.editReply({ - embeds: [interactionEmbed], - }); - }); - }); - return true; - }, -}; diff --git a/src/commands/shop/modules/roles/index.ts b/src/commands/shop/modules/roles/index.ts deleted file mode 100644 index 329c08f..0000000 --- a/src/commands/shop/modules/roles/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Dependencies -import { - ChatInputCommandInteraction, - SlashCommandSubcommandGroupBuilder, -} from "discord.js"; - -// Handlers - -// Modules -import moduleBuy from "./modules/buy"; -import moduleCancel from "./modules/cancel"; - -import prisma from "../../../../handlers/database"; - -export default { - builder: (group: SlashCommandSubcommandGroupBuilder) => { - return ( - group - .setName("roles") - .setDescription("Shop for custom roles.") - - // Modules - .addSubcommand(moduleBuy.builder) - .addSubcommand(moduleCancel.builder) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - if (!interaction.guild) return; - const { options, guild } = interaction; - - const getGuild = await prisma.guild.findUnique({ - where: { id: guild.id }, - }); - if (!getGuild) throw new Error("Guild not found"); - - if (!getGuild.shopRolesEnabled) - throw new Error("This server has disabled shop roles."); - - if (options?.getSubcommand() === "buy") { - await moduleBuy.execute(interaction); - } - - if (options?.getSubcommand() === "cancel") { - await moduleCancel.execute(interaction); - } - }, -}; diff --git a/src/commands/shop/modules/roles/modules/buy/index.ts b/src/commands/shop/modules/roles/modules/buy/index.ts deleted file mode 100644 index 5844ed4..0000000 --- a/src/commands/shop/modules/roles/modules/buy/index.ts +++ /dev/null @@ -1,178 +0,0 @@ -// Dependencies -// Helpers -import { - ChatInputCommandInteraction, - ColorResolvable, - EmbedBuilder, - GuildMemberRoleManager, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import deferReply from "../../../../../../handlers/deferReply"; -import getEmbedData from "../../../../../../helpers/getEmbedData"; -import logger from "../../../../../../middlewares/logger"; -// Configurations -// import fetchUser from "../../../../../../helpers/userData"; -// Models - -import prisma from "../../../../../../handlers/database"; -import pluralize from "../../../../../../helpers/pluralize"; - -// Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("buy") - .setDescription("Buy a custom role.") - .addStringOption((option) => - option - .setName("name") - .setDescription("Name of the role you wish to buy.") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("color") - .setDescription("Color of the role you wish to buy.") - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - const { successColor, footerText, footerIcon } = await getEmbedData( - interaction.guild - ); - const { options, guild, user, member } = interaction; - const optionName = options?.getString("name"); - const optionColor = options?.getString("color"); - // If amount is null - if (optionName === null) - throw new Error("We could not read your requested name"); - await guild?.roles - .create({ - name: optionName, - color: optionColor as ColorResolvable, - reason: `${user?.id} bought from shop`, - }) - .then(async (role) => { - const userId = "SNOWFLKAE"; - const guildId = "SNOWFLAKE"; - - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId, - guildId, - }, - }, - update: {}, - create: { - user: { - connectOrCreate: { - create: { - id: userId, - }, - where: { - id: userId, - }, - }, - }, - guild: { - connectOrCreate: { - create: { - id: guildId, - }, - where: { - id: guildId, - }, - }, - }, - }, - include: { - user: true, - guild: true, - }, - }); - - logger.silly(createGuildMember); - - // Get guild object - const pricePerHour = createGuildMember.guild.shopRolesPricePerHour; - - const updateGuildMember = await prisma.guildMember.update({ - where: { - userId_guildId: { - userId, - guildId, - }, - }, - data: { - creditsEarned: { decrement: pricePerHour }, - }, - }); - - logger.silly(updateGuildMember); - - const createShopRole = await prisma.guildShopRoles.upsert({ - where: { - guildId_userId_roleId: { - guildId: guild.id, - userId: user.id, - roleId: role.id, - }, - }, - update: {}, - create: { - roleId: role.id, - lastPayed: new Date(), - 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(createShopRole); - - await (member?.roles as GuildMemberRoleManager)?.add(role?.id); - logger?.silly(`Role ${role?.name} was bought by ${user?.tag}`); - const interactionEmbed = new EmbedBuilder() - .setTitle("[:shopping_cart:] Buy") - .setDescription( - `You bought **${optionName}** for **${pluralize( - pricePerHour, - "credit" - )}**.` - ) - .setTimestamp() - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }); - return interaction?.editReply({ - embeds: [interactionEmbed], - }); - }) - .catch(() => { - throw new Error("Failed creating role."); - }); - }, -}; diff --git a/src/commands/shop/modules/roles/modules/cancel/index.ts b/src/commands/shop/modules/roles/modules/cancel/index.ts deleted file mode 100644 index e4b96b5..0000000 --- a/src/commands/shop/modules/roles/modules/cancel/index.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Dependencies -// Helpers -import { - ChatInputCommandInteraction, - EmbedBuilder, - GuildMemberRoleManager, - SlashCommandSubcommandBuilder, -} from "discord.js"; -// Configurations -// Models -import deferReply from "../../../../../../handlers/deferReply"; -import logger from "../../../../../../middlewares/logger"; -// Configurations -// Models - -import prisma from "../../../../../../handlers/database"; -import getEmbedData from "../../../../../../helpers/getEmbedData"; -import pluralize from "../../../../../../helpers/pluralize"; - -// Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("cancel") - .setDescription("Cancel a purchase.") - .addRoleOption((option) => - option - .setName("role") - .setDescription("Role you wish to cancel.") - .setRequired(true) - ); - }, - execute: async (interaction: ChatInputCommandInteraction) => { - await deferReply(interaction, true); - - const { successColor, footerText, footerIcon } = await getEmbedData( - interaction.guild - ); - const { options, guild, user, member } = interaction; - const optionRole = options.getRole("role"); - if (optionRole === null) - throw new Error("We could not read your requested role."); - if (!guild) throw new Error("No guild specified"); - if (!user) throw new Error("No user specified"); - - const roleExist = await prisma.guildShopRoles.findUnique({ - where: { - guildId_userId_roleId: { - guildId: guild.id, - userId: user.id, - roleId: optionRole.id, - }, - }, - }); - if (roleExist === null) return; - await (member?.roles as GuildMemberRoleManager)?.remove(optionRole?.id); - await guild?.roles - .delete(optionRole?.id, `${user?.id} canceled from shop`) - .then(async () => { - const createGuildMember = await prisma.guildMember.upsert({ - where: { - userId_guildId: { - userId: user.id, - guildId: guild.id, - }, - }, - update: {}, - create: { - 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("Guild member not created"); - - const deleteShopRole = await prisma.guildShopRoles.delete({ - where: { - guildId_userId_roleId: { - guildId: guild?.id, - userId: user?.id, - roleId: optionRole?.id, - }, - }, - }); - - logger.silly(deleteShopRole); - - const interactionEmbed = new EmbedBuilder() - .setTitle("[:shopping_cart:] Cancel") - .setDescription(`You have canceled ${optionRole.name}.`) - .setTimestamp() - .setColor(successColor) - .addFields({ - name: "Your balance", - value: `${pluralize(createGuildMember.creditsEarned, "credit")}`, - }) - .setFooter({ text: footerText, iconURL: footerIcon }); - return interaction?.editReply({ - embeds: [interactionEmbed], - }); - }); - }, -}; diff --git a/src/commands/shop/subcommands/cpgg/index.ts b/src/commands/shop/subcommands/cpgg/index.ts new file mode 100644 index 0000000..0fedae7 --- /dev/null +++ b/src/commands/shop/subcommands/cpgg/index.ts @@ -0,0 +1,194 @@ +import axios from "axios"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ChatInputCommandInteraction, + EmbedBuilder, + Message, + SlashCommandSubcommandBuilder, +} from "discord.js"; +import { v4 as uuidv4 } from "uuid"; +import prisma from "../../../../handlers/database"; +import deferReply from "../../../../handlers/deferReply"; +import encryption from "../../../../helpers/encryption"; +import getEmbedData from "../../../../helpers/getEmbedData"; +import logger from "../../../../middlewares/logger"; + +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("cpgg") + .setDescription("Buy cpgg power.") + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("How much credits you want to withdraw.") + .setRequired(true) + ); +}; + +export const execute = async (interaction: ChatInputCommandInteraction) => { + await deferReply(interaction, true); + + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedData(interaction.guild); + const { options, guild, user, client } = interaction; + const optionAmount = options?.getInteger("amount"); + if (optionAmount === null) { + logger?.silly(`Amount is null.`); + const interactionEmbed = new EmbedBuilder() + .setTitle("[:dollar:] Gift") + .setDescription("We could not read your requested amount.") + .setTimestamp() + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + return interaction?.editReply({ + embeds: [interactionEmbed], + }); + } + if (!guild) throw new Error("Guild not found"); + + const createGuildMember = await prisma.guildMember.upsert({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + update: {}, + create: { + 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); + + const dmUser = client?.users?.cache?.get(user?.id); + + if ((optionAmount || createGuildMember.creditsEarned) < 100) + throw new Error("You can't withdraw to CPGG below 100 credits."); + + if ((optionAmount || createGuildMember.creditsEarned) > 1000000) + throw new Error("Amount or user credits is above 1.000.000."); + + if (createGuildMember.creditsEarned < optionAmount) + throw new Error("You can't withdraw more than you have on your account."); + + if ( + !createGuildMember.guild.apiCpggUrlIv || + !createGuildMember.guild.apiCpggUrlContent + ) + throw new Error("No API url available"); + + if ( + !createGuildMember.guild.apiCpggTokenIv || + !createGuildMember.guild.apiCpggTokenContent + ) + throw new Error("No API token available"); + + const code = uuidv4(); + const url = encryption.decrypt({ + iv: createGuildMember.guild.apiCpggUrlIv, + content: createGuildMember.guild.apiCpggUrlContent, + }); + const api = axios?.create({ + baseURL: `${url}/api/`, + headers: { + Authorization: `Bearer ${encryption.decrypt({ + iv: createGuildMember.guild.apiCpggTokenIv, + content: createGuildMember.guild.apiCpggTokenContent, + })}`, + }, + }); + const shopUrl = `${url}/store`; + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Redeem it here") + .setStyle(ButtonStyle.Link) + .setEmoji("🏦") + .setURL(`${shopUrl}?voucher=${code}`) + ); + await api + ?.post("vouchers", { + uses: 1, + code, + credits: optionAmount || createGuildMember.creditsEarned, + memo: `${interaction?.createdTimestamp} - ${interaction?.user?.id}`, + }) + ?.then(async () => { + logger?.silly(`Successfully created voucher.`); + createGuildMember.creditsEarned -= + optionAmount || createGuildMember.creditsEarned; + + const updateGuildMember = await prisma.guildMember.update({ + where: { + userId_guildId: { + userId: user.id, + guildId: guild.id, + }, + }, + data: { + creditsEarned: { + decrement: optionAmount || createGuildMember.creditsEarned, + }, + }, + }); + + logger.silly(updateGuildMember); + + if (!interaction.guild) throw new Error("Guild is undefined"); + const dmEmbed = new EmbedBuilder() + .setTitle("[:shopping_cart:] CPGG") + .setDescription( + `This voucher comes from **${interaction.guild.name}**.` + ) + .setTimestamp() + .addFields({ + name: "💶 Credits", + value: `${optionAmount || createGuildMember.creditsEarned}`, + inline: true, + }) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + await dmUser + ?.send({ + embeds: [dmEmbed], + components: [buttons], + }) + .then(async (msg: Message) => { + const interactionEmbed = new EmbedBuilder() + .setTitle("[:shopping_cart:] CPGG") + .setDescription(`I have sent you the code in [DM](${msg.url})!`) + .setTimestamp() + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }); + await interaction?.editReply({ + embeds: [interactionEmbed], + }); + }); + }); + return true; +}; diff --git a/src/commands/utils/index.ts b/src/commands/utils/index.ts index 4bce106..7b32084 100644 --- a/src/commands/utils/index.ts +++ b/src/commands/utils/index.ts @@ -1,25 +1,31 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; // Modules -import moduleAbout from "./modules/about"; -import moduleAvatar from "./modules/avatar"; +import { + builder as AboutBuilder, + execute as AboutExecute, +} from "./modules/about"; +import { + builder as AvatarBuilder, + execute as AvatarExecute, +} from "./modules/avatar"; export const builder = new SlashCommandBuilder() .setName("utils") .setDescription("Common utility.") // Modules - .addSubcommand(moduleAbout.builder) - .addSubcommand(moduleAvatar.builder); + .addSubcommand(AboutBuilder) + .addSubcommand(AvatarBuilder); // Execute the command export const execute = async (interaction: ChatInputCommandInteraction) => { switch (interaction.options.getSubcommand()) { case "about": - await moduleAbout.execute(interaction); + await AboutExecute(interaction); break; case "avatar": - await moduleAvatar.execute(interaction); + await AvatarExecute(interaction); break; default: throw new Error( diff --git a/src/commands/utils/modules/about/index.ts b/src/commands/utils/modules/about/index.ts index 3003511..13a9b87 100644 --- a/src/commands/utils/modules/about/index.ts +++ b/src/commands/utils/modules/about/index.ts @@ -14,107 +14,103 @@ import deferReply from "../../../../handlers/deferReply"; import getEmbedConfig from "../../../../helpers/getEmbedData"; // Function -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("about") - .setDescription("Check information about this instance"); - }, - execute: async (interaction: CommandInteraction) => { - await deferReply(interaction, false); - - if (!interaction.guild) throw new Error("You need to be in a guild"); - - const { client } = interaction; - - // await cooldown( - // interaction.guild, - // interaction.user, - // interaction.commandId, - // 3600 - // ); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - // // Initialize a storage for the user ids - // const userIds = new Set(); - // // Iterate over all guilds (always cached) - // for await (const guild of client.guilds.cache.values()) { - // // Fetch all guild members and iterate over them - // for await (const member of (await guild.members.fetch()).values()) { - // // Fetch the user, if user already cached, returns value from cache - // // Will probably always return from cache - // const user = await client.users.fetch(member.id); - // // Check if user id is not already in set and user is not a bot - // if (!userIds.has(user.id) && !user.bot) { - // // Add unique user id to our set - // userIds.add(user.id); - // } - // } - // } - - const buttons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("Support") - .setStyle(ButtonStyle.Link) - .setEmoji("💬") - .setURL("https://discord.zyner.org"), - new ButtonBuilder() - .setLabel("Documentation") - .setStyle(ButtonStyle.Link) - .setEmoji("📚") - .setURL("https://xyter.zyner.org") - ); - - const interactionEmbed = new EmbedBuilder() - .setColor(successColor) - .setTitle(":toolbox:︱About this instance") - .setDescription( - `This bot instance is hosted by [${process.env.BOT_HOSTER_NAME}](${process.env.BOT_HOSTER_URL}) who might have modified the [source code](https://github.com/ZynerOrg/xyter).` - ) - .setFields( - { - name: "Latency", - value: `${Math.round(client.ws.ping)} ms`, - inline: true, - }, - { - name: "Servers (cached)", - value: `${client.guilds.cache.size}`, - inline: true, - }, - { - name: "Users (cached)", - value: `${client.guilds.cache.reduce( - (a, g) => a + g.memberCount, - 0 - )}`, - inline: true, - }, - { - name: "Version", - value: `[${process.env.npm_package_version}](https://github.com/ZynerOrg/xyter/releases/tag/${process.env.npm_package_version})`, - inline: true, - }, - { - name: "Since last restart", - value: `${formatDuration( - intervalToDuration({ - start: subMilliseconds(new Date(), client.uptime), - end: new Date(), - }) - )}`, - inline: true, - } - ) - .setTimestamp() - .setFooter({ text: footerText, iconURL: footerIcon }); - - await interaction.editReply({ - embeds: [interactionEmbed], - components: [buttons], - }); - }, +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("about") + .setDescription("Check information about this instance"); +}; + +export const execute = async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + + if (!interaction.guild) throw new Error("You need to be in a guild"); + + const { client } = interaction; + + // await cooldown( + // interaction.guild, + // interaction.user, + // interaction.commandId, + // 3600 + // ); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + // // Initialize a storage for the user ids + // const userIds = new Set(); + // // Iterate over all guilds (always cached) + // for await (const guild of client.guilds.cache.values()) { + // // Fetch all guild members and iterate over them + // for await (const member of (await guild.members.fetch()).values()) { + // // Fetch the user, if user already cached, returns value from cache + // // Will probably always return from cache + // const user = await client.users.fetch(member.id); + // // Check if user id is not already in set and user is not a bot + // if (!userIds.has(user.id) && !user.bot) { + // // Add unique user id to our set + // userIds.add(user.id); + // } + // } + // } + + const buttons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Support") + .setStyle(ButtonStyle.Link) + .setEmoji("💬") + .setURL("https://discord.zyner.org"), + new ButtonBuilder() + .setLabel("Documentation") + .setStyle(ButtonStyle.Link) + .setEmoji("📚") + .setURL("https://xyter.zyner.org") + ); + + const interactionEmbed = new EmbedBuilder() + .setColor(successColor) + .setTitle(":toolbox:︱About this instance") + .setDescription( + `This bot instance is hosted by [${process.env.BOT_HOSTER_NAME}](${process.env.BOT_HOSTER_URL}) who might have modified the [source code](https://github.com/ZynerOrg/xyter).` + ) + .setFields( + { + name: "Latency", + value: `${Math.round(client.ws.ping)} ms`, + inline: true, + }, + { + name: "Servers (cached)", + value: `${client.guilds.cache.size}`, + inline: true, + }, + { + name: "Users (cached)", + value: `${client.guilds.cache.reduce((a, g) => a + g.memberCount, 0)}`, + inline: true, + }, + { + name: "Version", + value: `[${process.env.npm_package_version}](https://github.com/ZynerOrg/xyter/releases/tag/${process.env.npm_package_version})`, + inline: true, + }, + { + name: "Since last restart", + value: `${formatDuration( + intervalToDuration({ + start: subMilliseconds(new Date(), client.uptime), + end: new Date(), + }) + )}`, + inline: true, + } + ) + .setTimestamp() + .setFooter({ text: footerText, iconURL: footerIcon }); + + await interaction.editReply({ + embeds: [interactionEmbed], + components: [buttons], + }); }; diff --git a/src/commands/utils/modules/avatar/index.ts b/src/commands/utils/modules/avatar/index.ts index aa7bafb..9dc6966 100644 --- a/src/commands/utils/modules/avatar/index.ts +++ b/src/commands/utils/modules/avatar/index.ts @@ -6,45 +6,44 @@ import { import deferReply from "../../../../handlers/deferReply"; import getEmbedConfig from "../../../../helpers/getEmbedData"; -export default { - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("avatar") - .setDescription("Check someones avatar!)") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user whose avatar you want to check") - ); - }, - execute: async (interaction: CommandInteraction) => { - await deferReply(interaction, false); - - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild +export const builder = (command: SlashCommandSubcommandBuilder) => { + return command + .setName("avatar") + .setDescription("Check someones avatar!)") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user whose avatar you want to check") ); - const userOption = interaction.options.getUser("user"); - - const targetUser = userOption || interaction.user; - - const embed = new EmbedBuilder() - .setTitle(":toolbox:︱Avatar") - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }); - - const avatarUrl = targetUser.displayAvatarURL(); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - userOption - ? `You can also [download it here](${avatarUrl})!` - : `Your avatar is available to [download here](${avatarUrl}).` - ) - .setThumbnail(avatarUrl) - .setColor(successColor), - ], - }); - }, +}; + +export const execute = async (interaction: CommandInteraction) => { + await deferReply(interaction, false); + + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const userOption = interaction.options.getUser("user"); + + const targetUser = userOption || interaction.user; + + const embed = new EmbedBuilder() + .setTitle(":toolbox:︱Avatar") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + const avatarUrl = targetUser.displayAvatarURL(); + + return interaction.editReply({ + embeds: [ + embed + .setDescription( + userOption + ? `You can also [download it here](${avatarUrl})!` + : `Your avatar is available to [download here](${avatarUrl}).` + ) + .setThumbnail(avatarUrl) + .setColor(successColor), + ], + }); }; diff --git a/src/events/messageCreate/index.ts b/src/events/messageCreate/index.ts index 6a745cc..36447e1 100644 --- a/src/events/messageCreate/index.ts +++ b/src/events/messageCreate/index.ts @@ -11,4 +11,13 @@ export const execute = async (message: Message) => { await modules.credits.execute(message); await modules.points.execute(message); await modules.counters.execute(message); + + const { client } = message; + if (!message.member) return; + if (message.author.bot) return; + + client.emit("guildMemberAdd", message.member); + client.emit("guildMemberRemove", message.member); + client.emit("messageDelete", message); + client.emit("messageUpdate", message, message); };