diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index 92db357..d754312 100644 --- a/.cspell/custom-dictionary-workspace.txt +++ b/.cspell/custom-dictionary-workspace.txt @@ -1,5 +1,6 @@ # Custom Dictionary Words Controlpanel +cooldown cpgg dagen discordjs diff --git a/.gitignore b/.gitignore index 9f176fb..9e1cc15 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,7 @@ config.json package-lock.json -**/config/*.ts -!**/config/index.ts -!**/config/example.*.ts +config/ # Build build/ diff --git a/src/commands/config/index.ts b/src/commands/config/index.ts deleted file mode 100644 index 4110d5c..0000000 --- a/src/commands/config/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "./modules"; - -// Handlers -import logger from "../../logger"; - -export const builder = new SlashCommandBuilder() - .setName("config") - .setDescription("Manage guild configurations.") - - .addSubcommand(modules.pterodactyl.builder) - .addSubcommand(modules.credits.builder) - .addSubcommand(modules.points.builder) - .addSubcommand(modules.welcome.builder) - .addSubcommand(modules.audits.builder) - .addSubcommand(modules.shop.builder) - .addSubcommand(modules.embeds.builder); - -export const moduleData = modules; - -// Function -export const execute = async (interaction: CommandInteraction) => { - switch (interaction.options?.getSubcommand()) { - case "pterodactyl": - return modules.pterodactyl.execute(interaction); - case "credits": - return modules.credits.execute(interaction); - case "points": - return modules.points.execute(interaction); - case "welcome": - return modules.welcome.execute(interaction); - case "audits": - return modules.audits.execute(interaction); - case "shop": - return modules.shop.execute(interaction); - case "embeds": - return modules.embeds.execute(interaction); - } -}; diff --git a/src/commands/config/modules/audits/index.ts b/src/commands/config/modules/audits/index.ts deleted file mode 100644 index 0ea9a17..0000000 --- a/src/commands/config/modules/audits/index.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Dependencies -import { CommandInteraction, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../logger"; - -// Models -import guildSchema from "../../../../database/schemas/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { ChannelType } from "discord-api-types/v10"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("audits") - .setDescription("Audits") - .addBooleanOption((option) => - option.setName("status").setDescription("Should audits be enabled?") - ) - .addChannelOption((option) => - option - .setName("channel") - .setDescription("Channel for audit messages.") - .addChannelTypes(ChannelType.GuildText) - ); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - const { guild, options } = interaction; - - // Get options - const status = options?.getBoolean("status"); - const channel = options?.getChannel("channel"); - - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild not found in database.`); - } - - // Modify values - guildDB.audits.status = status !== null ? status : guildDB?.audits?.status; - guildDB.audits.channelId = - channel !== null ? channel.id : guildDB?.audits?.channelId; - - // Save guild - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild audits updated.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - Guild [Audits]", - description: `Audits settings updated.`, - color: successColor, - fields: [ - { - name: "🤖 Status", - value: `${guildDB?.audits?.status}`, - inline: true, - }, - { - name: "🌊 Channel", - value: `${guildDB?.audits?.channelId}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/commands/config/modules/credits/index.ts b/src/commands/config/modules/credits/index.ts deleted file mode 100644 index 8ec5998..0000000 --- a/src/commands/config/modules/credits/index.ts +++ /dev/null @@ -1,144 +0,0 @@ -// Dependencies -import { CommandInteraction, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; - -//Handlers -import logger from "../../../../logger"; - -// Models -import guildSchema from "../../../../database/schemas/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("credits") - .setDescription(`Credits`) - .addBooleanOption((option) => - option.setName("status").setDescription("Should credits be enabled?") - ) - .addNumberOption((option) => - option.setName("rate").setDescription("Amount of credits per message.") - ) - .addNumberOption((option) => - option - .setName("minimum-length") - .setDescription("Minimum length of message to earn credits.") - ) - .addNumberOption((option) => - option - .setName("work-rate") - .setDescription("Maximum amount of credits on work.") - ) - .addNumberOption((option) => - option - .setName("work-timeout") - .setDescription("Timeout between work schedules (seconds).") - ) - .addNumberOption((option) => - option - .setName("timeout") - .setDescription("Timeout between earning credits (seconds).") - ); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure member - const { guild, options } = interaction; - - if (guild == null) return; - - // Get options - const status = options?.getBoolean("status"); - 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"); - - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild is null`); - } - - // Modify values - guildDB.credits.status = - status !== null ? status : guildDB?.credits?.status; - guildDB.credits.rate = rate !== null ? rate : guildDB?.credits?.rate; - guildDB.credits.timeout = - timeout !== null ? timeout : guildDB?.credits?.timeout; - guildDB.credits.workRate = - workRate !== null ? workRate : guildDB?.credits?.workRate; - guildDB.credits.workTimeout = - workTimeout !== null ? workTimeout : guildDB?.credits?.workTimeout; - guildDB.credits.minimumLength = - minimumLength !== null ? minimumLength : guildDB?.credits?.minimumLength; - - // Save guild - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild saved`); - - return interaction?.editReply({ - embeds: [ - { - title: ":tools: Settings - Guild [Credits]", - description: `Credits settings updated.`, - color: successColor, - fields: [ - { - name: "🤖 Status", - value: `${guildDB?.credits?.status}`, - inline: true, - }, - { - name: "📈 Rate", - value: `${guildDB?.credits?.rate}`, - inline: true, - }, - { - name: "📈 Work Rate", - value: `${guildDB?.credits?.workRate}`, - inline: true, - }, - { - name: "🔨 Minimum Length", - value: `${guildDB?.credits?.minimumLength}`, - inline: true, - }, - { - name: "⏰ Timeout", - value: `${guildDB?.credits?.timeout}`, - inline: true, - }, - { - name: "⏰ Work Timeout", - value: `${guildDB?.credits?.workTimeout}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/commands/config/modules/embeds/components/getValues/index.ts b/src/commands/config/modules/embeds/components/getValues/index.ts deleted file mode 100644 index d511a52..0000000 --- a/src/commands/config/modules/embeds/components/getValues/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ColorResolvable, CommandInteraction } from "discord.js"; -import guildSchema from "../../../../../../database/schemas/guild"; -import getEmbedConfig from "../../../../../../helpers/getEmbedConfig"; - -export default async (interaction: CommandInteraction) => { - const { options, guild } = interaction; - - if (!guild) throw new Error("Guild not found"); - - const embedConfig = await getEmbedConfig(guild); - if (!embedConfig) throw new Error("Embed config not found"); - - // Get new values - const newSuccessColor = options.getString("success-color") as ColorResolvable; - const newWaitColor = options.getString("wait-color") as ColorResolvable; - const newErrorColor = options.getString("error-color") as ColorResolvable; - const newFooterIcon = options.getString("footer-icon"); - const newFooterText = options.getString("footer-text"); - - // Get guild values - const guildData = await guildSchema.findOne({ - guildId: guild.id, - }); - if (!guildData) throw new Error("Guild data not found"); - if (!guildData?.embeds) - throw new Error("Guild embed configuration not found"); - let { successColor, waitColor, errorColor, footerText, footerIcon } = - guildData.embeds; - - // Set new values - successColor = newSuccessColor || successColor; - waitColor = newWaitColor || waitColor; - errorColor = newErrorColor || errorColor; - footerIcon = newFooterIcon || footerIcon; - footerText = newFooterText || footerText; - - return { successColor, waitColor, errorColor, footerText, footerIcon }; -}; diff --git a/src/commands/config/modules/embeds/index.ts b/src/commands/config/modules/embeds/index.ts deleted file mode 100644 index 94f1db8..0000000 --- a/src/commands/config/modules/embeds/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Dependencies -import { - ColorResolvable, - CommandInteraction, - MessageEmbed, - Permissions, -} from "discord.js"; - -//Handlers -import logger from "../../../../logger"; - -// Models -import guildSchema from "../../../../database/schemas/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; -import getValues from "./components/getValues"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("embeds") - .setDescription(`Embeds`) - .addStringOption((option) => - option - .setName("success-color") - .setDescription("No provided description") - ) - .addStringOption((option) => - option.setName("wait-color").setDescription("No provided description") - ) - .addStringOption((option) => - option.setName("error-color").setDescription("No provided description") - ) - .addStringOption((option) => - option.setName("footer-icon").setDescription("No provided description") - ) - .addStringOption((option) => - option.setName("footer-text").setDescription("No provided description") - ); - }, - execute: async (interaction: CommandInteraction) => { - const { guild } = interaction; - if (!guild) throw new Error("Guild not found"); - - const { successColor, waitColor, errorColor, footerText, footerIcon } = - await getValues(interaction); - - // Initialize embed object - const embed = new MessageEmbed() - .setTitle("[:tools:] Embeds") - .setFooter({ text: footerText, iconURL: footerIcon }) - .setTimestamp(new Date()); - - // Get guild values - const guildData = await guildSchema.findOne({ - guildId: guild.id, - }); - if (!guildData) throw new Error("Guild data not found"); - - await guildData.save().then(async () => { - 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, - }, - ]); - - return interaction.editReply({ - embeds: [embed], - }); - }); - }, -}; diff --git a/src/commands/config/modules/index.ts b/src/commands/config/modules/index.ts deleted file mode 100644 index 09a9f5a..0000000 --- a/src/commands/config/modules/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import audits from "./audits"; -import credits from "./credits"; -import points from "./points"; -import pterodactyl from "./pterodactyl"; -import shop from "./shop"; -import welcome from "./welcome"; -import embeds from "./embeds"; - -export default { audits, credits, points, pterodactyl, shop, welcome, embeds }; diff --git a/src/commands/config/modules/points/index.ts b/src/commands/config/modules/points/index.ts deleted file mode 100644 index 0693916..0000000 --- a/src/commands/config/modules/points/index.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Dependencies -import { CommandInteraction, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../logger"; - -// Models -import guildSchema from "../../../../database/schemas/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("points") - .setDescription("Points") - .addBooleanOption((option) => - option.setName("status").setDescription("Should credits be enabled?") - ) - .addNumberOption((option) => - option.setName("rate").setDescription("Amount of credits per message.") - ) - .addNumberOption((option) => - option - .setName("minimum-length") - .setDescription("Minimum length of message to earn credits.") - ) - .addNumberOption((option) => - option - .setName("timeout") - .setDescription("Timeout between earning credits (milliseconds).") - ); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - // Destructure member - const { options, guild } = interaction; - - // Get options - const status = options?.getBoolean("status"); - const rate = options?.getNumber("rate"); - const timeout = options?.getNumber("timeout"); - const minimumLength = options?.getNumber("minimum-length"); - - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild not found in database.`); - } - - // Modify values - guildDB.points.status = status !== null ? status : guildDB?.points?.status; - guildDB.points.rate = rate !== null ? rate : guildDB?.points?.rate; - guildDB.points.timeout = - timeout !== null ? timeout : guildDB?.points?.timeout; - guildDB.points.minimumLength = - minimumLength !== null ? minimumLength : guildDB?.points?.minimumLength; - - // Save guild - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild points updated.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - Guild [Points]", - description: `Points settings updated.`, - color: successColor, - fields: [ - { - name: "🤖 Status", - value: `${guildDB?.points?.status}`, - inline: true, - }, - { - name: "📈 Rate", - value: `${guildDB?.points?.rate}`, - inline: true, - }, - { - name: "🔨 Minimum Length", - value: `${guildDB?.points?.minimumLength}`, - inline: true, - }, - { - name: "⏰ Timeout", - value: `${guildDB?.points?.timeout}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/commands/config/modules/pterodactyl/index.ts b/src/commands/config/modules/pterodactyl/index.ts deleted file mode 100644 index 52e65b0..0000000 --- a/src/commands/config/modules/pterodactyl/index.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Dependencies -import { CommandInteraction, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../logger"; - -// Models -import apiSchema from "../../../../database/schemas/api"; -import encryption from "../../../../handlers/encryption"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("pterodactyl") - .setDescription("Controlpanel.gg") - .addStringOption((option) => - option - .setName("url") - .setDescription(`Controlpanel.gg URL`) - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("token") - .setDescription(`Controlpanel.gg Token`) - .setRequired(true) - ); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure member - const { options, guild } = interaction; - - // Get options - const tokenData = options.getString("token"); - const url = options.getString("url"); - const token = tokenData && encryption.encrypt(tokenData); - - // Update API credentials - await apiSchema - ?.findOneAndUpdate( - { guildId: guild?.id }, - { url, token }, - { new: true, upsert: true } - ) - .then(async () => { - logger?.silly(`Updated API credentials.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - Guild [Pterodactyl]", - color: successColor, - description: `Successfully updated API credentials.`, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/commands/config/modules/shop/index.ts b/src/commands/config/modules/shop/index.ts deleted file mode 100644 index a775385..0000000 --- a/src/commands/config/modules/shop/index.ts +++ /dev/null @@ -1,97 +0,0 @@ -// Dependencies -import { CommandInteraction, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../logger"; - -// Models -import guildSchema from "../../../../database/schemas/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("shop") - .setDescription("Shop") - .addBooleanOption((option) => - option - .setName("roles-status") - .setDescription("Should roles be enabled?") - ) - .addNumberOption((option) => - option - .setName("roles-price-per-hour") - .setDescription("Price per hour for roles.") - ); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure member - const { options, guild } = interaction; - - // Get options - const rolesStatus = options?.getBoolean("roles-status"); - const rolesPricePerHour = options?.getNumber("roles-price-per-hour"); - - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild not found in database.`); - } - - // Modify values - guildDB.shop.roles.status = - rolesStatus !== null ? rolesStatus : guildDB?.shop?.roles?.status; - guildDB.shop.roles.pricePerHour = - rolesPricePerHour !== null - ? rolesPricePerHour - : guildDB?.shop?.roles?.pricePerHour; - - // Save guild - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild shop updated.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - Guild [Shop]", - description: `Shop settings updated.`, - color: successColor, - fields: [ - { - name: "🤖 Roles Status", - value: `${guildDB?.shop?.roles.status}`, - inline: true, - }, - { - name: "🌊 Roles Price Per Hour", - value: `${guildDB?.shop?.roles.pricePerHour}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/commands/config/modules/welcome/index.ts b/src/commands/config/modules/welcome/index.ts deleted file mode 100644 index e6d5e9b..0000000 --- a/src/commands/config/modules/welcome/index.ts +++ /dev/null @@ -1,142 +0,0 @@ -// Dependencies -import { CommandInteraction, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; - -// Handlers -import logger from "../../../../logger"; - -// Models -import guildSchema from "../../../../database/schemas/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import { ChannelType } from "discord-api-types/v10"; - -// Function -export default { - metadata: { - guildOnly: true, - ephemeral: true, - permissions: [Permissions.FLAGS.MANAGE_GUILD], - }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("welcome") - .setDescription("Welcome") - .addBooleanOption((option) => - option.setName("status").setDescription("Should welcome be enabled?") - ) - .addChannelOption((option) => - option - .setName("join-channel") - .setDescription("Channel for join messages.") - .addChannelTypes(ChannelType.GuildText) - ) - - .addChannelOption((option) => - option - .setName("leave-channel") - .setDescription("Channel for leave messages.") - .addChannelTypes(ChannelType.GuildText) - ) - - .addStringOption((option) => - option - .setName("leave-message") - .setDescription("Message for leave messages.") - ) - .addStringOption((option) => - option - .setName("join-message") - .setDescription("Message for join messages.") - ); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); // Destructure member - const { options, guild } = interaction; - - // Get options - 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"); - - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild not found in database.`); - } - - // Modify values - guildDB.welcome.status = - status !== null ? status : guildDB?.welcome?.status; - guildDB.welcome.joinChannel = - joinChannel !== null ? joinChannel.id : guildDB?.welcome?.joinChannel; - guildDB.welcome.leaveChannel = - leaveChannel !== null ? leaveChannel.id : guildDB?.welcome?.leaveChannel; - - guildDB.welcome.joinChannelMessage = - joinChannelMessage !== null - ? joinChannelMessage - : guildDB?.welcome?.joinChannelMessage; - guildDB.welcome.leaveChannelMessage = - leaveChannelMessage !== null - ? leaveChannelMessage - : guildDB?.welcome?.leaveChannelMessage; - - // Save guild - await guildDB?.save()?.then(async () => { - logger?.silly(`Guild welcome updated.`); - - if (!guildDB?.welcome?.status) { - return interaction?.editReply({ - embeds: [ - { - title: "[:tools:] Welcome", - description: `This module is currently disabled, please enable it to continue.`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - return interaction?.editReply({ - embeds: [ - { - title: "[:tools:] Welcome", - description: `The following configuration will be used. - - [👋] **Welcome** - - ㅤ**Channel**: <#${guildDB?.welcome?.joinChannel}> - ㅤ**Message**: ${guildDB?.welcome?.joinChannelMessage} - - [🚪] **Leave** - - ㅤ**Channel**: <#${guildDB?.welcome?.leaveChannel}> - ㅤ**Message**: ${guildDB?.welcome?.leaveChannelMessage}`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/commands/reputation/modules/give.ts b/src/commands/reputation/modules/give.ts deleted file mode 100644 index c55e909..0000000 --- a/src/commands/reputation/modules/give.ts +++ /dev/null @@ -1,165 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../helpers/getEmbedConfig"; - -import { timeout } from "../../../config/reputation"; - -// Handlers -import logger from "../../../logger"; - -// Models -import timeoutSchema from "../../../database/schemas/timeout"; -import fetchUser from "../../../helpers/fetchUser"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { guildOnly: true, ephemeral: true }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("give") - .setDescription("Give reputation to a user") - .addUserOption((option) => - option - .setName("target") - .setDescription("The user you want to repute.") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("type") - .setDescription("What type of reputation you want to repute") - .setRequired(true) - .addChoices( - { name: "Positive", value: "positive" }, - { - name: "Negative", - value: "negative", - } - ) - ); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); // Destructure - const { options, user, guild } = interaction; - - // Target option - const optionTarget = options?.getUser("target"); - - // Type information - const optionType = options?.getString("type"); - - if (guild === null) { - return logger?.silly(`Guild is null`); - } - - // User information - const userObj = await fetchUser(user, guild); - - if (userObj === null) { - return logger?.silly(`User is null`); - } - - // Check if user has a timeout - const isTimeout = await timeoutSchema?.findOne({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-04-10-16-42", - }); - - // If user is not on timeout - if (isTimeout) { - logger?.silly(`User is on timeout`); - - return interaction?.editReply({ - embeds: [ - { - title: ":loudspeaker: Reputation [Give]", - description: `You cannot give reputation while on timeout, please wait ${timeout} seconds.`, - timestamp: new Date(), - color: errorColor, - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - // Do not allow self reputation - if (optionTarget?.id === user?.id) { - logger?.silly(`User is trying to give reputation to self`); - - return interaction?.editReply({ - embeds: [ - { - title: ":loudspeaker: Reputation [Give]", - description: `You cannot give reputation to yourself.`, - timestamp: new Date(), - color: errorColor, - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - // If type is positive - if (optionType === "positive") { - logger?.silly(`User is giving positive reputation`); - - userObj.reputation += 1; - } - - // If type is negative - else if (optionType === "negative") { - logger?.silly(`User is giving negative reputation`); - - userObj.reputation -= 1; - } - - // Save user - await userObj?.save()?.then(async () => { - logger?.silly(`User reputation has been updated`); - - await timeoutSchema?.create({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-04-10-16-42", - }); - - return interaction?.editReply({ - embeds: [ - { - title: ":loudspeaker: Reputation [Give]", - description: `You have given reputation to ${optionTarget}`, - timestamp: new Date(), - color: successColor, - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - - setTimeout(async () => { - logger?.silly(`Removing timeout`); - - await timeoutSchema?.deleteOne({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-04-10-16-42", - }); - }, timeout); - }, -}; diff --git a/src/commands/shop/modules/index.ts b/src/commands/shop/modules/index.ts deleted file mode 100644 index d191ec6..0000000 --- a/src/commands/shop/modules/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import pterodactyl from "./pterodactyl"; -import * as roles from "./roles"; - -export default { pterodactyl, roles }; diff --git a/src/commands/utility/modules/about.ts b/src/commands/utility/modules/about.ts deleted file mode 100644 index 18eca4c..0000000 --- a/src/commands/utility/modules/about.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; - -// Configurations -import getEmbedConfig from "../../../helpers/getEmbedConfig"; - -import { hosterName, hosterUrl } from "../../../config/other"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Function -export default { - metadata: { guildOnly: false, ephemeral: false }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command.setName("about").setDescription("About this bot!)"); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const interactionEmbed = { - title: ":hammer: Utilities [About]", - description: `This bot is hosted by ${ - hosterUrl ? `[${hosterName}](${hosterUrl})` : `${hosterName}` - }, the bot is developed by [Zyner](https://github.com/ZynerOrg)! - - If you are interested in contributing, then just [fork it](https://github.com/ZynerOrg/xyter) yourself, we :heart: Open Source.`, - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; - interaction?.editReply({ embeds: [interactionEmbed] }); - }, -}; diff --git a/src/config/example.database.ts b/src/config_example/database.ts similarity index 100% rename from src/config/example.database.ts rename to src/config_example/database.ts diff --git a/src/config/example.discord.ts b/src/config_example/discord.ts similarity index 100% rename from src/config/example.discord.ts rename to src/config_example/discord.ts diff --git a/src/config/example.embed.ts b/src/config_example/embed.ts similarity index 100% rename from src/config/example.embed.ts rename to src/config_example/embed.ts diff --git a/src/config/example.encryption.ts b/src/config_example/encryption.ts similarity index 100% rename from src/config/example.encryption.ts rename to src/config_example/encryption.ts diff --git a/src/config/example.other.ts b/src/config_example/other.ts similarity index 73% rename from src/config/example.other.ts rename to src/config_example/other.ts index 1695d42..eefd62c 100644 --- a/src/config/example.other.ts +++ b/src/config_example/other.ts @@ -8,7 +8,7 @@ export const guildId = ""; export const hosterName = "someone"; // Hoster Url -export const hosterUrl = "scheme://domain.tld"; +export const hosterUrl = "https://xyter.zyner.org/customization/change-hoster"; // Winston log level export const logLevel = "info"; diff --git a/src/config/example.reputation.ts b/src/config_example/reputation.ts similarity index 100% rename from src/config/example.reputation.ts rename to src/config_example/reputation.ts diff --git a/src/database/index.ts b/src/database/index.ts deleted file mode 100644 index 9060935..0000000 --- a/src/database/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -// 3rd party dependencies -import mongoose from "mongoose"; - -// Dependencies -import logger from "../logger"; - -// Configuration -import { url } from "../config/database"; - -export default async () => { - await mongoose.connect(url).then(async (connection) => { - logger.info(`Connected to database: ${connection.connection.name}`); - }); - - mongoose.connection.on("error", async (error) => { - logger.error(`${error}`); - }); - - mongoose.connection.on("warn", async (warning) => { - logger.warn(warning); - }); -}; diff --git a/src/events/guildMemberAdd/audits.ts b/src/events/guildMemberAdd/audits.ts deleted file mode 100644 index 6e140c6..0000000 --- a/src/events/guildMemberAdd/audits.ts +++ /dev/null @@ -1,58 +0,0 @@ -import logger from "../../logger"; -import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; - -import guildSchema from "../../database/schemas/guild"; - -import getEmbedConfig from "../../helpers/getEmbedConfig"; - -export default { - execute: async (member: GuildMember) => { - const { footerText, footerIcon, successColor } = await getEmbedConfig( - member.guild - ); - - const guildData = await guildSchema.findOne({ guildId: member.guild.id }); - - const { client } = member; - - if (guildData === null) return; - - if (guildData.audits.status !== true) return; - if (!guildData.audits.channelId) return; - - const channel = client.channels.cache.get(`${guildData.audits.channelId}`); - - if (channel === null) return; - - (channel as TextChannel) - .send({ - embeds: [ - new MessageEmbed() - .setColor(successColor) - .setAuthor({ - name: "Member Joined", - iconURL: member.user.displayAvatarURL(), - }) - .setDescription(`${member.user} ${member.user.tag}`) - .addFields([ - { name: "Account Age", value: `${member.user.createdAt}` }, - ]) - .setTimestamp() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), - ], - }) - .then(async () => { - logger.info( - `Audit log sent for event guildMemberAdd in guild ${member.guild.name} (${member.guild.id})` - ); - }) - .catch(async () => { - logger.error( - `Audit log failed to send for event guildMemberAdd in guild ${member.guild.name} (${member.guild.id})` - ); - }); - }, -}; diff --git a/src/events/guildMemberRemove/audits.ts b/src/events/guildMemberRemove/audits.ts deleted file mode 100644 index 9f2c343..0000000 --- a/src/events/guildMemberRemove/audits.ts +++ /dev/null @@ -1,55 +0,0 @@ -import logger from "../../logger"; -import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; - -import guildSchema from "../../database/schemas/guild"; - -import getEmbedConfig from "../../helpers/getEmbedConfig"; - -export default { - execute: async (member: GuildMember) => { - const { footerText, footerIcon, errorColor } = await getEmbedConfig( - member.guild - ); - - const guildData = await guildSchema.findOne({ guildId: member.guild.id }); - - const { client } = member; - - if (guildData === null) return; - - if (guildData.audits.status !== true) return; - if (!guildData.audits.channelId) return; - - const channel = client.channels.cache.get(`${guildData.audits.channelId}`); - - if (channel === null) return; - - (channel as TextChannel) - .send({ - embeds: [ - new MessageEmbed() - .setColor(errorColor) - .setAuthor({ - name: "Member Left", - iconURL: member.user.displayAvatarURL(), - }) - .setDescription(`${member.user} ${member.user.tag}`) - .setTimestamp() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), - ], - }) - .then(async () => { - logger.info( - `Audit log sent for event guildMemberRemove in guild ${member.guild.name} (${member.guild.id})` - ); - }) - .catch(async () => { - logger.error( - `Audit log failed to send for event guildMemberRemove in guild ${member.guild.name} (${member.guild.id})` - ); - }); - }, -}; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts deleted file mode 100644 index 7e173dc..0000000 --- a/src/events/interactionCreate/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// 3rd party dependencies -import { CommandInteraction } from "discord.js"; - -// Dependencies -import isCommand from "../../events/interactionCreate/components/isCommand"; -import logger from "../../logger"; -import audits from "./audits"; -import { IEventOptions } from "../../interfaces/EventOptions"; - -export const options: IEventOptions = { - type: "on", -}; - -export const execute = async (interaction: CommandInteraction) => { - const { guild, id } = interaction; - - logger?.silly( - `New interaction: ${id} in guild: ${guild?.name} (${guild?.id})` - ); - - await audits.execute(interaction); - await isCommand(interaction); -}; diff --git a/src/events/messageCreate/modules/credits/index.ts b/src/events/messageCreate/modules/credits/index.ts deleted file mode 100644 index 1058490..0000000 --- a/src/events/messageCreate/modules/credits/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -import logger from "../../../../logger"; -import timeouts from "../../../../database/schemas/timeout"; -import { Message } from "discord.js"; - -import fetchUser from "../../../../helpers/fetchUser"; -import fetchGuild from "../../../../helpers/fetchGuild"; - -export default { - execute: async (message: Message) => { - const { guild, author, content, channel } = message; - - if (guild == null) return; - if (author.bot) return; - if (channel?.type !== "GUILD_TEXT") return; - - const { id: guildId } = guild; - const { id: userId } = author; - - const guildData = await fetchGuild(guild); - const userData = await fetchUser(author, guild); - - if (content.length < guildData.credits.minimumLength) return; - - const timeoutData = { - guildId, - userId, - timeoutId: "2022-04-14-13-51-00", - }; - - const timeout = await timeouts.findOne(timeoutData); - - if (timeout) { - logger.silly( - `User ${userId} in guild ${guildId} is on timeout 2022-04-14-13-51-00` - ); - return; - } - - userData.credits += guildData.credits.rate; - - await userData - .save() - .then(async () => { - logger.silly( - `User ${userId} in guild ${guildId} has ${userData.credits} credits` - ); - }) - .catch(async (err) => { - logger.error( - `Error saving credits for user ${userId} in guild ${guildId}`, - err - ); - }); - - await timeouts - .create(timeoutData) - .then(async () => { - logger.silly( - `Timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId} has been created` - ); - }) - .catch(async (err) => { - logger.error( - `Error creating timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId}`, - err - ); - }); - - setTimeout(async () => { - await timeouts - .deleteOne(timeoutData) - .then(async () => { - logger.silly( - `Timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId} has been deleted` - ); - }) - .catch(async (err) => { - logger.error( - `Error deleting timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId}`, - err - ); - }); - }, guildData.credits.timeout); - }, -}; diff --git a/src/events/messageCreate/modules/points/index.ts b/src/events/messageCreate/modules/points/index.ts deleted file mode 100644 index 12b683a..0000000 --- a/src/events/messageCreate/modules/points/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -import logger from "../../../../logger"; -import timeouts from "../../../../database/schemas/timeout"; - -import fetchUser from "../../../../helpers/fetchUser"; -import fetchGuild from "../../../../helpers/fetchGuild"; - -import { Message } from "discord.js"; -export default { - execute: async (message: Message) => { - const { guild, author, content, channel } = message; - - if (guild == null) return; - if (author.bot) return; - if (channel?.type !== "GUILD_TEXT") return; - - const { id: guildId } = guild; - const { id: userId } = author; - - const guildData = await fetchGuild(guild); - const userData = await fetchUser(author, guild); - - if (content.length < guildData.credits.minimumLength) return; - - const timeoutData = { - guildId, - userId, - timeoutId: "2022-04-14-14-15-00", - }; - - const timeout = await timeouts.findOne(timeoutData); - - if (timeout) { - logger.silly( - `User ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id} is on timeout 2022-04-14-14-15-00` - ); - return; - } - - userData.points += guildData.points.rate; - - await userData - .save() - .then(async () => { - logger.silly( - `Successfully saved user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` - ); - }) - .catch(async (err) => { - logger.error( - `Error saving points for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`, - err - ); - }); - - logger.silly( - `User ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id}) has ${userData.points} points` - ); - - await timeouts - .create(timeoutData) - .then(async () => { - logger.silly( - `Successfully created timeout for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` - ); - }) - .catch(async (err) => { - logger.error( - `Error creating timeout 2022-04-14-14-15-00 for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`, - err - ); - }); - - setTimeout(async () => { - await timeouts - .deleteOne(timeoutData) - .then(async () => { - logger.silly( - `Successfully deleted timeout 2022-04-14-14-15-00 for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` - ); - }) - .catch(async (err) => { - logger.error( - `Error deleting timeout 2022-04-14-14-15-00 for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`, - err - ); - }); - }, guildData.points.timeout); - }, -}; diff --git a/src/handlers/deployCommands.ts b/src/handlers/deployCommands.ts index 2123da7..4542377 100644 --- a/src/handlers/deployCommands.ts +++ b/src/handlers/deployCommands.ts @@ -12,6 +12,10 @@ import { ICommand } from "../interfaces/Command"; export default async (client: Client) => { const commandList: Array = []; + if (!client.commands) { + throw new Error("client.commands is not defined"); + } + logger.info("Gathering command list"); await Promise.all( @@ -25,7 +29,7 @@ export default async (client: Client) => { logger.info(`Finished gathering command list.`); }) .catch(async (error) => { - logger.error(`${error}`); + throw new Error(`Could not gather command list: ${error}`); }); const rest = new REST({ version: "9" }).setToken(token); diff --git a/src/handlers/schedules/index.ts b/src/handlers/schedules/index.ts deleted file mode 100644 index c6a1cef..0000000 --- a/src/handlers/schedules/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Dependencies -import { Client } from "discord.js"; -import schedule from "node-schedule"; - -import logger from "../../logger"; - -// Jobs -import shopRoles from "../../jobs/shopRoles"; - -export default async (client: Client) => { - const expression = "*/5 * * * *"; - - schedule.scheduleJob(expression, async () => { - logger.info("Running jobs."); - - await shopRoles(client) - .then(() => { - logger.info("Shop roles job finished."); - }) - .catch((err) => { - logger.error(`Shop roles job failed: ${err}`); - }); - }); -}; diff --git a/src/helpers/addSeconds/index.ts b/src/helpers/addSeconds/index.ts new file mode 100644 index 0000000..91bff4e --- /dev/null +++ b/src/helpers/addSeconds/index.ts @@ -0,0 +1,7 @@ +export default async (numOfSeconds: number, date: Date) => { + if (!numOfSeconds) throw new Error("numOfSeconds is required"); + + date.setSeconds(date.getSeconds() + numOfSeconds); + + return date; +}; diff --git a/src/helpers/cooldown/index.ts b/src/helpers/cooldown/index.ts new file mode 100644 index 0000000..2417d81 --- /dev/null +++ b/src/helpers/cooldown/index.ts @@ -0,0 +1,112 @@ +// Dependencies +import { CommandInteraction, Message } from "discord.js"; + +import logger from "../../logger"; + +import getEmbedConfig from "../../helpers/getEmbedConfig"; +import timeoutSchema from "../../models/timeout"; +import addSeconds from "../../helpers/addSeconds"; + +export const interaction = async (i: CommandInteraction, cooldown: number) => { + const { guild, user, commandId } = i; + + // Check if user has a timeout + const hasTimeout = await timeoutSchema.findOne({ + guildId: guild?.id || "0", + userId: user.id, + cooldown: cooldown, + timeoutId: commandId, + }); + + // If user is not on timeout + if (hasTimeout) { + const { guildId, userId, timeoutId, createdAt } = hasTimeout; + const overDue = (await addSeconds(cooldown, createdAt)) < new Date(); + + if (!overDue) { + const diff = Math.round( + (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 + ); + + throw new Error( + `You must wait ${diff} seconds before using this command.` + ); + } + + // Delete timeout + await timeoutSchema + .deleteOne({ + guildId, + userId, + timeoutId, + cooldown, + }) + .then(async () => { + logger.debug( + `Timeout document ${timeoutId} has been deleted from user ${userId}.` + ); + }); + } + // Create timeout + await timeoutSchema.create({ + guildId: guild?.id || "0", + userId: user.id, + cooldown: cooldown, + timeoutId: commandId, + }); +}; + +export const message = async ( + message: Message, + cooldown: number, + id: string +) => { + const { guild, member } = message; + if (!guild) throw new Error("Guild is undefined"); + if (!member) throw new Error("Member is undefined"); + + // Check if user has a timeout + const hasTimeout = await timeoutSchema.findOne({ + guildId: guild?.id || "0", + userId: member.id, + cooldown: cooldown, + timeoutId: id, + }); + + // If user is not on timeout + if (hasTimeout) { + const { guildId, userId, timeoutId, createdAt } = hasTimeout; + const overDue = (await addSeconds(cooldown, createdAt)) < new Date(); + + if (!overDue) { + const diff = Math.round( + (new Date(hasTimeout.createdAt).getTime() - new Date().getTime()) / 1000 + ); + + throw new Error( + `User: ${userId} on timeout-id: ${id} with cooldown: ${cooldown} secs with remaining: ${diff} secs.` + ); + } + + // Delete timeout + await timeoutSchema + .deleteOne({ + guildId, + userId: member.id, + timeoutId: id, + cooldown, + }) + .then(async () => { + logger.debug( + `Timeout document ${timeoutId} has been deleted from user ${userId}.` + ); + }); + } + // Create timeout + await timeoutSchema.create({ + guildId: guild?.id || "0", + userId: member.id, + cooldown: cooldown, + timeoutId: id, + }); +}; diff --git a/src/helpers/deferReply/index.ts b/src/helpers/deferReply/index.ts index 45d95ed..b5cfdc3 100644 --- a/src/helpers/deferReply/index.ts +++ b/src/helpers/deferReply/index.ts @@ -2,8 +2,6 @@ import { CommandInteraction, MessageEmbed } from "discord.js"; import getEmbedConfig from "../../helpers/getEmbedConfig"; export default async (interaction: CommandInteraction, ephemeral: boolean) => { - if (interaction.guild == null) return; - await interaction.deferReply({ ephemeral, }); diff --git a/src/helpers/dropGuild/index.ts b/src/helpers/dropGuild/index.ts index e0d8356..0c0e2ab 100644 --- a/src/helpers/dropGuild/index.ts +++ b/src/helpers/dropGuild/index.ts @@ -1,9 +1,9 @@ -import guildSchema from "../../database/schemas/guild"; -import userSchema from "../../database/schemas/user"; -import apiSchema from "../../database/schemas/api"; -import counterSchema from "../../database/schemas/counter"; -import shopRoleSchema from "../../database/schemas/shopRole"; -import timeoutSchema from "../../database/schemas/timeout"; +import guildSchema from "../../models/guild"; +import userSchema from "../../models/user"; +import apiSchema from "../../models/api"; +import counterSchema from "../../models/counter"; +import shopRoleSchema from "../../models/shopRole"; +import timeoutSchema from "../../models/timeout"; import logger from "../../logger"; diff --git a/src/helpers/dropUser/index.ts b/src/helpers/dropUser/index.ts index 976cd6c..3093048 100644 --- a/src/helpers/dropUser/index.ts +++ b/src/helpers/dropUser/index.ts @@ -1,4 +1,4 @@ -import userSchema from "../../database/schemas/user"; +import userSchema from "../../models/user"; import logger from "../../logger"; diff --git a/src/helpers/fetchGuild/index.ts b/src/helpers/fetchGuild/index.ts index bf4aff5..a89e840 100644 --- a/src/helpers/fetchGuild/index.ts +++ b/src/helpers/fetchGuild/index.ts @@ -2,7 +2,7 @@ import { Guild } from "discord.js"; // Models -import guildSchema from "../../database/schemas/guild"; +import guildSchema from "../../models/guild"; // Handlers import logger from "../../logger"; diff --git a/src/helpers/fetchUser/index.ts b/src/helpers/fetchUser/index.ts index 19b9f1f..3837f24 100644 --- a/src/helpers/fetchUser/index.ts +++ b/src/helpers/fetchUser/index.ts @@ -2,7 +2,7 @@ import { Guild, User } from "discord.js"; // Models -import userSchema from "../../database/schemas/user"; +import userSchema from "../../models/user"; // Handlers import logger from "../../logger"; diff --git a/src/helpers/getEmbedConfig/index.ts b/src/helpers/getEmbedConfig/index.ts index a52cbe5..22ddf29 100644 --- a/src/helpers/getEmbedConfig/index.ts +++ b/src/helpers/getEmbedConfig/index.ts @@ -1,4 +1,4 @@ -import guildSchema from "../../database/schemas/guild"; +import guildSchema from "../../models/guild"; import * as embedConfig from "../../config/embed"; import { Guild } from "discord.js"; diff --git a/src/helpers/listDir/index.ts b/src/helpers/listDir/index.ts index 247fbe0..95ed1da 100644 --- a/src/helpers/listDir/index.ts +++ b/src/helpers/listDir/index.ts @@ -2,9 +2,7 @@ import fs from "fs"; const fsPromises = fs.promises; export default async (path: string) => { - try { - return await fsPromises.readdir(path); - } catch (err) { - console.error("Error occurred while reading directory!", err); - } + return fsPromises.readdir(path).catch(async (e) => { + throw new Error(`Could not list directory: ${path}`, e); + }); }; diff --git a/src/index.ts b/src/index.ts index 31e3e40..ef3cab7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,10 +4,7 @@ import { token, intents } from "./config/discord"; import { Client } from "discord.js"; // discord.js -import database from "./database"; -import schedules from "./handlers/schedules"; -import * as eventManager from "./managers/event"; -import * as commandManager from "./managers/command"; +import * as managers from "./managers"; // Main process that starts all other sub processes const main = async () => { @@ -16,17 +13,7 @@ const main = async () => { intents, }); - // Start database manager - await database(); - - // Start schedule manager - await schedules(client); - - // Start command handler - await commandManager.register(client); - - // Start event handler - await eventManager.register(client); + await managers.start(client); // Authorize with Discord's API await client.login(token); diff --git a/src/interfaces/Event.ts b/src/interfaces/Event.ts new file mode 100644 index 0000000..c411a9f --- /dev/null +++ b/src/interfaces/Event.ts @@ -0,0 +1,6 @@ +import { IEventOptions } from "./EventOptions"; + +export interface IEvent { + options: IEventOptions; + execute: (...args: Promise[]) => Promise; +} diff --git a/src/interfaces/Job.ts b/src/interfaces/Job.ts new file mode 100644 index 0000000..d58dbf8 --- /dev/null +++ b/src/interfaces/Job.ts @@ -0,0 +1,8 @@ +import { Client } from "discord.js"; + +export interface IJob { + options: { + schedule: string; + }; + execute: (client: Client) => Promise; +} diff --git a/src/jobs/shopRoles.ts b/src/jobs/shopRoles.ts index 76ea653..e9ed502 100644 --- a/src/jobs/shopRoles.ts +++ b/src/jobs/shopRoles.ts @@ -4,57 +4,47 @@ import { Client } from "discord.js"; import logger from "../logger"; // Schemas -import userSchema from "../database/schemas/user"; -import shopRoleSchema from "../database/schemas/shopRole"; -import guildSchema from "../database/schemas/guild"; +import userSchema from "../models/user"; +import shopRoleSchema from "../models/shopRole"; +import guildSchema from "../models/guild"; -export default async (client: Client) => { +export const options = { + schedule: "*/5 * * * *", // https://crontab.guru/ +}; + +export const execute = async (client: Client) => { const roles = await shopRoleSchema.find(); - await Promise.all( roles.map(async (role) => { const { guildId, userId, roleId } = role; - const lastPayment = new Date(role.lastPayed); - const nextPayment = new Date( lastPayment.setHours(lastPayment.getHours() + 1) ); - if (new Date() < nextPayment) { logger.silly(`Shop role ${roleId} is not due for payment.`); } - const guildData = await guildSchema.findOne({ guildId }); - if (!guildData) { logger.error(`Guild ${guildId} not found.`); return; } - if (!userId) { logger.error(`User ID not found for shop role ${roleId}.`); return; } - const userData = await userSchema.findOne({ guildId, userId }); - if (!userData) { logger.error(`User ${userId} not found for shop role ${roleId}.`); return; } - const rGuild = client?.guilds?.cache?.get(guildId); - const rMember = await rGuild?.members?.fetch(userId); - if (!rMember) { logger.error(`Member ${userId} not found for shop role ${roleId}.`); return; } - const rRole = rMember.roles.cache.get(roleId); - if (!rMember || !rRole) { logger.error(`Member ${userId} not found for shop role ${roleId}.`); await shopRoleSchema @@ -76,36 +66,27 @@ export default async (client: Client) => { }); return; } - if (new Date() > nextPayment) { logger.silly( `Shop role ${roleId} is due for payment. Withdrawing credits from user ${userId}.` ); - const { pricePerHour } = guildData.shop.roles; - if (userData.credits < pricePerHour) { logger.error( `User ${userId} does not have enough credits to pay for shop role ${roleId}.` ); - if (!rMember) { logger.error(`Member ${userId} not found for shop role ${roleId}.`); return; } - rMember.roles.remove(roleId); - return; } - userData.credits -= pricePerHour; - await userData .save() .then(async () => { role.lastPayed = new Date(); - await role .save() .then(async () => { @@ -117,7 +98,6 @@ export default async (client: Client) => { err ); }); - logger.silly( `Shop role ${roleId} has been paid for. Keeping role ${roleId} for user ${userId}.` ); diff --git a/src/jobs/timeouts.ts b/src/jobs/timeouts.ts new file mode 100644 index 0000000..3198772 --- /dev/null +++ b/src/jobs/timeouts.ts @@ -0,0 +1,35 @@ +import logger from "../logger"; + +import timeoutSchema from "../models/timeout"; + +import addSeconds from "../helpers/addSeconds"; + +export const options = { + schedule: "*/30 * * * *", // https://crontab.guru/ +}; + +export const execute = async () => { + const timeouts = await timeoutSchema.find(); + await Promise.all( + timeouts.map(async (timeout) => { + const { guildId, userId, timeoutId, cooldown, createdAt } = timeout; + + const overDue = (await addSeconds(cooldown, createdAt)) < new Date(); + + if (overDue) { + timeoutSchema + .deleteOne({ + guildId, + userId, + timeoutId, + cooldown, + }) + .then(async () => { + logger.debug( + `Timeout document ${timeoutId} has been deleted from user ${userId}.` + ); + }); + } + }) + ); +}; diff --git a/src/logger/index.ts b/src/logger/index.ts index 1ffbceb..0593ec9 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -3,7 +3,8 @@ import "winston-daily-rotate-file"; import { logLevel } from "../config/other"; -const { combine, timestamp, printf, colorize, align, json } = winston.format; +const { combine, timestamp, printf, errors, colorize, align, json } = + winston.format; export default winston.createLogger({ level: logLevel || "info", @@ -16,6 +17,7 @@ export default winston.createLogger({ }), new winston.transports.Console({ format: combine( + errors({ stack: true, trace: true }), // <-- use errors format colorize({ all: true }), timestamp({ format: "YYYY-MM-DD HH:MM:ss", diff --git a/src/managers/command/index.ts b/src/managers/command/index.ts index 3cf3316..c3c08f9 100644 --- a/src/managers/command/index.ts +++ b/src/managers/command/index.ts @@ -1,20 +1,25 @@ -import fs from "fs"; // fs -import { Collection, Client } from "discord.js"; // discord.js -import logger from "../../logger"; -import { ICommand } from "../../interfaces/Command"; +import { Collection, Client } from "discord.js"; import listDir from "../../helpers/listDir"; +import logger from "../../logger"; + +import { ICommand } from "../../interfaces/Command"; export const register = async (client: Client) => { client.commands = new Collection(); - const commandNames = await listDir("commands"); - if (!commandNames) return; + const commandNames = await listDir("plugins/commands"); + + if (!commandNames) throw new Error("Could not list commands"); logger.info(`Loading ${commandNames.length} commands`); await Promise.all( - commandNames.map(async (commandName, index) => { - const command: ICommand = await import(`../../commands/${commandName}`); + commandNames.map(async (commandName) => { + const command: ICommand = await import( + `../../plugins/commands/${commandName}` + ).catch(async (e) => { + throw new Error(`Could not load command: ${commandName}`, e); + }); client.commands.set(command.builder.name, command); @@ -25,6 +30,6 @@ export const register = async (client: Client) => { logger.info(`Finished loading commands.`); }) .catch(async (err) => { - logger.error(`${err}`); + throw new Error(`Could not load commands: ${err}`); }); }; diff --git a/src/managers/database/index.ts b/src/managers/database/index.ts new file mode 100644 index 0000000..0063c61 --- /dev/null +++ b/src/managers/database/index.ts @@ -0,0 +1,27 @@ +// 3rd party dependencies +import mongoose from "mongoose"; + +// Dependencies +import logger from "../../logger"; + +// Configuration +import { url } from "../../config/database"; + +export const start = async () => { + await mongoose + .connect(url) + .then(async (connection) => { + logger.info(`Connected to database: ${connection.connection.name}`); + }) + .catch(async (e) => { + logger.error("Could not connect to database", e); + }); + + mongoose.connection.on("error", async (error) => { + logger.error(`${error}`); + }); + + mongoose.connection.on("warn", async (warning) => { + logger.warn(warning); + }); +}; diff --git a/src/managers/event/index.ts b/src/managers/event/index.ts index 8a3a506..348e138 100644 --- a/src/managers/event/index.ts +++ b/src/managers/event/index.ts @@ -1,14 +1,19 @@ /* eslint-disable no-loops/no-loops */ import { Client } from "discord.js"; import listDir from "../../helpers/listDir"; +import { IEvent } from "../../interfaces/Event"; +import logger from "../../logger"; export const register = async (client: Client) => { - const eventNames = await listDir("events"); + const eventNames = await listDir("plugins/events"); if (!eventNames) return; for await (const eventName of eventNames) { - const event = await import(`../../events/${eventName}`); - const eventExecutor = async (...args: any[]) => event.execute(...args); + const event: IEvent = await import(`../../plugins/events/${eventName}`); + const eventExecutor = async (...args: Promise[]) => + event.execute(...args).catch(async (err) => { + logger.error(`${err}`); + }); if (!event.options?.type) return; switch (event.options.type) { diff --git a/src/managers/index.ts b/src/managers/index.ts new file mode 100644 index 0000000..6981d3c --- /dev/null +++ b/src/managers/index.ts @@ -0,0 +1,13 @@ +import { Client } from "discord.js"; + +import * as database from "./database"; +import * as schedule from "./schedule"; +import * as event from "./event"; +import * as command from "./command"; + +export const start = async (client: Client) => { + await database.start(); + await schedule.start(client); + await command.register(client); + await event.register(client); +}; diff --git a/src/managers/schedule/index.ts b/src/managers/schedule/index.ts new file mode 100644 index 0000000..9fc8aa9 --- /dev/null +++ b/src/managers/schedule/index.ts @@ -0,0 +1,29 @@ +import logger from "../../logger"; +import { Client } from "discord.js"; + +import { IJob } from "../../interfaces/Job"; + +import listDir from "../../helpers/listDir"; + +import schedule from "node-schedule"; + +export const start = async (client: Client) => { + logger.info("Starting schedule manager..."); + + const jobNames = await listDir("jobs"); + if (!jobNames) return logger.info("No jobs found"); + + await Promise.all( + jobNames.map(async (jobName) => { + const job: IJob = await import(`../../jobs/${jobName}`); + + schedule.scheduleJob(job.options.schedule, async () => { + logger.info(`Executed job ${jobName}!`); + await job.execute(client); + }); + }) + ).then(async () => { + const list = schedule.scheduledJobs; + logger.silly(list); + }); +}; diff --git a/src/database/schemas/api.ts b/src/models/api.ts similarity index 92% rename from src/database/schemas/api.ts rename to src/models/api.ts index 5a78afd..d3690df 100644 --- a/src/database/schemas/api.ts +++ b/src/models/api.ts @@ -1,6 +1,6 @@ import { Snowflake } from "discord.js"; import { model, Schema } from "mongoose"; -import { IEncryptionData } from "../../interfaces/EncryptionData"; +import { IEncryptionData } from "../interfaces/EncryptionData"; export interface IApi { guildId: Snowflake; diff --git a/src/database/schemas/counter.ts b/src/models/counter.ts similarity index 100% rename from src/database/schemas/counter.ts rename to src/models/counter.ts diff --git a/src/database/schemas/guild.ts b/src/models/guild.ts similarity index 100% rename from src/database/schemas/guild.ts rename to src/models/guild.ts diff --git a/src/database/schemas/shopRole.ts b/src/models/shopRole.ts similarity index 100% rename from src/database/schemas/shopRole.ts rename to src/models/shopRole.ts diff --git a/src/database/schemas/timeout.ts b/src/models/timeout.ts similarity index 77% rename from src/database/schemas/timeout.ts rename to src/models/timeout.ts index f923f08..0e3554c 100644 --- a/src/database/schemas/timeout.ts +++ b/src/models/timeout.ts @@ -4,7 +4,10 @@ import { Schema, model } from "mongoose"; export interface ITimeout { userId: Snowflake; guildId: Snowflake; + cooldown: number; timeoutId: string; + createdAt: Date; + updatedAt: Date; } const timeoutSchema = new Schema( @@ -21,6 +24,12 @@ const timeoutSchema = new Schema( unique: false, index: true, }, + cooldown: { + type: Number, + required: true, + unique: false, + index: true, + }, timeoutId: { type: String }, }, { timestamps: true } diff --git a/src/database/schemas/user.ts b/src/models/user.ts similarity index 100% rename from src/database/schemas/user.ts rename to src/models/user.ts diff --git a/src/plugins/buttons/primary/index.ts b/src/plugins/buttons/primary/index.ts new file mode 100644 index 0000000..c1441db --- /dev/null +++ b/src/plugins/buttons/primary/index.ts @@ -0,0 +1,8 @@ +import { CommandInteraction } from "discord.js"; +import logger from "../../../logger"; + +export const metadata = { guildOnly: false, ephemeral: false }; + +export const execute = async (interaction: CommandInteraction) => { + logger.debug("primary button clicked!"); +}; diff --git a/src/commands/counters/index.ts b/src/plugins/commands/counters/index.ts similarity index 93% rename from src/commands/counters/index.ts rename to src/plugins/commands/counters/index.ts index da17e90..eca16dc 100644 --- a/src/commands/counters/index.ts +++ b/src/plugins/commands/counters/index.ts @@ -1,6 +1,6 @@ import { CommandInteraction } from "discord.js"; import { SlashCommandBuilder } from "@discordjs/builders"; -import logger from "../../logger"; +import logger from "../../../logger"; import modules from "../../commands/counters/modules"; diff --git a/src/commands/counters/modules/index.ts b/src/plugins/commands/counters/modules/index.ts similarity index 100% rename from src/commands/counters/modules/index.ts rename to src/plugins/commands/counters/modules/index.ts diff --git a/src/commands/counters/modules/view/index.ts b/src/plugins/commands/counters/modules/view/index.ts similarity index 91% rename from src/commands/counters/modules/view/index.ts rename to src/plugins/commands/counters/modules/view/index.ts index b509dd5..77e1714 100644 --- a/src/commands/counters/modules/view/index.ts +++ b/src/plugins/commands/counters/modules/view/index.ts @@ -1,10 +1,10 @@ -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChannelType } from "discord-api-types/v10"; -import counterSchema from "../../../../database/schemas/counter"; +import counterSchema from "../../../../../models/counter"; export default { metadata: { guildOnly: true, ephemeral: false }, @@ -25,7 +25,6 @@ export default { }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, guild } = interaction; diff --git a/src/commands/credits/index.ts b/src/plugins/commands/credits/index.ts similarity index 96% rename from src/commands/credits/index.ts rename to src/plugins/commands/credits/index.ts index 02c03a5..9d1a3b4 100644 --- a/src/commands/credits/index.ts +++ b/src/plugins/commands/credits/index.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; -import logger from "../../logger"; +import logger from "../../../logger"; import modules from "./modules"; diff --git a/src/commands/credits/modules/balance/index.ts b/src/plugins/commands/credits/modules/balance/index.ts similarity index 91% rename from src/commands/credits/modules/balance/index.ts rename to src/plugins/commands/credits/modules/balance/index.ts index 2387d57..0e646c2 100644 --- a/src/commands/credits/modules/balance/index.ts +++ b/src/plugins/commands/credits/modules/balance/index.ts @@ -1,10 +1,10 @@ -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import logger from "../../../../logger"; +import logger from "../../../../../logger"; -import fetchUser from "../../../../helpers/fetchUser"; +import fetchUser from "../../../../../helpers/fetchUser"; export default { metadata: { guildOnly: true, ephemeral: true }, @@ -19,7 +19,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, user, guild } = interaction; diff --git a/src/commands/credits/modules/gift/index.ts b/src/plugins/commands/credits/modules/gift/index.ts similarity index 96% rename from src/commands/credits/modules/gift/index.ts rename to src/plugins/commands/credits/modules/gift/index.ts index 9873c02..84b2350 100644 --- a/src/commands/credits/modules/gift/index.ts +++ b/src/plugins/commands/credits/modules/gift/index.ts @@ -2,15 +2,15 @@ import { CommandInteraction, MessageEmbed } from "discord.js"; // Configurations -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; // Handlers -import logger from "../../../../logger"; +import logger from "../../../../../logger"; import mongoose from "mongoose"; // Models -import fetchUser from "../../../../helpers/fetchUser"; +import fetchUser from "../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -38,7 +38,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, user, guild, client } = interaction; diff --git a/src/commands/credits/modules/index.ts b/src/plugins/commands/credits/modules/index.ts similarity index 100% rename from src/commands/credits/modules/index.ts rename to src/plugins/commands/credits/modules/index.ts diff --git a/src/commands/credits/modules/top/index.ts b/src/plugins/commands/credits/modules/top/index.ts similarity index 88% rename from src/commands/credits/modules/top/index.ts rename to src/plugins/commands/credits/modules/top/index.ts index 0aaa2f8..29aadda 100644 --- a/src/commands/credits/modules/top/index.ts +++ b/src/plugins/commands/credits/modules/top/index.ts @@ -1,10 +1,10 @@ -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import logger from "../../../../logger"; +import logger from "../../../../../logger"; -import userSchema, { IUser } from "../../../../database/schemas/user"; +import userSchema, { IUser } from "../../../../../models/user"; export default { metadata: { guildOnly: true, ephemeral: false }, @@ -13,7 +13,6 @@ export default { return command.setName("top").setDescription(`View the top users`); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { guild } = interaction; diff --git a/src/commands/credits/modules/work/index.ts b/src/plugins/commands/credits/modules/work/index.ts similarity index 50% rename from src/commands/credits/modules/work/index.ts rename to src/plugins/commands/credits/modules/work/index.ts index 7f29629..a84a696 100644 --- a/src/commands/credits/modules/work/index.ts +++ b/src/plugins/commands/credits/modules/work/index.ts @@ -4,17 +4,17 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import Chance from "chance"; // Configurations -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; // Handlers -import logger from "../../../../logger"; +import logger from "../../../../../logger"; // Models -import timeoutSchema from "../../../../database/schemas/timeout"; +import * as cooldown from "../../../../../helpers/cooldown"; // Helpers -import fetchUser from "../../../../helpers/fetchUser"; -import fetchGuild from "../../../../helpers/fetchGuild"; +import fetchUser from "../../../../../helpers/fetchUser"; +import fetchGuild from "../../../../../helpers/fetchGuild"; export default { metadata: { guildOnly: true, ephemeral: true }, @@ -23,9 +23,9 @@ export default { return command.setName("work").setDescription(`Work to earn credits`); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); // Destructure member + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure member const { guild, user } = interaction; const embed = new MessageEmbed() @@ -39,33 +39,13 @@ export default { // Chance module const chance = new Chance(); - // Check if user has a timeout - const isTimeout = await timeoutSchema?.findOne({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-03-15-19-16", - }); - if (guild === null) { return logger?.silly(`Guild is null`); } const guildDB = await fetchGuild(guild); - // If user is not on timeout - if (isTimeout) { - logger?.silly(`User ${user?.id} is on timeout`); - - return interaction.editReply({ - embeds: [ - embed - .setDescription( - `You are on timeout, please wait ${guildDB?.credits.workTimeout} seconds.` - ) - .setColor(errorColor), - ], - }); - } + await cooldown.interaction(interaction, guildDB?.credits?.workTimeout); const creditsEarned = chance.integer({ min: 0, @@ -93,23 +73,5 @@ export default { ], }); }); - - // Create a timeout for the user - await timeoutSchema?.create({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-03-15-19-16", - }); - - setTimeout(async () => { - logger?.silly(`Removing timeout for user ${user?.id}`); - - // When timeout is out, remove it from the database - await timeoutSchema?.deleteOne({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-03-15-19-16", - }); - }, guildDB?.credits?.workTimeout); }, }; diff --git a/src/commands/fun/index.ts b/src/plugins/commands/fun/index.ts similarity index 94% rename from src/commands/fun/index.ts rename to src/plugins/commands/fun/index.ts index 5c71cd2..e0337a6 100644 --- a/src/commands/fun/index.ts +++ b/src/plugins/commands/fun/index.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; -import logger from "../../logger"; +import logger from "../../../logger"; import modules from "../../commands/fun/modules"; diff --git a/src/commands/fun/modules/index.ts b/src/plugins/commands/fun/modules/index.ts similarity index 100% rename from src/commands/fun/modules/index.ts rename to src/plugins/commands/fun/modules/index.ts diff --git a/src/commands/fun/modules/meme/index.ts b/src/plugins/commands/fun/modules/meme/index.ts similarity index 92% rename from src/commands/fun/modules/meme/index.ts rename to src/plugins/commands/fun/modules/meme/index.ts index c168557..88b2304 100644 --- a/src/commands/fun/modules/meme/index.ts +++ b/src/plugins/commands/fun/modules/meme/index.ts @@ -1,11 +1,11 @@ -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; import axios from "axios"; import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; export default { - metadata: { guildOnly: false, ephemeral: false }, + metadata: { guildOnly: false, ephemeral: false, cooldown: 15 }, builder: (command: SlashCommandSubcommandBuilder) => { return command.setName("meme").setDescription("Get a meme from r/memes)"); diff --git a/src/commands/manage/index.ts b/src/plugins/commands/manage/index.ts similarity index 95% rename from src/commands/manage/index.ts rename to src/plugins/commands/manage/index.ts index 14b5f6c..1f914bb 100644 --- a/src/commands/manage/index.ts +++ b/src/plugins/commands/manage/index.ts @@ -4,7 +4,7 @@ import { CommandInteraction } from "discord.js"; // Groups import modules from "../../commands/manage/modules"; -import logger from "../../logger"; +import logger from "../../../logger"; export const moduleData = modules; diff --git a/src/commands/manage/modules/counters/index.ts b/src/plugins/commands/manage/modules/counters/index.ts similarity index 95% rename from src/commands/manage/modules/counters/index.ts rename to src/plugins/commands/manage/modules/counters/index.ts index 36d6cd9..dbafa9f 100644 --- a/src/commands/manage/modules/counters/index.ts +++ b/src/plugins/commands/manage/modules/counters/index.ts @@ -2,7 +2,7 @@ import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; -import logger from "../../../../logger"; +import logger from "../../../../../logger"; // Modules import modules from "./modules"; diff --git a/src/commands/manage/modules/counters/modules/add/index.ts b/src/plugins/commands/manage/modules/counters/modules/add/index.ts similarity index 91% rename from src/commands/manage/modules/counters/modules/add/index.ts rename to src/plugins/commands/manage/modules/counters/modules/add/index.ts index cdefbb8..d00a319 100644 --- a/src/commands/manage/modules/counters/modules/add/index.ts +++ b/src/plugins/commands/manage/modules/counters/modules/add/index.ts @@ -4,12 +4,12 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChannelType } from "discord-api-types/v10"; // Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; -import logger from "../../../../../../logger"; +import logger from "../../../../../../../logger"; // Models -import counterSchema from "../../../../../../database/schemas/counter"; +import counterSchema from "../../../../../../../models/counter"; // Function export default { @@ -43,7 +43,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, guild } = interaction; diff --git a/src/commands/manage/modules/counters/modules/index.ts b/src/plugins/commands/manage/modules/counters/modules/index.ts similarity index 100% rename from src/commands/manage/modules/counters/modules/index.ts rename to src/plugins/commands/manage/modules/counters/modules/index.ts diff --git a/src/commands/manage/modules/counters/modules/remove/index.ts b/src/plugins/commands/manage/modules/counters/modules/remove/index.ts similarity index 90% rename from src/commands/manage/modules/counters/modules/remove/index.ts rename to src/plugins/commands/manage/modules/counters/modules/remove/index.ts index 295f2b0..563829b 100644 --- a/src/commands/manage/modules/counters/modules/remove/index.ts +++ b/src/plugins/commands/manage/modules/counters/modules/remove/index.ts @@ -2,13 +2,13 @@ import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; // Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; // Handlers -import logger from "../../../../../../logger"; +import logger from "../../../../../../../logger"; // Models -import counterSchema from "../../../../../../database/schemas/counter"; +import counterSchema from "../../../../../../../models/counter"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChannelType } from "discord-api-types/v10"; @@ -33,7 +33,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, guild } = interaction; diff --git a/src/commands/manage/modules/credits/index.ts b/src/plugins/commands/manage/modules/credits/index.ts similarity index 84% rename from src/commands/manage/modules/credits/index.ts rename to src/plugins/commands/manage/modules/credits/index.ts index 5e215ec..b4673ff 100644 --- a/src/commands/manage/modules/credits/index.ts +++ b/src/plugins/commands/manage/modules/credits/index.ts @@ -1,6 +1,6 @@ import { CommandInteraction } from "discord.js"; import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; -import logger from "../../../../logger"; +import logger from "../../../../../logger"; import modules from "./modules"; @@ -14,7 +14,7 @@ export const builder = (group: SlashCommandSubcommandGroupBuilder) => { .addSubcommand(modules.set.builder) .addSubcommand(modules.take.builder) .addSubcommand(modules.transfer.builder) - .addSubcommand(modules.drop.builder); + .addSubcommand(modules.giveaway.builder); }; export const execute = async (interaction: CommandInteraction) => { @@ -27,7 +27,7 @@ export const execute = async (interaction: CommandInteraction) => { return modules.take.execute(interaction); case "transfer": return modules.transfer.execute(interaction); - case "drop": - return modules.drop.execute(interaction); + case "giveaway": + return modules.giveaway.execute(interaction); } }; diff --git a/src/commands/manage/modules/credits/modules/give/index.ts b/src/plugins/commands/manage/modules/credits/modules/give/index.ts similarity index 94% rename from src/commands/manage/modules/credits/modules/give/index.ts rename to src/plugins/commands/manage/modules/credits/modules/give/index.ts index ebbc762..9615f73 100644 --- a/src/commands/manage/modules/credits/modules/give/index.ts +++ b/src/plugins/commands/manage/modules/credits/modules/give/index.ts @@ -3,16 +3,16 @@ import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; // Handlers -import logger from "../../../../../../logger"; +import logger from "../../../../../../../logger"; // Helpers -import pluralize from "../../../../../../helpers/pluralize"; +import pluralize from "../../../../../../../helpers/pluralize"; // Models -import fetchUser from "../../../../../../helpers/fetchUser"; +import fetchUser from "../../../../../../../helpers/fetchUser"; // Function export default { @@ -40,7 +40,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); // Destructure const { guild, options } = interaction; diff --git a/src/commands/manage/modules/credits/modules/drop/index.ts b/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts similarity index 61% rename from src/commands/manage/modules/credits/modules/drop/index.ts rename to src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts index d5129ea..9ee2f17 100644 --- a/src/commands/manage/modules/credits/modules/drop/index.ts +++ b/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts @@ -1,22 +1,21 @@ // Dependencies -import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; +import { + CommandInteraction, + MessageActionRow, + MessageButton, + MessageEmbed, + Permissions, +} from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { v4 as uuidv4 } from "uuid"; import axios from "axios"; -import apiSchema from "../../../../../../database/schemas/api"; -import encryption from "../../../../../../handlers/encryption"; +import apiSchema from "../../../../../../../models/api"; +import encryption from "../../../../../../../handlers/encryption"; // Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; -// Handlers -import logger from "../../../../../../logger"; - -// Helpers -import pluralize from "../../../../../../helpers/pluralize"; - -// Models -import fetchUser from "../../../../../../helpers/fetchUser"; +import { ChannelType } from "discord-api-types/v10"; // Function export default { @@ -28,8 +27,8 @@ export default { builder: (command: SlashCommandSubcommandBuilder) => { return command - .setName("drop") - .setDescription("Drop some credits for specified amount of users.") + .setName("giveaway") + .setDescription("Giveaway some credits for specified amount of users.") .addIntegerOption((option) => option .setName("uses") @@ -47,12 +46,13 @@ export default { .setName("channel") .setDescription("The channel to send the message to.") .setRequired(true) + .addChannelTypes(ChannelType.GuildText) ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); // Destructure + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure const { guild, options } = interaction; const uses = options?.getInteger("uses"); @@ -76,31 +76,39 @@ export default { if (!apiCredentials) return; const api = axios?.create({ - baseURL: apiCredentials.url, + baseURL: `${apiCredentials?.url}/api/`, headers: { Authorization: `Bearer ${encryption.decrypt(apiCredentials.token)}`, }, }); - const shopUrl = apiCredentials?.url?.replace("/api", "/store"); + const shopUrl = `${apiCredentials?.url}/store`; await api .post("vouchers", { uses, code, credits: creditAmount, - memo: `${interaction?.createdTimestamp} - ${interaction?.user?.id}`, + memo: `[GIVEAWAY] ${interaction?.createdTimestamp} - ${interaction?.user?.id}`, }) .then(async () => { await interaction.editReply({ embeds: [ embed .setColor(successColor) - .setDescription(`Successfully crated code: ${code}`), + .setDescription(`Successfully created code: ${code}`), ], }); - const discordChannel = guild.channels.cache.get(channel.id); + const buttons = new MessageActionRow().addComponents( + new MessageButton() + .setLabel("Redeem it here") + .setStyle("LINK") + .setEmoji("🏦") + .setURL(`${shopUrl}?voucher=${code}`) + ); + + const discordChannel = guild?.channels.cache.get(channel.id); if (!discordChannel) return; @@ -109,17 +117,20 @@ export default { discordChannel.send({ embeds: [ new MessageEmbed() - .setTitle("[:parachute:] Code Drop!") + .setTitle("[:parachute:] Credits!") .addFields([ - { name: "Code", value: `${code}`, inline: true }, - { name: "Amount", value: `${creditAmount}`, inline: true }, - { name: "Uses", value: `${uses}`, inline: true }, + { + name: "💶 Credits", + value: `${creditAmount}`, + inline: true, + }, ]) .setDescription( - `${interaction.user} dropped a voucher! You can use the code [here](${shopUrl})!` + `${interaction.user} dropped a voucher for a maximum **${uses}** members!` ) .setColor(successColor), ], + components: [buttons], }); }); }, diff --git a/src/commands/manage/modules/credits/modules/index.ts b/src/plugins/commands/manage/modules/credits/modules/index.ts similarity index 55% rename from src/commands/manage/modules/credits/modules/index.ts rename to src/plugins/commands/manage/modules/credits/modules/index.ts index 07b72e6..96acc29 100644 --- a/src/commands/manage/modules/credits/modules/index.ts +++ b/src/plugins/commands/manage/modules/credits/modules/index.ts @@ -2,6 +2,6 @@ import give from "./give"; import set from "./set"; import take from "./take"; import transfer from "./transfer"; -import drop from "./drop"; +import giveaway from "./giveaway"; -export default { give, set, take, transfer, drop }; +export default { give, set, take, transfer, giveaway }; diff --git a/src/commands/manage/modules/credits/modules/set/index.ts b/src/plugins/commands/manage/modules/credits/modules/set/index.ts similarity index 95% rename from src/commands/manage/modules/credits/modules/set/index.ts rename to src/plugins/commands/manage/modules/credits/modules/set/index.ts index a8e1771..a3be389 100644 --- a/src/commands/manage/modules/credits/modules/set/index.ts +++ b/src/plugins/commands/manage/modules/credits/modules/set/index.ts @@ -2,15 +2,15 @@ import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; // Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; // Handlers -import logger from "../../../../../../logger"; +import logger from "../../../../../../../logger"; // Helpers // Models -import fetchUser from "../../../../../../helpers/fetchUser"; +import fetchUser from "../../../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -39,7 +39,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, guild } = interaction; diff --git a/src/commands/manage/modules/credits/modules/take/index.ts b/src/plugins/commands/manage/modules/credits/modules/take/index.ts similarity index 94% rename from src/commands/manage/modules/credits/modules/take/index.ts rename to src/plugins/commands/manage/modules/credits/modules/take/index.ts index 5cf794a..fd3076b 100644 --- a/src/commands/manage/modules/credits/modules/take/index.ts +++ b/src/plugins/commands/manage/modules/credits/modules/take/index.ts @@ -2,16 +2,16 @@ import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; // Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; // Handlers -import logger from "../../../../../../logger"; +import logger from "../../../../../../../logger"; // Helpers -import pluralize from "../../../../../../helpers/pluralize"; +import pluralize from "../../../../../../../helpers/pluralize"; // Models -import fetchUser from "../../../../../../helpers/fetchUser"; +import fetchUser from "../../../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -40,7 +40,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); // Destructure const { guild, options } = interaction; diff --git a/src/commands/manage/modules/credits/modules/transfer/index.ts b/src/plugins/commands/manage/modules/credits/modules/transfer/index.ts similarity index 97% rename from src/commands/manage/modules/credits/modules/transfer/index.ts rename to src/plugins/commands/manage/modules/credits/modules/transfer/index.ts index 011e603..dca0091 100644 --- a/src/commands/manage/modules/credits/modules/transfer/index.ts +++ b/src/plugins/commands/manage/modules/credits/modules/transfer/index.ts @@ -4,13 +4,13 @@ import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; import mongoose from "mongoose"; // Configurations -import getEmbedConfig from "../../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; // Handlers -import logger from "../../../../../../logger"; +import logger from "../../../../../../../logger"; // Models -import fetchUser from "../../../../../../helpers/fetchUser"; +import fetchUser from "../../../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -45,7 +45,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); // Destructure member const { guild, options } = interaction; diff --git a/src/commands/manage/modules/index.ts b/src/plugins/commands/manage/modules/index.ts similarity index 100% rename from src/commands/manage/modules/index.ts rename to src/plugins/commands/manage/modules/index.ts diff --git a/src/commands/profile/index.ts b/src/plugins/commands/profile/index.ts similarity index 94% rename from src/commands/profile/index.ts rename to src/plugins/commands/profile/index.ts index 2039179..68cdc73 100644 --- a/src/commands/profile/index.ts +++ b/src/plugins/commands/profile/index.ts @@ -6,7 +6,7 @@ import { CommandInteraction } from "discord.js"; import modules from "../../commands/profile/modules"; // Handlers -import logger from "../../logger"; +import logger from "../../../logger"; export const moduleData = modules; diff --git a/src/commands/profile/modules/index.ts b/src/plugins/commands/profile/modules/index.ts similarity index 100% rename from src/commands/profile/modules/index.ts rename to src/plugins/commands/profile/modules/index.ts diff --git a/src/commands/profile/modules/view.ts b/src/plugins/commands/profile/modules/view/index.ts similarity index 92% rename from src/commands/profile/modules/view.ts rename to src/plugins/commands/profile/modules/view/index.ts index 5a59fd3..4fbd5c6 100644 --- a/src/commands/profile/modules/view.ts +++ b/src/plugins/commands/profile/modules/view/index.ts @@ -2,12 +2,12 @@ import { CommandInteraction } from "discord.js"; // Configurations -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; // Models -import fetchUser from "../../../helpers/fetchUser"; +import fetchUser from "../../../../../helpers/fetchUser"; -import logger from "../../../logger"; +import logger from "../../../../../logger"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -24,7 +24,6 @@ export default { }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { successColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); // Destructure diff --git a/src/commands/reputation/index.ts b/src/plugins/commands/reputation/index.ts similarity index 71% rename from src/commands/reputation/index.ts rename to src/plugins/commands/reputation/index.ts index b0812d4..ae1876e 100644 --- a/src/commands/reputation/index.ts +++ b/src/plugins/commands/reputation/index.ts @@ -6,7 +6,7 @@ import { CommandInteraction } from "discord.js"; import modules from "./modules"; // Handlers -import logger from "../../logger"; +import logger from "../../../logger"; export const moduleData = modules; @@ -17,13 +17,7 @@ export const builder = new SlashCommandBuilder() .addSubcommand(modules.give.builder); export const execute = async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options?.getSubcommand() === "give") { - logger?.silly(`Executing give subcommand`); - + if (interaction.options.getSubcommand() === "give") { await modules.give.execute(interaction); } - - logger?.silly(`No subcommand found`); }; diff --git a/src/plugins/commands/reputation/modules/give/components/noSelfReputation.ts b/src/plugins/commands/reputation/modules/give/components/noSelfReputation.ts new file mode 100644 index 0000000..f6a6778 --- /dev/null +++ b/src/plugins/commands/reputation/modules/give/components/noSelfReputation.ts @@ -0,0 +1,6 @@ +import { User } from "discord.js"; +export default async (to: User | null, from: User | null) => { + if (from?.id === to?.id) { + throw new Error("You cannot give reputation to yourself."); + } +}; diff --git a/src/plugins/commands/reputation/modules/give/index.ts b/src/plugins/commands/reputation/modules/give/index.ts new file mode 100644 index 0000000..c899ecb --- /dev/null +++ b/src/plugins/commands/reputation/modules/give/index.ts @@ -0,0 +1,87 @@ +import { CommandInteraction } from "discord.js"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; +import { timeout } from "../../../../../config/reputation"; +import logger from "../../../../../logger"; +import fetchUser from "../../../../../helpers/fetchUser"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import * as cooldown from "../../../../../helpers/cooldown"; +import noSelfReputation from "./components/noSelfReputation"; + +export default { + metadata: { guildOnly: true, ephemeral: true }, + + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("give") + .setDescription("Give reputation to a user") + .addUserOption((option) => + option + .setName("target") + .setDescription("The user you want to repute.") + .setRequired(true) + ) + .addStringOption((option) => + option + .setName("type") + .setDescription("What type of reputation you want to repute") + .setRequired(true) + .addChoices( + { name: "Positive", value: "positive" }, + { + name: "Negative", + value: "negative", + } + ) + ); + }, + execute: async (interaction: CommandInteraction) => { + const { options, user, guild } = interaction; + + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(guild); // Destructure + + const optionTarget = options?.getUser("target"); + const optionType = options?.getString("type"); + + if (!guild) throw new Error("Guild is undefined"); + + const userObj = await fetchUser(user, guild); + if (!userObj) throw new Error("User is undefined"); + + // Pre-checks + await noSelfReputation(optionTarget, user); + + // Check if user is on cooldown otherwise create one + await cooldown.interaction(interaction, timeout); + + switch (optionType) { + case "positive": + userObj.reputation += 1; + break; + case "negative": + userObj.reputation += 1; + break; + default: + throw new Error("Invalid reputation type"); + } + + await userObj.save().then(async () => { + logger.silly(`User reputation has been updated`); + + await interaction.editReply({ + embeds: [ + { + title: "[:loudspeaker:] Give", + description: `You have given a ${optionType} repute to ${optionTarget}`, + timestamp: new Date(), + color: successColor, + footer: { + iconURL: footerIcon, + text: footerText, + }, + }, + ], + }); + }); + }, +}; diff --git a/src/commands/reputation/modules/index.ts b/src/plugins/commands/reputation/modules/index.ts similarity index 100% rename from src/commands/reputation/modules/index.ts rename to src/plugins/commands/reputation/modules/index.ts diff --git a/src/commands/shop/index.ts b/src/plugins/commands/shop/index.ts similarity index 74% rename from src/commands/shop/index.ts rename to src/plugins/commands/shop/index.ts index 2ef6f65..697c94e 100644 --- a/src/commands/shop/index.ts +++ b/src/plugins/commands/shop/index.ts @@ -6,7 +6,7 @@ import { CommandInteraction } from "discord.js"; import modules from "./modules"; // Handlers -import logger from "../../logger"; +import logger from "../../../logger"; export const moduleData = modules; @@ -14,16 +14,16 @@ export const moduleData = modules; export const builder = new SlashCommandBuilder() .setName("shop") .setDescription("Shop for credits and custom roles.") - .addSubcommand(modules.pterodactyl.builder) + .addSubcommand(modules.cpgg.builder) .addSubcommandGroup(modules.roles.builder); export const execute = async (interaction: CommandInteraction) => { const { options } = interaction; - if (options?.getSubcommand() === "pterodactyl") { - logger.silly(`Executing pterodactyl subcommand`); + if (options?.getSubcommand() === "cpgg") { + logger.silly(`Executing cpgg subcommand`); - return modules.pterodactyl.execute(interaction); + return modules.cpgg.execute(interaction); } if (options?.getSubcommandGroup() === "roles") { diff --git a/src/commands/shop/modules/pterodactyl.ts b/src/plugins/commands/shop/modules/cpgg/index.ts similarity index 65% rename from src/commands/shop/modules/pterodactyl.ts rename to src/plugins/commands/shop/modules/cpgg/index.ts index daa698d..aeee472 100644 --- a/src/commands/shop/modules/pterodactyl.ts +++ b/src/plugins/commands/shop/modules/cpgg/index.ts @@ -1,25 +1,30 @@ -import { CommandInteraction } from "discord.js"; +import { + CommandInteraction, + MessageActionRow, + MessageButton, +} from "discord.js"; import { v4 as uuidv4 } from "uuid"; import axios from "axios"; -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; -import logger from "../../../logger"; -import encryption from "../../../handlers/encryption"; +import logger from "../../../../../logger"; +import encryption from "../../../../../handlers/encryption"; -import pluralize from "../../../helpers/pluralize"; +import pluralize from "../../../../../helpers/pluralize"; -import apiSchema from "../../../database/schemas/api"; -import fetchUser from "../../../helpers/fetchUser"; +import apiSchema from "../../../../../models/api"; +import fetchUser from "../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { message } from "../../../../../helpers/cooldown/index"; export default { metadata: { guildOnly: true, ephemeral: true }, builder: (command: SlashCommandSubcommandBuilder) => { return command - .setName("pterodactyl") - .setDescription("Buy pterodactyl power.") + .setName("cpgg") + .setDescription("Buy cpgg power.") .addIntegerOption((option) => option .setName("amount") @@ -27,7 +32,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, guild, user, client } = interaction; @@ -71,8 +75,8 @@ export default { return interaction?.editReply({ embeds: [ { - title: ":shopping_cart: Shop [Pterodactyl]", - description: `You **can't** withdraw for __Pterodactyl__ below **100**.`, + title: "[:shopping_cart:] CPGG", + description: `You **can't** withdraw for __CPGG__ below **100**.`, color: errorColor, fields: [ { @@ -96,9 +100,9 @@ export default { return interaction?.editReply({ embeds: [ { - title: ":shopping_cart: Shop [Pterodactyl]", + title: "[:shopping_cart:] CPGG", description: - "You **can't** withdraw for __Pterodactyl__ above **1.000.000**.", + "You **can't** withdraw for __CPGG__ above **1.000.000**.", color: errorColor, fields: [ { @@ -122,7 +126,7 @@ export default { return interaction?.editReply({ embeds: [ { - title: ":shopping_cart: Shop [Pterodactyl]", + title: "[:shopping_cart:] CPGG", description: `You have **insufficient** credits.`, color: errorColor, fields: [ @@ -150,13 +154,21 @@ export default { if (!apiCredentials) return; const api = axios?.create({ - baseURL: apiCredentials.url, + baseURL: `${apiCredentials.url}/api/`, headers: { Authorization: `Bearer ${encryption.decrypt(apiCredentials.token)}`, }, }); - const shopUrl = apiCredentials?.url?.replace("/api", "/store"); + const shopUrl = `${apiCredentials?.url}/store`; + + const buttons = new MessageActionRow().addComponents( + new MessageButton() + .setLabel("Redeem it here") + .setStyle("LINK") + .setEmoji("🏦") + .setURL(`${shopUrl}?voucher=${code}`) + ); await api @@ -178,43 +190,47 @@ export default { ?.then(async () => { logger?.silly(`Successfully saved new credits.`); - await dmUser?.send({ - embeds: [ - { - title: ":shopping_cart: Shop [Pterodactyl]", - description: `Redeem this voucher [here](${shopUrl})!`, - fields: [ - { name: "Code", value: `${code}`, inline: true }, + if (!interaction.guild) throw new Error("Guild is undefined"); + + await dmUser + ?.send({ + embeds: [ + { + title: "[:shopping_cart:] CPGG", + description: `This voucher comes from **${interaction.guild.name}**.`, + fields: [ + { + name: "💶 Credits", + value: `${optionAmount || userDB?.credits}`, + inline: true, + }, + ], + color: successColor, + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, + }, + ], + components: [buttons], + }) + .then(async (msg) => { + return interaction?.editReply({ + embeds: [ { - name: "Credits", - value: `${optionAmount || userDB?.credits}`, - inline: true, + title: "[:shopping_cart:] CPGG", + description: `I have sent you the code in [DM](${msg.url})!`, + color: successColor, + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, }, ], - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - - return interaction?.editReply({ - embeds: [ - { - title: ":shopping_cart: Shop [Pterodactyl]", - description: "I have sent you the code in DM!", - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); + }); + }); }) .catch(async (error) => { @@ -223,7 +239,7 @@ export default { return interaction?.editReply({ embeds: [ { - title: ":shopping_cart: Shop [Pterodactyl]", + title: "[:shopping_cart:] CPGG", description: "Something went wrong.", color: errorColor, timestamp: new Date(), @@ -243,7 +259,7 @@ export default { return interaction?.editReply({ embeds: [ { - title: ":shopping_cart: Shop [Pterodactyl]", + title: "[:shopping_cart:] CPGG", description: "Something went wrong.", color: errorColor, timestamp: new Date(), diff --git a/src/plugins/commands/shop/modules/index.ts b/src/plugins/commands/shop/modules/index.ts new file mode 100644 index 0000000..c3df89d --- /dev/null +++ b/src/plugins/commands/shop/modules/index.ts @@ -0,0 +1,4 @@ +import cpgg from "./cpgg"; +import * as roles from "./roles"; + +export default { cpgg, roles }; diff --git a/src/commands/shop/modules/roles/index.ts b/src/plugins/commands/shop/modules/roles/index.ts similarity index 89% rename from src/commands/shop/modules/roles/index.ts rename to src/plugins/commands/shop/modules/roles/index.ts index bdec977..f16a962 100644 --- a/src/commands/shop/modules/roles/index.ts +++ b/src/plugins/commands/shop/modules/roles/index.ts @@ -3,14 +3,14 @@ import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; // Handlers -import logger from "../../../../logger"; +import logger from "../../../../../logger"; -import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; // Modules import modules from "./modules"; -import guildSchema from "../../../../database/schemas/guild"; +import guildSchema from "../../../../../models/guild"; export const moduleData = modules; diff --git a/src/commands/shop/modules/roles/modules/buy.ts b/src/plugins/commands/shop/modules/roles/modules/buy/index.ts similarity index 89% rename from src/commands/shop/modules/roles/modules/buy.ts rename to src/plugins/commands/shop/modules/roles/modules/buy/index.ts index b92ba7a..23f91dc 100644 --- a/src/commands/shop/modules/roles/modules/buy.ts +++ b/src/plugins/commands/shop/modules/roles/modules/buy/index.ts @@ -6,17 +6,17 @@ import { } from "discord.js"; // Configurations -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; // Models -import shopRolesSchema from "../../../../../database/schemas/shopRole"; -import guildSchema from "../../../../../database/schemas/guild"; +import shopRolesSchema from "../../../../../../../models/shopRole"; +import guildSchema from "../../../../../../../models/guild"; -import logger from "../../../../../logger"; +import logger from "../../../../../../../logger"; // Helpers -import pluralize from "../../../../../helpers/pluralize"; -import fetchUser from "../../../../../helpers/fetchUser"; +import pluralize from "../../../../../../../helpers/pluralize"; +import fetchUser from "../../../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -41,7 +41,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, guild, user, member } = interaction; diff --git a/src/commands/shop/modules/roles/modules/cancel.ts b/src/plugins/commands/shop/modules/roles/modules/cancel/index.ts similarity index 88% rename from src/commands/shop/modules/roles/modules/cancel.ts rename to src/plugins/commands/shop/modules/roles/modules/cancel/index.ts index cf16d95..5ff483e 100644 --- a/src/commands/shop/modules/roles/modules/cancel.ts +++ b/src/plugins/commands/shop/modules/roles/modules/cancel/index.ts @@ -2,16 +2,16 @@ import { CommandInteraction, GuildMemberRoleManager } from "discord.js"; // Configurations -import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; // Models -import shopRolesSchema from "../../../../../database/schemas/shopRole"; +import shopRolesSchema from "../../../../../../../models/shopRole"; -import logger from "../../../../../logger"; +import logger from "../../../../../../../logger"; // Helpers -import pluralize from "../../../../../helpers/pluralize"; -import fetchUser from "../../../../../helpers/fetchUser"; +import pluralize from "../../../../../../../helpers/pluralize"; +import fetchUser from "../../../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -30,7 +30,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const { options, guild, user, member } = interaction; diff --git a/src/commands/shop/modules/roles/modules/index.ts b/src/plugins/commands/shop/modules/roles/modules/index.ts similarity index 100% rename from src/commands/shop/modules/roles/modules/index.ts rename to src/plugins/commands/shop/modules/roles/modules/index.ts diff --git a/src/commands/utility/index.ts b/src/plugins/commands/utility/index.ts similarity index 73% rename from src/commands/utility/index.ts rename to src/plugins/commands/utility/index.ts index 6fb52db..c97ef47 100644 --- a/src/commands/utility/index.ts +++ b/src/plugins/commands/utility/index.ts @@ -1,16 +1,9 @@ -// Dependencies import { SlashCommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; -// Modules -import modules from "../../commands/utility/modules"; - -// Handlers -import logger from "../../logger"; - +import modules from "./modules"; export const moduleData = modules; -// Function export const builder = new SlashCommandBuilder() .setName("utility") .setDescription("Common utility.") @@ -21,9 +14,7 @@ export const builder = new SlashCommandBuilder() .addSubcommand(modules.avatar.builder); export const execute = async (interaction: CommandInteraction) => { - const { options } = interaction; - - switch (options.getSubcommand()) { + switch (interaction.options.getSubcommand()) { case "lookup": return modules.lookup.execute(interaction); case "about": @@ -33,6 +24,8 @@ export const execute = async (interaction: CommandInteraction) => { case "avatar": return modules.avatar.execute(interaction); default: - logger.error(`Unknown subcommand ${options.getSubcommand()}`); + throw new Error( + `Unknown subcommand: ${interaction.options.getSubcommand()}` + ); } }; diff --git a/src/plugins/commands/utility/modules/about/index.ts b/src/plugins/commands/utility/modules/about/index.ts new file mode 100644 index 0000000..f89d74d --- /dev/null +++ b/src/plugins/commands/utility/modules/about/index.ts @@ -0,0 +1,71 @@ +// Dependencies +import { + CommandInteraction, + MessageActionRow, + MessageButton, +} from "discord.js"; + +// Configurations +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; + +import { hosterName, hosterUrl } from "../../../../../config/other"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + metadata: { guildOnly: false, ephemeral: false }, + + builder: (command: SlashCommandSubcommandBuilder) => { + return command.setName("about").setDescription("About this bot!)"); + }, + execute: async (interaction: CommandInteraction) => { + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const buttons = new MessageActionRow().addComponents( + new MessageButton() + .setLabel("Source Code") + .setStyle("LINK") + .setEmoji("📄") + .setURL("https://github.com/ZynerOrg/xyter"), + new MessageButton() + .setLabel("Website") + .setStyle("LINK") + .setEmoji("🌐") + .setURL("https://zyner.org"), + new MessageButton() + .setLabel("Get Help") + .setStyle("LINK") + .setEmoji("💬") + .setURL("https://discord.zyner.org"), + new MessageButton() + .setLabel(`Hosted by ${hosterName}`) + .setStyle("LINK") + .setEmoji("⚒️") + .setURL(`${hosterUrl}`) + ); + + const interactionEmbed = { + title: "[:tools:] About", + description: ` + **Xyter**'s goal is to provide a __privacy-friendly__ discord bot. + We created **Xyter** to **replace the mess** of having a dozen or so bots in __your__ community. + On top of this, you can also see our **source code** for **security** and **privacy** issues. + As well as making your own **fork** of the bot, you can also get **help** from our community. + + Developed with ❤️ by **Zyner**, a non-profit project by teens. + `, + color: successColor, + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, + }; + await interaction.editReply({ + embeds: [interactionEmbed], + components: [buttons], + }); + }, +}; diff --git a/src/commands/utility/modules/avatar.ts b/src/plugins/commands/utility/modules/avatar/index.ts similarity index 91% rename from src/commands/utility/modules/avatar.ts rename to src/plugins/commands/utility/modules/avatar/index.ts index 57110cf..24ab6c1 100644 --- a/src/commands/utility/modules/avatar.ts +++ b/src/plugins/commands/utility/modules/avatar/index.ts @@ -1,4 +1,4 @@ -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; @@ -17,7 +17,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { successColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); diff --git a/src/commands/utility/modules/index.ts b/src/plugins/commands/utility/modules/index.ts similarity index 100% rename from src/commands/utility/modules/index.ts rename to src/plugins/commands/utility/modules/index.ts diff --git a/src/commands/utility/modules/lookup.ts b/src/plugins/commands/utility/modules/lookup/index.ts similarity index 95% rename from src/commands/utility/modules/lookup.ts rename to src/plugins/commands/utility/modules/lookup/index.ts index a5825f9..44abe14 100644 --- a/src/commands/utility/modules/lookup.ts +++ b/src/plugins/commands/utility/modules/lookup/index.ts @@ -1,11 +1,11 @@ import axios from "axios"; import { CommandInteraction } from "discord.js"; -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import embedBuilder from "../../../helpers/embedBuilder"; +import embedBuilder from "../../../../../helpers/embedBuilder"; export default { metadata: { guildOnly: false, ephemeral: false }, @@ -24,7 +24,6 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { errorColor, successColor, footerText, footerIcon } = await getEmbedConfig(interaction.guild); const embedTitle = "[:hammer:] Utility (Lookup)"; diff --git a/src/commands/utility/modules/stats.ts b/src/plugins/commands/utility/modules/stats/index.ts similarity index 95% rename from src/commands/utility/modules/stats.ts rename to src/plugins/commands/utility/modules/stats/index.ts index 805b948..a2a8282 100644 --- a/src/commands/utility/modules/stats.ts +++ b/src/plugins/commands/utility/modules/stats/index.ts @@ -1,4 +1,4 @@ -import getEmbedConfig from "../../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; @@ -9,7 +9,6 @@ export default { return command.setName("stats").setDescription("Check bot statistics!)"); }, execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; const { successColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); diff --git a/src/events/guildCreate/index.ts b/src/plugins/events/guildCreate/index.ts similarity index 60% rename from src/events/guildCreate/index.ts rename to src/plugins/events/guildCreate/index.ts index a309884..60d5a3c 100644 --- a/src/events/guildCreate/index.ts +++ b/src/plugins/events/guildCreate/index.ts @@ -1,8 +1,8 @@ import { Guild } from "discord.js"; -import updatePresence from "../../helpers/updatePresence"; -import fetchGuild from "../../helpers/fetchGuild"; -import logger from "../../logger"; -import { IEventOptions } from "../../interfaces/EventOptions"; +import updatePresence from "../../../helpers/updatePresence"; +import fetchGuild from "../../../helpers/fetchGuild"; +import logger from "../../../logger"; +import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", diff --git a/src/events/guildDelete/index.ts b/src/plugins/events/guildDelete/index.ts similarity index 64% rename from src/events/guildDelete/index.ts rename to src/plugins/events/guildDelete/index.ts index be090b8..146351f 100644 --- a/src/events/guildDelete/index.ts +++ b/src/plugins/events/guildDelete/index.ts @@ -2,10 +2,10 @@ import { Guild } from "discord.js"; // Dependencies -import updatePresence from "../../helpers/updatePresence"; -import dropGuild from "../../helpers/dropGuild"; -import logger from "../../logger"; -import { IEventOptions } from "../../interfaces/EventOptions"; +import updatePresence from "../../../helpers/updatePresence"; +import dropGuild from "../../../helpers/dropGuild"; +import logger from "../../../logger"; +import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", diff --git a/src/plugins/events/guildMemberAdd/audits.ts b/src/plugins/events/guildMemberAdd/audits.ts new file mode 100644 index 0000000..89c0384 --- /dev/null +++ b/src/plugins/events/guildMemberAdd/audits.ts @@ -0,0 +1,62 @@ +import logger from "../../../logger"; +import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; + +import guildSchema from "../../../models/guild"; + +import getEmbedConfig from "../../../helpers/getEmbedConfig"; + +export default { + execute: async (member: GuildMember) => { + const { client, guild } = member; + + const guildData = await guildSchema.findOne({ guildId: member.guild.id }); + if (!guildData) { + throw new Error("Could not find guild"); + } + if (guildData.audits.status !== true) return; + if (!guildData.audits.channelId) { + throw new Error("Channel not found"); + } + + const embedConfig = await getEmbedConfig(guild); + + const channel = client.channels.cache.get(guildData.audits.channelId); + if (channel?.type !== "GUILD_TEXT") { + throw new Error("Channel must be a text channel"); + } + + const embed = new MessageEmbed() + .setTimestamp(new Date()) + .setAuthor({ + name: "Member Joined", + iconURL: client.user?.displayAvatarURL(), + }) + .setFooter({ + text: embedConfig.footerText, + iconURL: embedConfig.footerIcon, + }); + + await channel + .send({ + embeds: [ + embed + .setColor(embedConfig.successColor) + .setDescription(`${member.user} - (${member.user.tag})`) + .addFields([ + { + name: "Account Age", + value: `${member.user.createdAt}`, + }, + ]), + ], + }) + .then(async () => { + logger.debug( + `Audit log sent for event guildMemberAdd in guild ${member.guild.name} (${member.guild.id})` + ); + }) + .catch(async () => { + throw new Error("Audit log failed to send"); + }); + }, +}; diff --git a/src/events/guildMemberAdd/index.ts b/src/plugins/events/guildMemberAdd/index.ts similarity index 59% rename from src/events/guildMemberAdd/index.ts rename to src/plugins/events/guildMemberAdd/index.ts index 0dff9bf..8310579 100644 --- a/src/events/guildMemberAdd/index.ts +++ b/src/plugins/events/guildMemberAdd/index.ts @@ -2,12 +2,14 @@ import { GuildMember } from "discord.js"; // Dependencies -import updatePresence from "../../helpers/updatePresence"; -import fetchUser from "../../helpers/fetchUser"; -import logger from "../../logger"; -import joinMessage from "../guildMemberAdd/joinMessage"; -import audits from "../guildMemberAdd/audits"; -import { IEventOptions } from "../../interfaces/EventOptions"; +import updatePresence from "../../../helpers/updatePresence"; +import fetchUser from "../../../helpers/fetchUser"; +import logger from "../../../logger"; + +import joinMessage from "./joinMessage"; +import audits from "./audits"; + +import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", @@ -16,7 +18,7 @@ export const options: IEventOptions = { export const execute = async (member: GuildMember) => { const { client, user, guild } = member; - logger?.silly( + logger.silly( `New member: ${user.tag} (${user.id}) added to guild: ${guild.name} (${guild.id})` ); diff --git a/src/events/guildMemberAdd/joinMessage.ts b/src/plugins/events/guildMemberAdd/joinMessage.ts similarity index 91% rename from src/events/guildMemberAdd/joinMessage.ts rename to src/plugins/events/guildMemberAdd/joinMessage.ts index db0a745..47f9043 100644 --- a/src/events/guildMemberAdd/joinMessage.ts +++ b/src/plugins/events/guildMemberAdd/joinMessage.ts @@ -1,8 +1,8 @@ import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; -import guildSchema from "../../database/schemas/guild"; +import guildSchema from "../../../models/guild"; -import getEmbedConfig from "../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../helpers/getEmbedConfig"; export default { execute: async (member: GuildMember) => { diff --git a/src/plugins/events/guildMemberRemove/audits.ts b/src/plugins/events/guildMemberRemove/audits.ts new file mode 100644 index 0000000..a940ca3 --- /dev/null +++ b/src/plugins/events/guildMemberRemove/audits.ts @@ -0,0 +1,62 @@ +import logger from "../../../logger"; +import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; + +import guildSchema from "../../../models/guild"; + +import getEmbedConfig from "../../../helpers/getEmbedConfig"; + +export default { + execute: async (member: GuildMember) => { + const { client, guild } = member; + + const guildData = await guildSchema.findOne({ guildId: member.guild.id }); + if (!guildData) { + throw new Error("Could not find guild"); + } + if (guildData.audits.status !== true) return; + if (!guildData.audits.channelId) { + throw new Error("Channel not found"); + } + + const embedConfig = await getEmbedConfig(guild); + + const channel = client.channels.cache.get(guildData.audits.channelId); + if (channel?.type !== "GUILD_TEXT") { + throw new Error("Channel must be a text channel"); + } + + const embed = new MessageEmbed() + .setTimestamp(new Date()) + .setAuthor({ + name: "Member Left", + iconURL: client.user?.displayAvatarURL(), + }) + .setFooter({ + text: embedConfig.footerText, + iconURL: embedConfig.footerIcon, + }); + + channel + .send({ + embeds: [ + embed + .setColor(embedConfig.errorColor) + .setDescription(`${member.user} - (${member.user.tag})`) + .addFields([ + { + name: "Account Age", + value: `${member.user.createdAt}`, + }, + ]), + ], + }) + .then(async () => { + logger.debug( + `Audit log sent for event guildMemberRemove in guild ${member.guild.name} (${member.guild.id})` + ); + }) + .catch(async () => { + throw new Error("Audit log failed to send"); + }); + }, +}; diff --git a/src/events/guildMemberRemove/index.ts b/src/plugins/events/guildMemberRemove/index.ts similarity index 72% rename from src/events/guildMemberRemove/index.ts rename to src/plugins/events/guildMemberRemove/index.ts index 502aad4..b900a12 100644 --- a/src/events/guildMemberRemove/index.ts +++ b/src/plugins/events/guildMemberRemove/index.ts @@ -2,12 +2,12 @@ import { GuildMember } from "discord.js"; // Dependencies -import updatePresence from "../../helpers/updatePresence"; -import dropUser from "../../helpers/dropUser"; -import logger from "../../logger"; +import updatePresence from "../../../helpers/updatePresence"; +import dropUser from "../../../helpers/dropUser"; +import logger from "../../../logger"; import leaveMessage from "./leaveMessage"; import audits from "./audits"; -import { IEventOptions } from "../../interfaces/EventOptions"; +import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", diff --git a/src/events/guildMemberRemove/leaveMessage.ts b/src/plugins/events/guildMemberRemove/leaveMessage.ts similarity index 91% rename from src/events/guildMemberRemove/leaveMessage.ts rename to src/plugins/events/guildMemberRemove/leaveMessage.ts index 427866b..daae364 100644 --- a/src/events/guildMemberRemove/leaveMessage.ts +++ b/src/plugins/events/guildMemberRemove/leaveMessage.ts @@ -1,8 +1,8 @@ import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; -import guildSchema from "../../database/schemas/guild"; +import guildSchema from "../../../models/guild"; -import getEmbedConfig from "../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../helpers/getEmbedConfig"; export default { execute: async (member: GuildMember) => { diff --git a/src/events/interactionCreate/audits.ts b/src/plugins/events/interactionCreate/audits.ts similarity index 84% rename from src/events/interactionCreate/audits.ts rename to src/plugins/events/interactionCreate/audits.ts index 3a311c5..7c71f80 100644 --- a/src/events/interactionCreate/audits.ts +++ b/src/plugins/events/interactionCreate/audits.ts @@ -1,9 +1,9 @@ -import logger from "../../logger"; +import logger from "../../../logger"; import { Interaction, MessageEmbed, TextChannel } from "discord.js"; -import guildSchema from "../../database/schemas/guild"; +import guildSchema from "../../../models/guild"; -import getEmbedConfig from "../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../helpers/getEmbedConfig"; export default { execute: async (interaction: Interaction) => { @@ -38,10 +38,12 @@ export default { .setDescription( ` **Interaction created by** ${interaction.user.username} **in** ${interaction.channel} + ㅤ**Interaction ID**: ${interaction.id} + ㅤ**Type**: ${interaction.type} + ㅤ**User ID**: ${interaction.user.id} ` ) .setThumbnail(interaction.user.displayAvatarURL()) - .addFields([{ name: "Event", value: "interactionCreate" }]) .setTimestamp() .setFooter({ text: footerText, diff --git a/src/plugins/events/interactionCreate/components/checks.ts b/src/plugins/events/interactionCreate/components/checks.ts new file mode 100644 index 0000000..006a14f --- /dev/null +++ b/src/plugins/events/interactionCreate/components/checks.ts @@ -0,0 +1,51 @@ +import { CommandInteraction, MessageEmbed } from "discord.js"; +import * as cooldown from "../../../../helpers/cooldown"; +import logger from "../../../../logger"; + +export default async ( + interaction: CommandInteraction, + metadata: any, + embedConfig: any +) => { + if ( + metadata.permissions && + metadata.guildOnly && + !interaction.memberPermissions?.has(metadata.permissions) + ) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:x:] Permission") + .setDescription(`You do not have the permission to manage the bot.`) + .setTimestamp(new Date()) + .setColor(embedConfig.errorColor) + .setFooter({ + text: embedConfig.footerText, + iconURL: embedConfig.footerIcon, + }), + ], + }); + } + + logger.info(metadata); + + if (metadata.cooldown) { + await cooldown + .interaction(interaction, metadata.cooldown) + .catch(async (error) => { + throw new Error("Cooldown error: " + error); + }); + } + + if (metadata.guildOnly) { + if (!interaction.guild) { + throw new Error("This command is guild only."); + } + } + + if (metadata.dmOnly) { + if (interaction.guild) { + throw new Error("This command is DM only."); + } + } +}; diff --git a/src/events/interactionCreate/components/isCommand.ts b/src/plugins/events/interactionCreate/components/isButton.ts similarity index 61% rename from src/events/interactionCreate/components/isCommand.ts rename to src/plugins/events/interactionCreate/components/isButton.ts index 3addf8b..eaf1605 100644 --- a/src/events/interactionCreate/components/isCommand.ts +++ b/src/plugins/events/interactionCreate/components/isButton.ts @@ -1,33 +1,49 @@ // Dependencies import { CommandInteraction, MessageEmbed } from "discord.js"; -import logger from "../../../logger"; +import logger from "../../../../logger"; -import deferReply from "../../../helpers/deferReply"; -import getEmbedConfig from "../../../helpers/getEmbedConfig"; -import getCommandMetadata from "../../../helpers/getCommandMetadata"; -import capitalizeFirstLetter from "../../../helpers/capitalizeFirstLetter"; +import deferReply from "../../../../helpers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import capitalizeFirstLetter from "../../../../helpers/capitalizeFirstLetter"; +import * as cooldown from "../../../../helpers/cooldown"; export default async (interaction: CommandInteraction) => { - if (!interaction.isCommand()) return; - if (interaction.guild == null) return; + if (!interaction.isButton()) return; const { errorColor, footerText, footerIcon } = await getEmbedConfig( interaction.guild ); - const { client, guild, commandName, user, memberPermissions } = interaction; + const { guild, customId, user, memberPermissions } = interaction; - const currentCommand = client.commands.get(commandName); + const currentButton = await import(`../../../buttons/${customId}`); - if (currentCommand == null) { - logger.silly(`Command ${commandName} not found`); + if (currentButton == null) { + logger.silly(`Button ${customId} not found`); } - const metadata = await getCommandMetadata(interaction, currentCommand); + const metadata = currentButton.metadata; await deferReply(interaction, metadata.ephemeral || false); + if (metadata.guildOnly) { + if (!guild) { + logger.debug(`Guild is null`); + + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:x:] Permission") + .setDescription("This command is only available for guild") + .setColor(errorColor) + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + } + if ( metadata.permissions && metadata.guildOnly && @@ -45,22 +61,6 @@ export default async (interaction: CommandInteraction) => { }); } - if (metadata.guildOnly) { - if (!guild) { - logger.debug(`Guild is null`); - - return interaction.editReply({ - embeds: [ - new MessageEmbed() - .setDescription("This command is only available for guild") - .setColor(errorColor) - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - } - if (metadata.dmOnly) { if (guild) { logger.silly(`Guild exist`); @@ -68,6 +68,7 @@ export default async (interaction: CommandInteraction) => { return interaction.editReply({ embeds: [ new MessageEmbed() + .setTitle("[:x:] Permission") .setDescription("This command is only available in DM.") .setColor(errorColor) .setTimestamp(new Date()) @@ -77,15 +78,32 @@ export default async (interaction: CommandInteraction) => { } } - await currentCommand + if (metadata.cooldown) { + await cooldown + .interaction(interaction, metadata.cooldown) + .catch(async (error) => { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:x:] Permission") + .setDescription(`${error}`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + } + + await currentButton .execute(interaction) .then(async () => { return logger?.silly( - `Command: ${commandName} executed in guild: ${guild?.name} (${guild?.id}) by user: ${user?.tag} (${user?.id})` + `Button: ${customId} executed in guild: ${guild?.name} (${guild?.id}) by user: ${user?.tag} (${user?.id})` ); }) .catch(async (error: string) => { - logger?.error(`${error}`); + logger?.debug(`INTERACTION BUTTON CATCH: ${error}`); return interaction.editReply({ embeds: [ diff --git a/src/plugins/events/interactionCreate/components/isCommand.ts b/src/plugins/events/interactionCreate/components/isCommand.ts new file mode 100644 index 0000000..3a14ad6 --- /dev/null +++ b/src/plugins/events/interactionCreate/components/isCommand.ts @@ -0,0 +1,48 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +import logger from "../../../../logger"; + +import deferReply from "../../../../helpers/deferReply"; +import getEmbedConfig from "../../../../helpers/getEmbedConfig"; +import getCommandMetadata from "../../../../helpers/getCommandMetadata"; +import capitalizeFirstLetter from "../../../../helpers/capitalizeFirstLetter"; +import * as cooldown from "../../../../helpers/cooldown"; + +export default async (interaction: CommandInteraction) => { + if (!interaction.isCommand()) return; + + const { errorColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const { client, guild, commandName, user, memberPermissions } = interaction; + + const currentCommand = client.commands.get(commandName); + + if (currentCommand == null) { + logger.silly(`Command ${commandName} not found`); + } + + const metadata = await getCommandMetadata(interaction, currentCommand); + + await deferReply(interaction, metadata.ephemeral || false); + + if (metadata.guildOnly && !guild) + throw new Error("This command is guild only."); + + if ( + metadata.permissions && + metadata.guildOnly && + !memberPermissions?.has(metadata.permissions) + ) + throw new Error("You don't have the required permissions"); + + if (metadata.dmOnly && guild) + throw new Error("This command is only available in DM"); + + if (metadata.cooldown) + await cooldown.interaction(interaction, metadata.cooldown); + + await currentCommand.execute(interaction); +}; diff --git a/src/plugins/events/interactionCreate/index.ts b/src/plugins/events/interactionCreate/index.ts new file mode 100644 index 0000000..f7365ec --- /dev/null +++ b/src/plugins/events/interactionCreate/index.ts @@ -0,0 +1,51 @@ +// 3rd party dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +// Dependencies +import isCommand from "../../events/interactionCreate/components/isCommand"; +import isButton from "../../events/interactionCreate/components/isButton"; +import logger from "../../../logger"; +import audits from "./audits"; +import { IEventOptions } from "../../../interfaces/EventOptions"; +import capitalizeFirstLetter from "../../../helpers/capitalizeFirstLetter"; +import getEmbedConfig from "../../../helpers/getEmbedConfig"; + +export const options: IEventOptions = { + type: "on", +}; + +export const execute = async (interaction: CommandInteraction) => { + const { guild, id } = interaction; + + logger?.silly( + `New interaction: ${id} in guild: ${guild?.name} (${guild?.id})` + ); + + const { errorColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + await audits.execute(interaction); + + try { + await isCommand(interaction); + await isButton(interaction); + } catch (error) { + logger.debug(`${error}`); + + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle( + `[:x:] ${capitalizeFirstLetter( + interaction.options.getSubcommand() + )}` + ) + .setDescription(`${"``"}${error}${"``"}`) + .setColor(errorColor) + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } +}; diff --git a/src/events/messageCreate/index.ts b/src/plugins/events/messageCreate/index.ts similarity index 83% rename from src/events/messageCreate/index.ts rename to src/plugins/events/messageCreate/index.ts index f5beddf..06619f0 100644 --- a/src/events/messageCreate/index.ts +++ b/src/plugins/events/messageCreate/index.ts @@ -1,7 +1,7 @@ import { Message } from "discord.js"; import modules from "../../events/messageCreate/modules"; -import { IEventOptions } from "../../interfaces/EventOptions"; +import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", diff --git a/src/events/messageCreate/modules/counters/index.ts b/src/plugins/events/messageCreate/modules/counters/index.ts similarity index 94% rename from src/events/messageCreate/modules/counters/index.ts rename to src/plugins/events/messageCreate/modules/counters/index.ts index 882fbc6..1a3c7a3 100644 --- a/src/events/messageCreate/modules/counters/index.ts +++ b/src/plugins/events/messageCreate/modules/counters/index.ts @@ -1,7 +1,7 @@ import { Message } from "discord.js"; -import logger from "../../../../logger"; -import counterSchema from "../../../../database/schemas/counter"; +import logger from "../../../../../logger"; +import counterSchema from "../../../../../models/counter"; export default { execute: async (message: Message) => { diff --git a/src/plugins/events/messageCreate/modules/credits/index.ts b/src/plugins/events/messageCreate/modules/credits/index.ts new file mode 100644 index 0000000..24a5ce2 --- /dev/null +++ b/src/plugins/events/messageCreate/modules/credits/index.ts @@ -0,0 +1,46 @@ +import logger from "../../../../../logger"; +import { Message } from "discord.js"; + +import fetchUser from "../../../../../helpers/fetchUser"; +import fetchGuild from "../../../../../helpers/fetchGuild"; + +import * as cooldown from "../../../../../helpers/cooldown"; + +export default { + execute: async (message: Message) => { + const { guild, author, content, channel } = message; + + if (guild == null) return; + if (author.bot) return; + if (channel?.type !== "GUILD_TEXT") return; + + const { id: guildId } = guild; + const { id: userId } = author; + + const guildData = await fetchGuild(guild); + const userData = await fetchUser(author, guild); + + if (content.length < guildData.credits.minimumLength) return; + + await cooldown.message( + message, + guildData.credits.timeout, + "messageCreate-credits" + ); + + userData.credits += guildData.credits.rate; + + await userData + .save() + .then(async () => { + logger.silly( + `User ${userId} in guild ${guildId} has ${userData.credits} credits` + ); + }) + .catch(async (err) => { + logger.error( + `Error saving credits for user ${userId} in guild ${guildId} - ${err}` + ); + }); + }, +}; diff --git a/src/events/messageCreate/modules/index.ts b/src/plugins/events/messageCreate/modules/index.ts similarity index 100% rename from src/events/messageCreate/modules/index.ts rename to src/plugins/events/messageCreate/modules/index.ts diff --git a/src/plugins/events/messageCreate/modules/points/index.ts b/src/plugins/events/messageCreate/modules/points/index.ts new file mode 100644 index 0000000..4a4496b --- /dev/null +++ b/src/plugins/events/messageCreate/modules/points/index.ts @@ -0,0 +1,51 @@ +import logger from "../../../../../logger"; + +import * as cooldown from "../../../../../helpers/cooldown"; + +import fetchUser from "../../../../../helpers/fetchUser"; +import fetchGuild from "../../../../../helpers/fetchGuild"; + +import { Message } from "discord.js"; +export default { + execute: async (message: Message) => { + const { guild, author, content, channel } = message; + + if (guild == null) return; + if (author.bot) return; + if (channel?.type !== "GUILD_TEXT") return; + + const { id: guildId } = guild; + const { id: userId } = author; + + const guildData = await fetchGuild(guild); + const userData = await fetchUser(author, guild); + + if (content.length < guildData.credits.minimumLength) return; + + await cooldown.message( + message, + guildData.credits.timeout, + "messageCreate-points" + ); + + userData.points += guildData.points.rate; + + await userData + .save() + .then(async () => { + logger.silly( + `Successfully saved user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` + ); + }) + .catch(async (err) => { + logger.error( + `Error saving points for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})`, + err + ); + }); + + logger.silly( + `User ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id}) has ${userData.points} points` + ); + }, +}; diff --git a/src/events/messageDelete/audits.ts b/src/plugins/events/messageDelete/audits.ts similarity index 91% rename from src/events/messageDelete/audits.ts rename to src/plugins/events/messageDelete/audits.ts index eb28000..4b5d5a2 100644 --- a/src/events/messageDelete/audits.ts +++ b/src/plugins/events/messageDelete/audits.ts @@ -1,9 +1,9 @@ -import logger from "../../logger"; +import logger from "../../../logger"; import { Message, MessageEmbed, TextChannel } from "discord.js"; -import guildSchema from "../../database/schemas/guild"; +import guildSchema from "../../../models/guild"; -import getEmbedConfig from "../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../helpers/getEmbedConfig"; export default { execute: async (message: Message) => { diff --git a/src/events/messageDelete/index.ts b/src/plugins/events/messageDelete/index.ts similarity index 82% rename from src/events/messageDelete/index.ts rename to src/plugins/events/messageDelete/index.ts index 7279add..3cb7f6e 100644 --- a/src/events/messageDelete/index.ts +++ b/src/plugins/events/messageDelete/index.ts @@ -1,7 +1,7 @@ import { Message } from "discord.js"; import audits from "../../events/messageDelete/audits"; import counter from "./modules/counter"; -import { IEventOptions } from "../../interfaces/EventOptions"; +import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", diff --git a/src/events/messageDelete/modules/counter.ts b/src/plugins/events/messageDelete/modules/counter.ts similarity index 90% rename from src/events/messageDelete/modules/counter.ts rename to src/plugins/events/messageDelete/modules/counter.ts index e488ae4..5681da8 100644 --- a/src/events/messageDelete/modules/counter.ts +++ b/src/plugins/events/messageDelete/modules/counter.ts @@ -2,8 +2,8 @@ import { Message } from "discord.js"; // Models -import counterSchema from "../../../database/schemas/counter"; -import logger from "../../../logger"; +import counterSchema from "../../../../models/counter"; +import logger from "../../../../logger"; export default async (message: Message) => { const { guild, channel, author, content } = message; diff --git a/src/events/messageUpdate/audits.ts b/src/plugins/events/messageUpdate/audits.ts similarity index 92% rename from src/events/messageUpdate/audits.ts rename to src/plugins/events/messageUpdate/audits.ts index 3438666..515a090 100644 --- a/src/events/messageUpdate/audits.ts +++ b/src/plugins/events/messageUpdate/audits.ts @@ -1,10 +1,10 @@ /* eslint-disable no-loops/no-loops */ -import logger from "../../logger"; +import logger from "../../../logger"; import { Message, MessageEmbed, TextChannel } from "discord.js"; -import guildSchema from "../../database/schemas/guild"; +import guildSchema from "../../../models/guild"; -import getEmbedConfig from "../../helpers/getEmbedConfig"; +import getEmbedConfig from "../../../helpers/getEmbedConfig"; export default { execute: async (oldMessage: Message, newMessage: Message) => { diff --git a/src/events/messageUpdate/index.ts b/src/plugins/events/messageUpdate/index.ts similarity index 85% rename from src/events/messageUpdate/index.ts rename to src/plugins/events/messageUpdate/index.ts index 263d53e..9c9bf47 100644 --- a/src/events/messageUpdate/index.ts +++ b/src/plugins/events/messageUpdate/index.ts @@ -1,12 +1,12 @@ // Dependencies import { Message } from "discord.js"; -import logger from "../../logger"; +import logger from "../../../logger"; // Modules import counter from "./modules/counter"; import audits from "./audits"; -import { IEventOptions } from "../../interfaces/EventOptions"; +import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "on", diff --git a/src/events/messageUpdate/modules/counter.ts b/src/plugins/events/messageUpdate/modules/counter.ts similarity index 89% rename from src/events/messageUpdate/modules/counter.ts rename to src/plugins/events/messageUpdate/modules/counter.ts index 8ea7087..03c60b9 100644 --- a/src/events/messageUpdate/modules/counter.ts +++ b/src/plugins/events/messageUpdate/modules/counter.ts @@ -2,8 +2,8 @@ import { Message } from "discord.js"; // Models -import counterSchema from "../../../database/schemas/counter"; -import logger from "../../../logger"; +import counterSchema from "../../../../models/counter"; +import logger from "../../../../logger"; export default async (message: Message) => { const { guild, channel, author, content } = message; diff --git a/src/plugins/events/rateLimit/index.ts b/src/plugins/events/rateLimit/index.ts new file mode 100644 index 0000000..8886c1a --- /dev/null +++ b/src/plugins/events/rateLimit/index.ts @@ -0,0 +1,14 @@ +// Dependencies +import { Client } from "discord.js"; +import logger from "../../../logger"; + +// Helpers +import { IEventOptions } from "../../../interfaces/EventOptions"; + +export const options: IEventOptions = { + type: "on", +}; + +export const execute = async (client: Client) => { + logger.warn("Discord's API client is rate-limited!"); +}; diff --git a/src/events/ready/index.ts b/src/plugins/events/ready/index.ts similarity index 53% rename from src/events/ready/index.ts rename to src/plugins/events/ready/index.ts index bd1e060..125bdc8 100644 --- a/src/events/ready/index.ts +++ b/src/plugins/events/ready/index.ts @@ -1,12 +1,12 @@ // Dependencies import { Client } from "discord.js"; -import logger from "../../logger"; +import logger from "../../../logger"; // Helpers -import updatePresence from "../../helpers/updatePresence"; -import deployCommands from "../../handlers/deployCommands"; -import devMode from "../../handlers/devMode"; -import { IEventOptions } from "../../interfaces/EventOptions"; +import updatePresence from "../../../helpers/updatePresence"; +import deployCommands from "../../../handlers/deployCommands"; +import devMode from "../../../handlers/devMode"; +import { IEventOptions } from "../../../interfaces/EventOptions"; export const options: IEventOptions = { type: "once", diff --git a/tsconfig.json b/tsconfig.json index 0879e1c..83ed3fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,21 +14,7 @@ "outDir": "./build", "resolveJsonModule": true, "baseUrl": "./src", - "typeRoots": ["/types/common", "./node_modules/@types"], - "paths": { - "@interface/*": ["interfaces/*"], - "@root/*": ["*"], - "@config/*": ["config/*"], - "@events/*": ["events/*"], - "@logger": ["logger"], - "@database": ["database"], - "@jobs/*": ["jobs/*"], - "@handlers/*": ["handlers/*"], - "@helpers/*": ["helpers/*"], - "@locale": ["locale"], - "@plugins/*": ["plugins/*"], - "@schemas/*": ["database/schemas/*"] - } + "typeRoots": ["/types/common", "./node_modules/@types"] }, "include": ["./src"], "exclude": ["./node_modules", "./test"]