diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index 92db357..f5e0cc5 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 @@ -22,6 +23,7 @@ pino Poรคng Profil rando +Repliable satta senaste Sifell diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 6debbd7..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "" -labels: "bug" -assignees: "VermiumSifell" ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Environment (please complete the following information):** - -- Commit: [git rev-parse HEAD] -- Branch: [git branch --show-current] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 0000000..dab3c2f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,60 @@ +name: ๐Ÿž Bug +description: File a bug/issue +title: "[BUG]: " +labels: ["bug"] +body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true + - type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: false + - type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: false + - type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. In this environment... + 2. Run '...' + 3. See error... + validations: + required: false + - type: textarea + attributes: + label: Environment + description: | + examples: + - **OS**: Ubuntu 20.04 + - **Node**: 13.14.0 + - **npm**: 7.6.3 + - **xyter**: 7d02cf9 + value: | + - OS: `lsb_release -d` + - Node: `node -v` + - npm: `npm -v` + - xyter: `git rev-parse --short HEAD` + render: markdown + validations: + required: true + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/command_request.yaml b/.github/ISSUE_TEMPLATE/command_request.yaml new file mode 100644 index 0000000..e6d047c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/command_request.yaml @@ -0,0 +1,48 @@ +name: ๐Ÿค– Request a new command +description: Suggest an command for this project +title: "[New Command]: / [command_group] " +labels: ["enhancement", "new-command"] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! We will use `/manage credits give` as an example for our placeholders. + - type: input + id: category + attributes: + label: Category + description: Where should we put this command? + placeholder: ex. manage + validations: + required: true + - type: input + id: group + attributes: + label: Group + description: Does this command belong to this group? + placeholder: ex. credits + - type: input + id: command + attributes: + label: Command + description: What should we call this command? + placeholder: ex. give + validations: + required: true + - type: textarea + id: description + attributes: + label: What should the command do? + description: Please tell us your concept, how it should work, and if there should be any additional features. + placeholder: "I would like to have a command to give users credits, that would make it easier for me to administrate credits! I would like it to add a specified amount of credits to specified user, without taking credits from the executer. Command should require Manage Guild permission. When successful, it should return something like: Added credits to !" + validations: + required: true + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 36014cd..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: 'enhancement' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index 0ae1541..9e1cc15 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,10 @@ config.json package-lock.json -**/config/*.ts -!**/config/index.ts -!**/config/example.*.ts +config/ + +# Build +build/ # Logs diff --git a/README.md b/README.md index c6ca509..c73ca8a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@
-

An multi-purpose bot built with discord.js

+

A privacy-focused bot built with discord.js

@@ -15,61 +15,7 @@

- About โ€ข - Features + Documentation โ€ข - Installation - โ€ข - License - โ€ข - Credits

- -## โ“ About - -Xyter is an open source, multi-purpose Discord bot that is develoepd by students. You can invite it to your Discord server using [this](https://bot.zyner.org) link! It comes packaged with a variety of commands and a multitude of settings that can be tailored to your server's specific needs. - -**To run this on Pterodactyl** please set startup command to `./node_modules/.bin/ts-node src/index.ts`! - -If you liked this repository, feel free to leave a star โญ to help promote Xyter! - -**For a more updated documentation visit [this site](https://xyter.zyner.org/)!** - -## โ— Features - -**10+** commands and counting across **13** different categories! - -- ๐Ÿ’ฐ **Credits**: `balance`, `gift`, `top`, `work`, `give`, `take`, `set` and `transfer`! -- ๐Ÿ’ฌ **Counters**: `view`, `add`, `remove`! -- ๐Ÿ”จ **Settings**: `guild credits`, `guild pterodactyl`, `guild points` and `user appearence`! -- ๐Ÿ‘‘ **Profile**: `view`! -- ๐Ÿ–ผ **Reputation**: `give`! -- ๐Ÿ’ฐ **Shop**: `roles buy`, `roles cancel` and `pterodactyl`! -- โ” **Utilities**: `lookup`, `about` and `stats`! -- **Full list** of commands: [here](https://github.com/ZynerOrg/xyter/blob/master/docs/COMMANDS.md). - -Xyter also comes packed with a variety of features, such as: - -- **Slash Commands** -- **Multi-language support**. -- And much more! There are over **5+** settings to tweak! - -## ๐Ÿ“ To-Do - -- Bug fixes -- Code optimisation -- New discord features -- Suggestions we deem very good. - -Some more is available in issues - -## ๐Ÿ“– License - -Released under the [GPL-3.0 License](https://github.com/ZynerOrg/xyter/blob/master/LICENSE) license. - -## ๐Ÿ“œ Credits - -- **[Vermium#9649](https://github.com/VermiumSifell)** - Founder, creator, hoster. -- **[Mastergamer433#5762](https://github.com/Mastergamer433)** - Work command for credits. -- Want to be on this list, aswell? - Check out the [Contributing page](https://github.com/ZynerOrg/xyter/blob/master/docs/CONTRIBUTING.md). diff --git a/package.json b/package.json index 216f2fb..ac149cc 100644 --- a/package.json +++ b/package.json @@ -27,16 +27,16 @@ "email": "vermium@zyner.org" }, "dependencies": { - "@discordjs/builders": "^0.13.0", - "@discordjs/rest": "^0.4.0", + "@discordjs/builders": "^0.15.0", + "@discordjs/rest": "^0.5.0", "@types/i18next-fs-backend": "^1.1.2", "axios": "^0.27.2", "chance": "^1.1.8", "common": "^0.2.5", "crypto": "^1.0.1", - "discord-api-types": "^0.33.0", + "discord-api-types": "^0.34.0", "discord.js": "^13.6.0", - "i18n": "^0.14.2", + "i18n": "^0.15.0", "i18next": "^21.6.13", "i18next-async-backend": "^2.0.0", "i18next-fs-backend": "^1.1.4", @@ -56,15 +56,16 @@ "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/parser": "^5.15.0", - "eslint": "8.15.0", + "eslint": "8.18.0", "eslint-config-airbnb-base": "15.0.0", "eslint-config-prettier": "8.5.0", "eslint-plugin-import": "2.26.0", "eslint-plugin-no-loops": "^0.3.0", "eslint-plugin-prettier": "^4.0.0", "husky": "8.0.1", - "jest": "28.0.0", - "lint-staged": "^12.3.7", + "jest": "28.1.1", + "lint-staged": "13.0.1", + "nodemon": "^2.0.16", "prettier": "^2.6.0" }, "lint-staged": { diff --git a/renovate.json b/renovate.json index 4f39080..57d1542 100644 --- a/renovate.json +++ b/renovate.json @@ -1,3 +1,4 @@ { - "extends": ["config:base"] + "extends": ["config:base"], + "baseBranches": ["dev"] } 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 56% rename from src/config/example.other.ts rename to src/config_example/other.ts index 3a86a51..eefd62c 100644 --- a/src/config/example.other.ts +++ b/src/config_example/other.ts @@ -8,4 +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 b7dd3b6..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/guildCreate/index.ts b/src/events/guildCreate/index.ts deleted file mode 100644 index 01b70f6..0000000 --- a/src/events/guildCreate/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -// 3rd party dependencies -import { Guild } from "discord.js"; - -// Dependencies -import updatePresence from "@helpers/updatePresence"; -import fetchGuild from "@helpers/fetchGuild"; -import logger from "@logger"; - -export default { - async execute(guild: Guild) { - const { client } = guild; - - logger?.silly(`Added to guild: ${guild.name} (${guild.id})`); - - await fetchGuild(guild); - await updatePresence(client); - - logger.silly(`guildCreate: ${guild}`); - }, -}; diff --git a/src/events/guildDelete/index.ts b/src/events/guildDelete/index.ts deleted file mode 100644 index 123393c..0000000 --- a/src/events/guildDelete/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -// 3rd party dependencies -import { Guild } from "discord.js"; - -// Dependencies -import updatePresence from "@helpers/updatePresence"; -import dropGuild from "@helpers/dropGuild"; -import logger from "@logger"; - -export default { - async execute(guild: Guild) { - const { client } = guild; - - logger?.silly(`Deleted from guild: ${guild.name} (${guild.id})`); - - await dropGuild(guild); - await updatePresence(client); - - logger.silly(`guildDelete: ${guild}`); - }, -}; diff --git a/src/events/guildMemberAdd/audits.ts b/src/events/guildMemberAdd/audits.ts deleted file mode 100644 index c47c78e..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 "@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/guildMemberAdd/index.ts b/src/events/guildMemberAdd/index.ts deleted file mode 100644 index 9ece541..0000000 --- a/src/events/guildMemberAdd/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -// 3rd party dependencies -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"; - -export default { - async execute(member: GuildMember) { - const { client, user, guild } = member; - - logger?.silly( - `New member: ${user.tag} (${user.id}) added to guild: ${guild.name} (${guild.id})` - ); - - await audits.execute(member); - await joinMessage.execute(member); - await fetchUser(user, guild); - await updatePresence(client); - }, -}; diff --git a/src/events/guildMemberRemove/audits.ts b/src/events/guildMemberRemove/audits.ts deleted file mode 100644 index 620e52e..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 "@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/guildMemberRemove/index.ts b/src/events/guildMemberRemove/index.ts deleted file mode 100644 index 3d28c31..0000000 --- a/src/events/guildMemberRemove/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -// 3rd party dependencies -import { GuildMember } from "discord.js"; - -// Dependencies -import updatePresence from "@helpers/updatePresence"; -import dropUser from "@helpers/dropUser"; -import logger from "@logger"; -import leaveMessage from "./leaveMessage"; -import audits from "./audits"; - -export default { - async execute(member: GuildMember) { - const { client, user, guild } = member; - - logger?.silly( - `Removed member: ${user.tag} (${user.id}) from guild: ${guild.name} (${guild.id})` - ); - - await audits.execute(member); - await leaveMessage.execute(member); - await dropUser(user, guild); - await updatePresence(client); - }, -}; diff --git a/src/events/index.ts b/src/events/index.ts deleted file mode 100644 index b7dd3b6..0000000 --- a/src/events/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/interactionCreate/components/isCommand.ts b/src/events/interactionCreate/components/isCommand.ts deleted file mode 100644 index 7af8e7e..0000000 --- a/src/events/interactionCreate/components/isCommand.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; - -import logger from "@logger"; - -import deferReply from "@root/helpers/deferReply"; -import getEmbedConfig from "@helpers/getEmbedConfig"; -import getCommandMetadata from "@helpers/getCommandMetadata"; - -export default async (interaction: CommandInteraction) => { - if (!interaction.isCommand()) return; - if (interaction.guild == null) 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.permissions && - metadata.guildOnly && - !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(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - - 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`); - - return interaction.editReply({ - embeds: [ - new MessageEmbed() - .setDescription("This command is only available in DM.") - .setColor(errorColor) - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } - } - - await currentCommand - .execute(interaction) - .then(async () => { - return logger?.silly( - `Command: ${commandName} executed in guild: ${guild?.name} (${guild?.id}) by user: ${user?.tag} (${user?.id})` - ); - }) - .catch(async (error: any) => { - logger?.error(`${error}`); - - return interaction.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("Error") - .setDescription( - `There was an error executing the command: **${currentCommand?.data?.name}**.` - ) - .setColor(errorColor) - .setTimestamp(new Date()) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - }); -}; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts deleted file mode 100644 index 2848f39..0000000 --- a/src/events/interactionCreate/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -// 3rd party dependencies -import { CommandInteraction } from "discord.js"; - -// Dependencies -import isCommand from "@root/events/interactionCreate/components/isCommand"; -import logger from "@logger"; -import audits from "./audits"; - -export default { - async execute(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/index.ts b/src/events/messageCreate/index.ts deleted file mode 100644 index 004ca71..0000000 --- a/src/events/messageCreate/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Message } from "discord.js"; -import modules from "@events/messageCreate/modules"; - -export default { - async execute(message: Message) { - await modules.credits.execute(message); - await modules.points.execute(message); - await modules.counters.execute(message); - }, -}; diff --git a/src/events/messageCreate/modules/credits/index.ts b/src/events/messageCreate/modules/credits/index.ts deleted file mode 100644 index 856c308..0000000 --- a/src/events/messageCreate/modules/credits/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -import logger from "@logger"; -import timeouts from "@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/index.ts b/src/events/messageCreate/modules/index.ts deleted file mode 100644 index e2d04fb..0000000 --- a/src/events/messageCreate/modules/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import counters from "@events/messageCreate/modules/counters"; -import credits from "@events/messageCreate/modules/credits"; -import points from "@events/messageCreate/modules/points"; - -export default { - counters, - credits, - points, -}; diff --git a/src/events/messageCreate/modules/points/index.ts b/src/events/messageCreate/modules/points/index.ts deleted file mode 100644 index e4d3aa4..0000000 --- a/src/events/messageCreate/modules/points/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -import logger from "@logger"; -import timeouts from "@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/events/messageDelete/index.ts b/src/events/messageDelete/index.ts deleted file mode 100644 index 77223d2..0000000 --- a/src/events/messageDelete/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Message } from "discord.js"; -import audits from "@events/messageDelete/audits"; -import counter from "./modules/counter"; - -export default { - async execute(message: Message) { - await audits.execute(message); - await counter(message); - }, -}; diff --git a/src/events/messageUpdate/index.ts b/src/events/messageUpdate/index.ts deleted file mode 100644 index 9238805..0000000 --- a/src/events/messageUpdate/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Dependencies -import { Message } from "discord.js"; -import logger from "@logger"; - -// Modules -import counter from "./modules/counter"; - -import audits from "./audits"; - -export default { - async execute(oldMessage: Message, newMessage: Message) { - const { author, guild } = newMessage; - - await audits.execute(oldMessage, newMessage); - - logger?.silly( - `Message update event fired by ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` - ); - - if (author?.bot) return logger?.silly(`Message update event fired by bot`); - - await counter(newMessage); - }, -}; diff --git a/src/events/ready/index.ts b/src/events/ready/index.ts deleted file mode 100644 index b312f73..0000000 --- a/src/events/ready/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Dependencies -import { Client } from "discord.js"; -import logger from "@logger"; - -// Helpers -import updatePresence from "@helpers/updatePresence"; -import deployCommands from "@handlers/deployCommands"; -import devMode from "@handlers/devMode"; - -export default { - once: true, - async execute(client: Client) { - logger.info("Ready!"); - - await updatePresence(client); - await devMode(client); - await deployCommands(client); - - client.guilds?.cache.forEach((guild) => { - logger.silly( - `${client.user?.tag} (${client.user?.id}) is in guild: ${guild.name} (${guild.id}) with member count of ${guild.memberCount}` - ); - }); - }, -}; diff --git a/src/handlers/commands.ts b/src/handlers/commands.ts deleted file mode 100644 index 81b1eeb..0000000 --- a/src/handlers/commands.ts +++ /dev/null @@ -1,36 +0,0 @@ -import fs from "fs"; // fs -import { Collection } from "discord.js"; // discord.js -import { Client } from "@root/types/common/discord"; -import logger from "@logger"; - -export default async (client: Client) => { - client.commands = new Collection(); - - fs.readdir("./src/plugins", async (error, plugins) => { - if (error) { - return logger.error(`Error reading plugins: ${error}`); - } - - await Promise.all( - plugins.map(async (pluginName, index) => { - const plugin = await import(`../plugins/${pluginName}`); - - await client.commands.set( - plugin.default.builder.name, - plugin.default, - plugin.default.metadata - ); - - logger.verbose( - `Loaded plugin ${index + 1}/${plugins.length}: ${pluginName}` - ); - }) - ) - .then(async () => { - logger.info(`Started all ${plugins.length} plugins.`); - }) - .catch(async (err) => { - logger.error(`${err}`); - }); - }); -}; diff --git a/src/handlers/deployCommands.ts b/src/handlers/deployCommands.ts deleted file mode 100644 index 8af167e..0000000 --- a/src/handlers/deployCommands.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Dependencies -import { token, clientId } from "@config/discord"; -import { devMode, guildId } from "@config/other"; - -import logger from "../logger"; -import { Client } from "@root/types/common/discord"; -import { REST } from "@discordjs/rest"; -import { Routes } from "discord-api-types/v9"; - -export default async (client: Client) => { - const pluginList = [] as string[]; - - await Promise.all( - client.commands.map(async (pluginData: any) => { - pluginList.push(pluginData.builder.toJSON()); - logger.verbose( - `Plugin is ready for deployment: ${pluginData.builder.name}` - ); - }) - ) - .then(async () => { - logger.info("All plugins are ready to be deployed."); - }) - .catch(async (error) => { - logger.error(`${error}`); - }); - - const rest = new REST({ version: "9" }).setToken(token); - - await rest - .put(Routes.applicationCommands(clientId), { - body: pluginList, - }) - .then(async () => { - logger.info(`Successfully deployed plugins to Discord's API`); - }) - .catch(async (error) => { - logger.error(`${error}`); - }); - - if (devMode) { - await rest - .put(Routes.applicationGuildCommands(clientId, guildId), { - body: pluginList, - }) - .then(async () => - logger.info(`Successfully deployed guild plugins to Discord's API`) - ) - .catch(async (error) => { - logger.error(`${error}`); - }); - } -}; diff --git a/src/handlers/deployCommands/index.ts b/src/handlers/deployCommands/index.ts new file mode 100644 index 0000000..492e1ab --- /dev/null +++ b/src/handlers/deployCommands/index.ts @@ -0,0 +1,58 @@ +import { token, clientId } from "../../config/discord"; +import { devMode, guildId } from "../../config/other"; + +import logger from "../../logger"; +import { Client } from "discord.js"; +import { REST } from "@discordjs/rest"; +import { Routes } from "discord-api-types/v9"; +import { RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10"; + +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( + client.commands.map(async (commandData: ICommand) => { + commandList.push(commandData.builder.toJSON()); + + logger.verbose(`${commandData.builder.name} pushed to list`); + }) + ) + .then(async () => { + logger.info(`Finished gathering command list.`); + }) + .catch(async (error) => { + throw new Error(`Could not gather command list: ${error}`); + }); + + const rest = new REST({ version: "9" }).setToken(token); + + await rest + .put(Routes.applicationCommands(clientId), { + body: commandList, + }) + .then(async () => { + logger.info(`Finished updating command list.`); + }) + .catch(async (error) => { + logger.error(`${error}`); + }); + + if (devMode) { + await rest + .put(Routes.applicationGuildCommands(clientId, guildId), { + body: commandList, + }) + .then(async () => logger.info(`Finished updating guild command list.`)) + .catch(async (error) => { + logger.error(`${error}`); + }); + } +}; diff --git a/src/handlers/devMode.ts b/src/handlers/devMode/index.ts similarity index 79% rename from src/handlers/devMode.ts rename to src/handlers/devMode/index.ts index 53a2cd6..c7900a0 100644 --- a/src/handlers/devMode.ts +++ b/src/handlers/devMode/index.ts @@ -1,10 +1,10 @@ // Dependencies import { Client } from "discord.js"; -import logger from "@logger"; +import logger from "../../logger"; // Configuration -import { devMode, guildId } from "@config/other"; +import { devMode, guildId } from "../../config/other"; export default async (client: Client) => { if (!devMode) { diff --git a/src/handlers/encryption.ts b/src/handlers/encryption/index.ts similarity index 71% rename from src/handlers/encryption.ts rename to src/handlers/encryption/index.ts index 8e7ea27..8b6512c 100644 --- a/src/handlers/encryption.ts +++ b/src/handlers/encryption/index.ts @@ -1,12 +1,13 @@ import crypto from "crypto"; -import { secretKey, algorithm } from "@config/encryption"; +import { secretKey, algorithm } from "../../config/encryption"; + +import { IEncryptionData } from "../../interfaces/EncryptionData"; const iv = crypto.randomBytes(16); -const encrypt = (text: any): { iv: any; content: any } => { +const encrypt = (text: crypto.BinaryLike): IEncryptionData => { const cipher = crypto.createCipheriv(algorithm, secretKey, iv); - const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); return { @@ -15,7 +16,7 @@ const encrypt = (text: any): { iv: any; content: any } => { }; }; -const decrypt = (hash: any) => { +const decrypt = (hash: IEncryptionData) => { const decipher = crypto.createDecipheriv( algorithm, secretKey, diff --git a/src/handlers/events.ts b/src/handlers/events.ts deleted file mode 100644 index f7da9c1..0000000 --- a/src/handlers/events.ts +++ /dev/null @@ -1,37 +0,0 @@ -import fs from "fs"; // fs -import { Client } from "discord.js"; // discord.js -import logger from "@logger"; - -export default async (client: Client) => { - fs.readdir("./src/events", async (error, events) => { - if (error) { - return logger.error(`Error reading plugins: ${error}`); - } - - await Promise.all( - events.map(async (eventName, index) => { - const event = await import(`../events/${eventName}`); - - logger.verbose( - `Loaded event ${index + 1}/${events.length}: ${eventName}` - ); - - if (event.once) { - return client.once(eventName, async (...args) => - event.default.execute(...args) - ); - } - - return client.on(eventName, async (...args) => - event.default.execute(...args) - ); - }) - ) - .then(async () => { - logger.info(`Started all ${events.length} events.`); - }) - .catch(async (err) => { - logger.error(`${err}`); - }); - }); -}; diff --git a/src/handlers/schedules/index.ts b/src/handlers/schedules/index.ts deleted file mode 100644 index 20ed685..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..6c47b18 --- /dev/null +++ b/src/helpers/addSeconds/index.ts @@ -0,0 +1,4 @@ +export default async (seconds: number, date: Date) => { + date.setSeconds(date.getSeconds() + seconds); + return date; +}; diff --git a/src/helpers/capitalizeFirstLetter/index.ts b/src/helpers/capitalizeFirstLetter/index.ts new file mode 100644 index 0000000..1190b36 --- /dev/null +++ b/src/helpers/capitalizeFirstLetter/index.ts @@ -0,0 +1,3 @@ +export default (text: string): string => { + return text.charAt(0).toUpperCase() + text.slice(1); +}; diff --git a/src/helpers/cooldown/index.ts b/src/helpers/cooldown/index.ts new file mode 100644 index 0000000..f574f57 --- /dev/null +++ b/src/helpers/cooldown/index.ts @@ -0,0 +1,156 @@ +// Dependencies +import { CommandInteraction, ButtonInteraction, Message } from "discord.js"; + +import logger from "../../logger"; + +import timeoutSchema from "../../models/timeout"; +import addSeconds from "../../helpers/addSeconds"; + +export const command = 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 button = async (i: ButtonInteraction, cooldown: number) => { + const { guild, user, customId } = i; + + // Check if user has a timeout + const hasTimeout = await timeoutSchema.findOne({ + guildId: guild?.id || "0", + userId: user.id, + cooldown: cooldown, + timeoutId: customId, + }); + + // 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: customId, + }); +}; + +export const message = async (msg: Message, cooldown: number, id: string) => { + const { guild, member } = msg; + 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.ts b/src/helpers/deferReply.ts deleted file mode 100644 index feec1a7..0000000 --- a/src/helpers/deferReply.ts +++ /dev/null @@ -1,28 +0,0 @@ -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, - }); - - const { waitColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - - await interaction.editReply({ - embeds: [ - new MessageEmbed() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }) - .setTimestamp(new Date()) - .setTitle("Processing your request") - .setColor(waitColor) - .setDescription("Please wait..."), - ], - }); -}; diff --git a/src/helpers/deferReply/index.ts b/src/helpers/deferReply/index.ts new file mode 100644 index 0000000..02cc413 --- /dev/null +++ b/src/helpers/deferReply/index.ts @@ -0,0 +1,27 @@ +import { Interaction, MessageEmbed } from "discord.js"; +import getEmbedConfig from "../../helpers/getEmbedConfig"; + +export default async (interaction: Interaction, ephemeral: boolean) => { + if (!interaction.isRepliable()) + throw new Error(`Cannot reply to an interaction that is not repliable`); + + await interaction.deferReply({ + ephemeral, + }); + + const embedConfig = await getEmbedConfig(interaction.guild); + + await interaction.editReply({ + embeds: [ + new MessageEmbed() + .setFooter({ + text: embedConfig.footerText, + iconURL: embedConfig.footerIcon, + }) + .setTimestamp(new Date()) + .setTitle("Processing your request") + .setColor(embedConfig.waitColor) + .setDescription("Please wait..."), + ], + }); +}; diff --git a/src/helpers/dropGuild.ts b/src/helpers/dropGuild/index.ts similarity index 85% rename from src/helpers/dropGuild.ts rename to src/helpers/dropGuild/index.ts index d69d09b..0c0e2ab 100644 --- a/src/helpers/dropGuild.ts +++ b/src/helpers/dropGuild/index.ts @@ -1,11 +1,11 @@ -import guildSchema from "@schemas/guild"; -import userSchema from "@schemas/user"; -import apiSchema from "@schemas/api"; -import counterSchema from "@schemas/counter"; -import shopRoleSchema from "@schemas/shopRole"; -import timeoutSchema from "@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"; +import logger from "../../logger"; import { Guild } from "discord.js"; diff --git a/src/helpers/dropUser.ts b/src/helpers/dropUser/index.ts similarity index 84% rename from src/helpers/dropUser.ts rename to src/helpers/dropUser/index.ts index 8306285..3093048 100644 --- a/src/helpers/dropUser.ts +++ b/src/helpers/dropUser/index.ts @@ -1,6 +1,6 @@ -import userSchema from "@schemas/user"; +import userSchema from "../../models/user"; -import logger from "@logger"; +import logger from "../../logger"; import { Guild, User } from "discord.js"; diff --git a/src/helpers/embedBuilder.ts b/src/helpers/embedBuilder/index.ts similarity index 74% rename from src/helpers/embedBuilder.ts rename to src/helpers/embedBuilder/index.ts index 8d15d90..d93cc5c 100644 --- a/src/helpers/embedBuilder.ts +++ b/src/helpers/embedBuilder/index.ts @@ -1,4 +1,4 @@ -import { footerText, footerIcon } from "@config/embed"; +import { footerText, footerIcon } from "../../config/embed"; import { MessageEmbed } from "discord.js"; export default new MessageEmbed() diff --git a/src/helpers/fetchGuild.ts b/src/helpers/fetchGuild/index.ts similarity index 87% rename from src/helpers/fetchGuild.ts rename to src/helpers/fetchGuild/index.ts index 4c8f107..a89e840 100644 --- a/src/helpers/fetchGuild.ts +++ b/src/helpers/fetchGuild/index.ts @@ -2,10 +2,10 @@ import { Guild } from "discord.js"; // Models -import guildSchema from "@schemas/guild"; +import guildSchema from "../../models/guild"; // Handlers -import logger from "@logger"; +import logger from "../../logger"; // Function export default async (guild: Guild) => { diff --git a/src/helpers/fetchUser.ts b/src/helpers/fetchUser/index.ts similarity index 90% rename from src/helpers/fetchUser.ts rename to src/helpers/fetchUser/index.ts index 96386ef..3837f24 100644 --- a/src/helpers/fetchUser.ts +++ b/src/helpers/fetchUser/index.ts @@ -2,10 +2,10 @@ import { Guild, User } from "discord.js"; // Models -import userSchema from "@schemas/user"; +import userSchema from "../../models/user"; // Handlers -import logger from "@logger"; +import logger from "../../logger"; // Function export default async (user: User, guild: Guild) => { diff --git a/src/helpers/getCommandMetadata.ts b/src/helpers/getCommandMetadata.ts deleted file mode 100644 index 7894c00..0000000 --- a/src/helpers/getCommandMetadata.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CommandInteraction } from "discord.js"; - -export default async (interaction: CommandInteraction, currentCommand: any) => { - const subcommand = interaction.options.getSubcommand(); - const subcommandGroup = interaction.options.getSubcommandGroup(false); - - if (!subcommandGroup) { - return currentCommand.modules[subcommand].metadata; - } - - return currentCommand.modules[subcommandGroup].modules[subcommand].metadata; -}; diff --git a/src/helpers/getCommandMetadata/index.ts b/src/helpers/getCommandMetadata/index.ts new file mode 100644 index 0000000..647eb75 --- /dev/null +++ b/src/helpers/getCommandMetadata/index.ts @@ -0,0 +1,14 @@ +import { CommandInteraction } from "discord.js"; +import { ICommand } from "../../interfaces/Command"; + +export default async ( + interaction: CommandInteraction, + currentCommand: ICommand +) => { + const subcommand = interaction.options.getSubcommand(); + const subcommandGroup = interaction.options.getSubcommandGroup(false); + + return subcommandGroup + ? currentCommand.moduleData[subcommandGroup].moduleData[subcommand].metadata + : currentCommand.moduleData[subcommand].metadata; +}; diff --git a/src/helpers/getEmbedConfig.ts b/src/helpers/getEmbedConfig.ts deleted file mode 100644 index 3532542..0000000 --- a/src/helpers/getEmbedConfig.ts +++ /dev/null @@ -1,18 +0,0 @@ -import guildSchema from "@schemas/guild"; - -import { ColorResolvable, Guild } from "discord.js"; - -export default async (guild: Guild) => { - const guildConfig = await guildSchema.findOne({ guildId: guild.id }); - - if (guildConfig == null) - return { - successColor: "#22bb33" as ColorResolvable, - waitColor: "#f0ad4e" as ColorResolvable, - errorColor: "#bb2124" as ColorResolvable, - footerIcon: "https://github.com/ZynerOrg.png", - footerText: "https://github.com/ZynerOrg/xyter", - }; - - return guildConfig.embeds; -}; diff --git a/src/helpers/getEmbedConfig/index.ts b/src/helpers/getEmbedConfig/index.ts new file mode 100644 index 0000000..ccd3fdb --- /dev/null +++ b/src/helpers/getEmbedConfig/index.ts @@ -0,0 +1,18 @@ +import guildSchema from "../../models/guild"; +import * as embedConfig from "../../config/embed"; + +import { Guild } from "discord.js"; + +export default async (guild?: Guild | null) => { + if (!guild) { + return { ...embedConfig }; + } + + const guildConfig = await guildSchema.findOne({ guildId: guild.id }); + if (!guildConfig) { + return { + ...embedConfig, + }; + } + return guildConfig.embeds; +}; diff --git a/src/helpers/listDir/index.ts b/src/helpers/listDir/index.ts new file mode 100644 index 0000000..382e41d --- /dev/null +++ b/src/helpers/listDir/index.ts @@ -0,0 +1,8 @@ +import fs from "fs"; +const fsPromises = fs.promises; + +export default async (path: string) => { + return fsPromises.readdir(path).catch(async (err) => { + throw new Error(`Could not list directory: ${path}`, err); + }); +}; diff --git a/src/helpers/pluralize.ts b/src/helpers/pluralize/index.ts similarity index 86% rename from src/helpers/pluralize.ts rename to src/helpers/pluralize/index.ts index 3c5d55f..d6e266d 100644 --- a/src/helpers/pluralize.ts +++ b/src/helpers/pluralize/index.ts @@ -1,4 +1,4 @@ -import logger from "@root/logger"; +import logger from "../../logger"; export default (count: number, noun: string, suffix?: string): string => { const result = `${count} ${noun}${count !== 1 ? suffix || "s" : ""}`; diff --git a/src/helpers/saveUser.ts b/src/helpers/saveUser.ts deleted file mode 100644 index 8feb92e..0000000 --- a/src/helpers/saveUser.ts +++ /dev/null @@ -1,43 +0,0 @@ -import sleep from "@helpers/sleep"; -import logger from "@logger"; -import Chance from "chance"; - -export default async function saveUser(data: any, data2: any) { - process.nextTick( - async () => { - // Chance module - const chance = new Chance(); - - await sleep( - chance.integer({ - min: 0, - max: 1, - }) * - 10 + - 1 * 100 - ); // 100 - 1000 random Number generator - data.save((_: any) => - _ - ? logger?.error( - `ERROR Occurred while saving data (saveUser) \n${"=".repeat( - 50 - )}\n${`${_}\n${"=".repeat(50)}`}` - ) - : logger?.silly(`Saved user: ${data.id} (saveUser)`) - ); - if (data2) { - data2.save((_: any) => - _ - ? logger?.error( - `ERROR Occurred while saving data (saveUser) \n${"=".repeat( - 50 - )}\n${`${_}\n${"=".repeat(50)}`}` - ) - : logger?.silly(`Saved user: ${data2.id} (saveUser)`) - ); - } - }, - data, - data2 - ); -} diff --git a/src/helpers/sleep.ts b/src/helpers/sleep.ts deleted file mode 100644 index 5e53d79..0000000 --- a/src/helpers/sleep.ts +++ /dev/null @@ -1,8 +0,0 @@ -import logger from "@logger"; - -export default function sleep(milliseconds: any) { - return new Promise((resolve) => { - setTimeout(resolve, milliseconds); - logger?.silly(`Sleeping for ${milliseconds} milliseconds`); - }); -} diff --git a/src/helpers/updatePresence.ts b/src/helpers/updatePresence.ts deleted file mode 100644 index 7f23885..0000000 --- a/src/helpers/updatePresence.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Dependencies -import { Client } from "discord.js"; -import logger from "@logger"; - -// Function -export default async (client: Client) => { - const status = `${client?.guilds?.cache?.size} guilds.`; - - client?.user?.setPresence({ - activities: [{ type: "WATCHING", name: status }], - status: "online", - }); - logger?.debug(`Updated client presence to: ${status}`); -}; diff --git a/src/helpers/updatePresence/index.ts b/src/helpers/updatePresence/index.ts new file mode 100644 index 0000000..334a3e2 --- /dev/null +++ b/src/helpers/updatePresence/index.ts @@ -0,0 +1,20 @@ +// Dependencies +import { Client } from "discord.js"; +import logger from "../../logger"; + +// Function +export default async (client: Client) => { + if (!client?.user) throw new Error("Client's user is undefined."); + const { guilds } = client; + + const memberCount = guilds.cache.reduce((a, g) => a + g.memberCount, 0); + const guildCount = guilds.cache.size; + + const status = `${memberCount} users in ${guildCount} guilds.`; + client.user.setPresence({ + activities: [{ type: "LISTENING", name: status }], + status: "online", + }); + + logger.info(`Client's presence is set to "${status}"`); +}; diff --git a/src/index.ts b/src/index.ts index eaef4db..4327353 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,8 @@ -import "tsconfig-paths/register"; // Allows using tsconfig.json paths during runtime - -import { token, intents } from "@config/discord"; +import { token, intents } from "./config/discord"; import { Client } from "discord.js"; // discord.js -import database from "@database"; -import schedules from "@handlers/schedules"; -import events from "@handlers/events"; -import commands from "@handlers/commands"; +import * as managers from "./managers"; // Main process that starts all other sub processes const main = async () => { @@ -16,17 +11,7 @@ const main = async () => { intents, }); - // Start database manager - await database(); - - // Start schedule manager - await schedules(client); - - // Start command handler - await commands(client); - - // Start event handler - await events(client); + await managers.start(client); // Authorize with Discord's API await client.login(token); diff --git a/src/interfaces/Command.ts b/src/interfaces/Command.ts new file mode 100644 index 0000000..f8a895a --- /dev/null +++ b/src/interfaces/Command.ts @@ -0,0 +1,7 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; + +export interface ICommand { + builder: SlashCommandBuilder; + moduleData: any; + execute: Promise; +} diff --git a/src/interfaces/EncryptionData.ts b/src/interfaces/EncryptionData.ts new file mode 100644 index 0000000..ac41844 --- /dev/null +++ b/src/interfaces/EncryptionData.ts @@ -0,0 +1,4 @@ +export interface IEncryptionData { + iv: string; + content: string; +} 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/EventOptions.ts b/src/interfaces/EventOptions.ts new file mode 100644 index 0000000..7634394 --- /dev/null +++ b/src/interfaces/EventOptions.ts @@ -0,0 +1,3 @@ +export interface IEventOptions { + type: "on" | "once"; +} 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/interfaces/ShopRole.ts b/src/interfaces/ShopRole.ts new file mode 100644 index 0000000..ec62cfd --- /dev/null +++ b/src/interfaces/ShopRole.ts @@ -0,0 +1,9 @@ +import { Snowflake } from "discord.js"; +import { Document } from "mongoose"; + +export interface IShopRole extends Document { + guildId: Snowflake; + userId: Snowflake; + roleId: Snowflake; + lastPayed: Date; +} diff --git a/src/jobs/shop/index.ts b/src/jobs/shop/index.ts new file mode 100644 index 0000000..b2ef366 --- /dev/null +++ b/src/jobs/shop/index.ts @@ -0,0 +1,12 @@ +// Dependencies +import { Client } from "discord.js"; + +import * as roles from "./modules/roles"; + +export const options = { + schedule: "*/5 * * * *", // https://crontab.guru/ +}; + +export const execute = async (client: Client) => { + await roles.execute(client); +}; diff --git a/src/jobs/shop/modules/roles/components/dueForPayment.ts b/src/jobs/shop/modules/roles/components/dueForPayment.ts new file mode 100644 index 0000000..ad8a59c --- /dev/null +++ b/src/jobs/shop/modules/roles/components/dueForPayment.ts @@ -0,0 +1,10 @@ +import { Client } from "discord.js"; +import logger from "../../../../../logger"; + +import { IShopRole } from "../../../../../interfaces/ShopRole"; + +export const execute = async (_client: Client, role: IShopRole) => { + const { roleId } = role; + + logger.silly(`Shop role ${roleId} is not due for payment.`); +}; diff --git a/src/jobs/shop/modules/roles/components/overDueForPayment.ts b/src/jobs/shop/modules/roles/components/overDueForPayment.ts new file mode 100644 index 0000000..77ab6e0 --- /dev/null +++ b/src/jobs/shop/modules/roles/components/overDueForPayment.ts @@ -0,0 +1,85 @@ +import { Client } from "discord.js"; +import logger from "../../../../../logger"; + +import { IShopRole } from "../../../../../interfaces/ShopRole"; +import guildSchema from "../../../../../models/guild"; +import userSchema from "../../../../../models/user"; +import shopRoleSchema from "../../../../../models/shopRole"; + +export const execute = async (client: Client, role: IShopRole) => { + const { guildId, userId, roleId } = role; + if (!userId) throw new Error("User ID not found for shop role."); + + const guildData = await guildSchema.findOne({ guildId }); + if (!guildData) throw new Error("Guild not found."); + + const userData = await userSchema.findOne({ guildId, userId }); + if (!userData) throw new Error("User not found."); + + const rGuild = client.guilds.cache.get(guildId); + if (!rGuild) throw new Error("Guild not found."); + + const rMember = await rGuild.members.fetch(userId); + if (!rMember) throw new Error("Member not found."); + + const rRole = rMember.roles.cache.get(roleId); + if (!rRole) throw new Error("Role not found."); + + logger.debug(`Shop role ${roleId} is due for payment.`); + + const { pricePerHour } = guildData.shop.roles; + + if (userData.credits < pricePerHour) { + await rMember.roles + .remove(roleId) + .then(async () => { + await shopRoleSchema + .deleteOne({ + userId, + roleId, + guildId, + }) + .then(async () => { + logger.silly( + `Shop role document ${roleId} has been deleted from user ${userId}.` + ); + }) + .catch(async (err) => { + throw new Error( + `Error deleting shop role document ${roleId} from user ${userId}.`, + err + ); + }); + }) + .catch(async (err) => { + throw new Error( + `Error removing role ${roleId} from user ${userId}.`, + err + ); + }); + + throw new Error("User does not have enough credits."); + } + + userData.credits -= pricePerHour; + await userData + .save() + .then(async () => { + logger.silly(`User ${userId} has been updated.`); + + role.lastPayed = new Date(); + await role + .save() + .then(async () => { + logger.silly(`Shop role ${roleId} has been updated.`); + }) + .catch(async (err) => { + throw new Error(`Error updating shop role ${roleId}.`, err); + }); + + logger.debug(`Shop role ${roleId} has been paid.`); + }) + .catch(async (err) => { + throw new Error(`Error updating user ${userId}.`, err); + }); +}; diff --git a/src/jobs/shop/modules/roles/index.ts b/src/jobs/shop/modules/roles/index.ts new file mode 100644 index 0000000..c8e393a --- /dev/null +++ b/src/jobs/shop/modules/roles/index.ts @@ -0,0 +1,32 @@ +import { Client } from "discord.js"; + +import { IShopRole } from "../../../../interfaces/ShopRole"; +import shopRoleSchema from "../../../../models/shopRole"; + +import * as overDueForPayment from "./components/overDueForPayment"; +import * as dueForPayment from "./components/dueForPayment"; + +export const execute = async (client: Client) => { + const roles = await shopRoleSchema.find(); + + await Promise.all( + roles.map(async (role: IShopRole) => { + const { lastPayed } = role; + const nextPayment = new Date( + lastPayed.setHours(lastPayed.getHours() + 1) + ); + + const now = new Date(); + + if (nextPayment > now) { + await dueForPayment.execute(client, role); + + return; + } + + if (nextPayment < now) { + await overDueForPayment.execute(client, role); + } + }) + ); +}; diff --git a/src/jobs/shopRoles.ts b/src/jobs/shopRoles.ts deleted file mode 100644 index b42a3f5..0000000 --- a/src/jobs/shopRoles.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Dependencies -import { Client } from "discord.js"; - -import logger from "@logger"; - -// Schemas -import userSchema from "@schemas/user"; -import shopRoleSchema from "@schemas/shopRole"; -import guildSchema from "@schemas/guild"; - -export default 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 - .deleteOne({ - userId, - roleId, - guildId, - }) - .then(async () => { - logger.silly( - `Shop role document ${roleId} has been deleted from user ${userId}.` - ); - }) - .catch(async (error) => { - logger.error( - `Error deleting shop role document ${roleId} from user ${userId}.`, - error - ); - }); - 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 () => { - logger.silly(`Shop role ${roleId} has been paid for.`); - }) - .catch(async (err) => { - logger.error( - `Error saving shop role ${roleId} last payed date.`, - err - ); - }); - - logger.silly( - `Shop role ${roleId} has been paid for. Keeping role ${roleId} for user ${userId}.` - ); - }) - .catch(async (err) => { - logger.error( - `Error saving user ${userId} credits for shop role ${roleId}.`, - err - ); - }); - } - }) - ); -}; diff --git a/src/jobs/timeouts/index.ts b/src/jobs/timeouts/index.ts new file mode 100644 index 0000000..ce18f09 --- /dev/null +++ b/src/jobs/timeouts/index.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 ae96924..0593ec9 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -1,10 +1,13 @@ import winston from "winston"; import "winston-daily-rotate-file"; -const { combine, timestamp, printf, colorize, align, json } = winston.format; +import { logLevel } from "../config/other"; + +const { combine, timestamp, printf, errors, colorize, align, json } = + winston.format; export default winston.createLogger({ - level: process.env.LOG_LEVEL || "silly", + level: logLevel || "info", transports: [ new winston.transports.DailyRotateFile({ filename: "logs/combined-%DATE%.log", @@ -14,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 new file mode 100644 index 0000000..c3c08f9 --- /dev/null +++ b/src/managers/command/index.ts @@ -0,0 +1,35 @@ +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("plugins/commands"); + + if (!commandNames) throw new Error("Could not list commands"); + + logger.info(`Loading ${commandNames.length} commands`); + + await Promise.all( + 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); + + logger.verbose(`${command.builder.name} loaded`); + }) + ) + .then(async () => { + logger.info(`Finished loading commands.`); + }) + .catch(async (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 new file mode 100644 index 0000000..348e138 --- /dev/null +++ b/src/managers/event/index.ts @@ -0,0 +1,29 @@ +/* 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("plugins/events"); + if (!eventNames) return; + + for await (const eventName of eventNames) { + 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) { + case "once": + client.once(eventName, eventExecutor); + break; + + case "on": + client.on(eventName, eventExecutor); + break; + } + } +}; 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 64% rename from src/database/schemas/api.ts rename to src/models/api.ts index 12519d2..fd5cbd7 100644 --- a/src/database/schemas/api.ts +++ b/src/models/api.ts @@ -1,10 +1,11 @@ import { Snowflake } from "discord.js"; import { model, Schema } from "mongoose"; +import { IEncryptionData } from "../interfaces/EncryptionData"; export interface IApi { guildId: Snowflake; - url: string; - token: { iv: string; content: string }; + url: IEncryptionData; + token: IEncryptionData; } const apiSchema = new Schema( @@ -16,11 +17,18 @@ const apiSchema = new Schema( index: true, }, url: { - type: String, - required: true, - unique: false, - index: true, - default: "https://localhost/api/", + iv: { + type: String, + required: true, + unique: false, + index: true, + }, + content: { + type: String, + required: true, + unique: false, + index: true, + }, }, token: { iv: { @@ -28,14 +36,12 @@ const apiSchema = new Schema( required: true, unique: false, index: true, - default: "token", }, content: { type: String, required: true, unique: false, index: true, - default: "token", }, }, }, 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..83162a6 --- /dev/null +++ b/src/plugins/buttons/primary/index.ts @@ -0,0 +1,8 @@ +import { ButtonInteraction } from "discord.js"; +import logger from "../../../logger"; + +export const metadata = { guildOnly: false, ephemeral: false }; + +export const execute = async (interaction: ButtonInteraction) => { + logger.debug(interaction.customId, "primary button clicked!"); +}; diff --git a/src/plugins/commands/counters/index.ts b/src/plugins/commands/counters/index.ts new file mode 100644 index 0000000..9626b99 --- /dev/null +++ b/src/plugins/commands/counters/index.ts @@ -0,0 +1,18 @@ +import { CommandInteraction } from "discord.js"; +import { SlashCommandBuilder } from "@discordjs/builders"; + +import modules from "../../commands/counters/modules"; + +export const builder = new SlashCommandBuilder() + .setName("counters") + .setDescription("View guild counters") + + .addSubcommand(modules.view.builder); + +export const moduleData = modules; + +export const execute = async (interaction: CommandInteraction) => { + if (interaction.options.getSubcommand() === "view") { + await modules.view.execute(interaction); + } +}; diff --git a/src/plugins/commands/counters/modules/index.ts b/src/plugins/commands/counters/modules/index.ts new file mode 100644 index 0000000..dc539f8 --- /dev/null +++ b/src/plugins/commands/counters/modules/index.ts @@ -0,0 +1,3 @@ +import view from "./view"; + +export default { view }; diff --git a/src/plugins/counters/modules/view/index.ts b/src/plugins/commands/counters/modules/view/index.ts similarity index 92% rename from src/plugins/counters/modules/view/index.ts rename to src/plugins/commands/counters/modules/view/index.ts index d672f0d..77e1714 100644 --- a/src/plugins/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 "@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/plugins/commands/credits/index.ts b/src/plugins/commands/credits/index.ts new file mode 100644 index 0000000..9d1a3b4 --- /dev/null +++ b/src/plugins/commands/credits/index.ts @@ -0,0 +1,37 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; +import logger from "../../../logger"; + +import modules from "./modules"; + +export const builder = new SlashCommandBuilder() + .setName("credits") + .setDescription("Manage your credits.") + + .addSubcommand(modules.balance.builder) + .addSubcommand(modules.gift.builder) + .addSubcommand(modules.top.builder) + .addSubcommand(modules.work.builder); + +export const moduleData = modules; + +export const execute = async (interaction: CommandInteraction) => { + const { options } = interaction; + + switch (options.getSubcommand()) { + case "balance": + await modules.balance.execute(interaction); + break; + case "gift": + await modules.gift.execute(interaction); + break; + case "top": + await modules.top.execute(interaction); + break; + case "work": + await modules.work.execute(interaction); + break; + default: + logger.silly(`Unknown subcommand ${options.getSubcommand()}`); + } +}; diff --git a/src/plugins/credits/modules/balance/index.ts b/src/plugins/commands/credits/modules/balance/index.ts similarity index 92% rename from src/plugins/credits/modules/balance/index.ts rename to src/plugins/commands/credits/modules/balance/index.ts index 60ee17e..0e646c2 100644 --- a/src/plugins/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/plugins/credits/modules/gift/index.ts b/src/plugins/commands/credits/modules/gift/index.ts similarity index 75% rename from src/plugins/credits/modules/gift/index.ts rename to src/plugins/commands/credits/modules/gift/index.ts index 0325ab0..84b2350 100644 --- a/src/plugins/credits/modules/gift/index.ts +++ b/src/plugins/commands/credits/modules/gift/index.ts @@ -2,16 +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"; -// Helpers -import saveUser from "@helpers/saveUser"; +import mongoose from "mongoose"; // Models -import fetchUser from "@helpers/fetchUser"; +import fetchUser from "../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -39,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; @@ -184,53 +182,69 @@ export default { }); } - // Withdraw amount from fromUserDB - fromUserDB.credits -= optionAmount; + const session = await mongoose.startSession(); - // Deposit amount to toUserDB - toUserDB.credits += optionAmount; + session.startTransaction(); - // Save users - await saveUser(fromUserDB, toUserDB).then(async () => { - // Get DM user object - const dmUser = client.users.cache.get(optionUser.id); + try { + // Withdraw amount from fromUserDB + fromUserDB.credits -= optionAmount; - if (dmUser == null) return; + // Deposit amount to toUserDB + toUserDB.credits += optionAmount; - // Send DM to user - await dmUser - .send({ - embeds: [ - embed - .setDescription( - `${ - user.tag - } has gifted you ${optionAmount} credits with reason: ${ - optionReason || "unspecified" - }` - ) - .setColor(successColor), - ], - }) - .catch(async (error) => - logger.error(`[Gift] Error sending DM to user: ${error}`) - ); + await fromUserDB.save(); - logger.silly( - `[Gift] Successfully gifted ${optionAmount} credits to ${optionUser.tag}` - ); + await toUserDB.save(); + + await session.commitTransaction(); + } catch (error) { + await session.abortTransaction(); + session.endSession(); + logger.error(`${error}`); return interaction.editReply({ embeds: [ embed .setDescription( - `Successfully gifted ${optionAmount} credits to ${ - optionUser.tag - } with reason: ${optionReason || "unspecified"}` + "An error occurred while trying to gift credits. Please try again." ) - .setColor(successColor), + .setColor(errorColor), ], }); + } finally { + // ending the session + session.endSession(); + } + + // Get DM user object + const dmUser = client.users.cache.get(optionUser.id); + + if (!dmUser) throw new Error("User not found"); + + // Send DM to user + await dmUser.send({ + embeds: [ + embed + .setDescription( + `${user.tag} has gifted you ${optionAmount} credits with reason: ${ + optionReason || "unspecified" + }` + ) + .setColor(successColor), + ], + }); + + return interaction.editReply({ + embeds: [ + embed + .setDescription( + `Successfully gifted ${optionAmount} credits to ${ + optionUser.tag + } with reason: ${optionReason || "unspecified"}` + ) + .setColor(successColor), + ], }); }, }; diff --git a/src/plugins/commands/credits/modules/index.ts b/src/plugins/commands/credits/modules/index.ts new file mode 100644 index 0000000..20edcaf --- /dev/null +++ b/src/plugins/commands/credits/modules/index.ts @@ -0,0 +1,6 @@ +import balance from "./balance"; +import gift from "./gift"; +import top from "./top"; +import work from "./work"; + +export default { balance, gift, top, work }; diff --git a/src/plugins/credits/modules/top/index.ts b/src/plugins/commands/credits/modules/top/index.ts similarity index 87% rename from src/plugins/credits/modules/top/index.ts rename to src/plugins/commands/credits/modules/top/index.ts index 747f1bc..54478aa 100644 --- a/src/plugins/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 "@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; @@ -55,7 +54,7 @@ export default { embeds: [ embed .setDescription( - `Below are the top 10 users in this guild. + `Below are the top ten members in this guild. ${topTen.map(entry).join("\n")} ` diff --git a/src/plugins/credits/modules/work/index.ts b/src/plugins/commands/credits/modules/work/index.ts similarity index 51% rename from src/plugins/credits/modules/work/index.ts rename to src/plugins/commands/credits/modules/work/index.ts index 1cde8cb..ef0a550 100644 --- a/src/plugins/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 "@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.command(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/plugins/commands/dns/index.ts b/src/plugins/commands/dns/index.ts new file mode 100644 index 0000000..ee623ec --- /dev/null +++ b/src/plugins/commands/dns/index.ts @@ -0,0 +1,22 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +import modules from "./modules"; +export const moduleData = modules; + +export const builder = new SlashCommandBuilder() + .setName("dns") + .setDescription("DNS commands.") + + .addSubcommand(modules.lookup.builder); + +export const execute = async (interaction: CommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "lookup": + return modules.lookup.execute(interaction); + default: + throw new Error( + `Unknown subcommand: ${interaction.options.getSubcommand()}` + ); + } +}; diff --git a/src/plugins/commands/dns/modules/index.ts b/src/plugins/commands/dns/modules/index.ts new file mode 100644 index 0000000..92a80ac --- /dev/null +++ b/src/plugins/commands/dns/modules/index.ts @@ -0,0 +1,3 @@ +import lookup from "./lookup"; + +export default { lookup }; diff --git a/src/plugins/commands/dns/modules/lookup/index.ts b/src/plugins/commands/dns/modules/lookup/index.ts new file mode 100644 index 0000000..ea56b83 --- /dev/null +++ b/src/plugins/commands/dns/modules/lookup/index.ts @@ -0,0 +1,131 @@ +import axios from "axios"; +import { CommandInteraction, MessageEmbed } from "discord.js"; + +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; + +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +export default { + metadata: { guildOnly: false, ephemeral: false }, + + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("lookup") + .setDescription( + "Lookup a domain or ip. (Request sent over HTTP, proceed with caution!)" + ) + .addStringOption((option) => + option + .setName("query") + .setDescription("The query you want to look up.") + .setRequired(true) + ); + }, + execute: async (interaction: CommandInteraction) => { + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); + const embedTitle = "[:hammer:] Utility (Lookup)"; + + const { options } = interaction; + const query = options.getString("query"); + + await axios + .get(`http://ip-api.com/json/${query}`) + .then(async (response) => { + if (response.data.status !== "success") { + await interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle(embedTitle) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }) + .setDescription( + `${response?.data?.message}: ${response?.data?.query}` + ), + ], + }); + return; + } + + await interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle(embedTitle) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }) + .setTimestamp(new Date()) + .setColor(successColor) + .setFields([ + { + name: ":classical_building: AS", + value: `${response.data.as || "Unknown"}`, + inline: true, + }, + { + name: ":classical_building: ISP", + value: `${response.data.isp || "Unknown"}`, + inline: true, + }, + { + name: ":classical_building: Organization", + value: `${response.data.org || "Unknown"}`, + inline: true, + }, + { + name: ":compass: Latitude", + value: `${response.data.lat || "Unknown"}`, + inline: true, + }, + { + name: ":compass: Longitude", + value: `${response.data.lon || "Unknown"}`, + inline: true, + }, + { + name: ":clock4: Timezone", + value: `${response.data.timezone || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: Country", + value: `${response.data.country || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: Region", + value: `${response.data.regionName || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: City", + value: `${response.data.city || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: Country Code", + value: `${response.data.countryCode || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: Region Code", + value: `${response.data.region || "Unknown"}`, + inline: true, + }, + { + name: ":globe_with_meridians: ZIP", + value: `${response.data.zip || "Unknown"}`, + inline: true, + }, + ]), + ], + }); + }); + }, +}; diff --git a/src/plugins/commands/fun/index.ts b/src/plugins/commands/fun/index.ts new file mode 100644 index 0000000..e0337a6 --- /dev/null +++ b/src/plugins/commands/fun/index.ts @@ -0,0 +1,23 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; +import logger from "../../../logger"; + +import modules from "../../commands/fun/modules"; + +export const builder = new SlashCommandBuilder() + .setName("fun") + .setDescription("Fun commands.") + + .addSubcommand(modules.meme.builder); + +export const moduleData = modules; + +export const execute = async (interaction: CommandInteraction) => { + const { options } = interaction; + + if (options.getSubcommand() === "meme") { + await modules.meme.execute(interaction); + } else { + logger.silly(`Unknown subcommand ${options.getSubcommand()}`); + } +}; diff --git a/src/plugins/commands/fun/modules/index.ts b/src/plugins/commands/fun/modules/index.ts new file mode 100644 index 0000000..53aeddc --- /dev/null +++ b/src/plugins/commands/fun/modules/index.ts @@ -0,0 +1,5 @@ +import meme from "./meme"; + +export default { + meme, +}; diff --git a/src/plugins/commands/fun/modules/meme/index.ts b/src/plugins/commands/fun/modules/meme/index.ts new file mode 100644 index 0000000..88b2304 --- /dev/null +++ b/src/plugins/commands/fun/modules/meme/index.ts @@ -0,0 +1,59 @@ +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, cooldown: 15 }, + + builder: (command: SlashCommandSubcommandBuilder) => { + return command.setName("meme").setDescription("Get a meme from r/memes)"); + }, + + execute: async (interaction: CommandInteraction) => { + const { guild } = interaction; + + const embedConfig = await getEmbedConfig(guild); + + await axios + .get("https://www.reddit.com/r/memes/random/.json") + .then(async (res) => { + const response = res.data[0].data.children; + const content = response[0].data; + + const embed = new MessageEmbed() + .setAuthor({ + name: content.title, + iconURL: + "https://www.redditinc.com/assets/images/site/reddit-logo.png", + url: `https://reddit.com${content.permalink}`, + }) + .setTitle("[:sweat_smile:] Meme") + .addFields([ + { + name: "Author", + value: `[${content.author}](https://reddit.com/user/${content.author})`, + inline: true, + }, + { + name: "Votes", + value: `${content.ups}/${content.downs}`, + inline: true, + }, + ]) + .setTimestamp(new Date()) + .setImage(content.url) + .setFooter({ + text: embedConfig.footerText, + iconURL: embedConfig.footerIcon, + }) + .setColor(embedConfig.successColor); + + return interaction.editReply({ embeds: [embed] }); + }) + .catch((error) => { + throw new Error(error.message); + }); + }, +}; diff --git a/src/plugins/commands/manage/index.ts b/src/plugins/commands/manage/index.ts new file mode 100644 index 0000000..1b61f39 --- /dev/null +++ b/src/plugins/commands/manage/index.ts @@ -0,0 +1,28 @@ +//Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +// Groups +import modules from "../../commands/manage/modules"; + +export const moduleData = modules; + +// Function +export const builder = new SlashCommandBuilder() + .setName("manage") + .setDescription("Manage the bot.") + .addSubcommandGroup(modules.counters.builder) + .addSubcommandGroup(modules.credits.builder); + +export const execute = async (interaction: CommandInteraction) => { + // Destructure + const { options } = interaction; + + if (options?.getSubcommandGroup() === "credits") { + return modules.credits.execute(interaction); + } + + if (options?.getSubcommandGroup() === "counters") { + return modules.counters.execute(interaction); + } +}; diff --git a/src/plugins/commands/manage/modules/counters/index.ts b/src/plugins/commands/manage/modules/counters/index.ts new file mode 100644 index 0000000..dbafa9f --- /dev/null +++ b/src/plugins/commands/manage/modules/counters/index.ts @@ -0,0 +1,37 @@ +// Dependencies +import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +import logger from "../../../../../logger"; + +// Modules +import modules from "./modules"; + +// Function +export const moduleData = modules; + +export const builder = (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("counters") + .setDescription("Manage guild counters.") + .addSubcommand(modules.add.builder) + .addSubcommand(modules.remove.builder); +}; + +export const execute = async (interaction: CommandInteraction) => { + const { options } = interaction; + + if (options?.getSubcommand() === "add") { + logger?.silly(`Executing create subcommand`); + + return modules.add.execute(interaction); + } + + if (options?.getSubcommand() === "remove") { + logger?.silly(`Executing delete subcommand`); + + return modules.remove.execute(interaction); + } + + logger?.silly(`Unknown subcommand ${options?.getSubcommand()}`); +}; diff --git a/src/plugins/manage/modules/counters/modules/add/index.ts b/src/plugins/commands/manage/modules/counters/modules/add/index.ts similarity index 93% rename from src/plugins/manage/modules/counters/modules/add/index.ts rename to src/plugins/commands/manage/modules/counters/modules/add/index.ts index b4c6e99..d00a319 100644 --- a/src/plugins/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 "@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/plugins/commands/manage/modules/counters/modules/index.ts b/src/plugins/commands/manage/modules/counters/modules/index.ts new file mode 100644 index 0000000..bc9da9c --- /dev/null +++ b/src/plugins/commands/manage/modules/counters/modules/index.ts @@ -0,0 +1,4 @@ +import add from "./add"; +import remove from "./remove"; + +export default { add, remove }; diff --git a/src/plugins/manage/modules/counters/modules/remove/index.ts b/src/plugins/commands/manage/modules/counters/modules/remove/index.ts similarity index 92% rename from src/plugins/manage/modules/counters/modules/remove/index.ts rename to src/plugins/commands/manage/modules/counters/modules/remove/index.ts index 8a61687..563829b 100644 --- a/src/plugins/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 "@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/plugins/commands/manage/modules/credits/index.ts b/src/plugins/commands/manage/modules/credits/index.ts new file mode 100644 index 0000000..92efd12 --- /dev/null +++ b/src/plugins/commands/manage/modules/credits/index.ts @@ -0,0 +1,32 @@ +import { CommandInteraction } from "discord.js"; +import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; + +import modules from "./modules"; + +export const moduleData = modules; + +export const builder = (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("credits") + .setDescription("Manage the credits of a user.") + .addSubcommand(modules.give.builder) + .addSubcommand(modules.set.builder) + .addSubcommand(modules.take.builder) + .addSubcommand(modules.transfer.builder) + .addSubcommand(modules.giveaway.builder); +}; + +export const execute = async (interaction: CommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "give": + return modules.give.execute(interaction); + case "set": + return modules.set.execute(interaction); + case "take": + return modules.take.execute(interaction); + case "transfer": + return modules.transfer.execute(interaction); + case "giveaway": + return modules.giveaway.execute(interaction); + } +}; diff --git a/src/plugins/manage/modules/credits/modules/give/index.ts b/src/plugins/commands/manage/modules/credits/modules/give/index.ts similarity index 94% rename from src/plugins/manage/modules/credits/modules/give/index.ts rename to src/plugins/commands/manage/modules/credits/modules/give/index.ts index cec0376..9615f73 100644 --- a/src/plugins/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/plugins/commands/manage/modules/credits/modules/giveaway/index.ts b/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts new file mode 100644 index 0000000..5c08048 --- /dev/null +++ b/src/plugins/commands/manage/modules/credits/modules/giveaway/index.ts @@ -0,0 +1,139 @@ +// Dependencies +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 "../../../../../../../models/api"; +import encryption from "../../../../../../../handlers/encryption"; + +// Configurations +import getEmbedConfig from "../../../../../../../helpers/getEmbedConfig"; + +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("giveaway") + .setDescription("Giveaway some credits for specified amount of users.") + .addIntegerOption((option) => + option + .setName("uses") + .setDescription("How many users should be able to use this.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("credit") + .setDescription(`How much credits provided per use.`) + .setRequired(true) + ) + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The channel to send the message to.") + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ); + }, + execute: async (interaction: CommandInteraction) => { + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure + const { guild, options } = interaction; + + const uses = options?.getInteger("uses"); + const creditAmount = options?.getInteger("credit"); + const channel = options?.getChannel("channel"); + + if (!uses) throw new Error("Amount of uses is required."); + if (!creditAmount) throw new Error("Amount of credits is required."); + if (!channel) throw new Error("Channel is required."); + + const embed = new MessageEmbed() + .setTitle("[:toolbox:] Giveaway") + .setFooter({ text: footerText, iconURL: footerIcon }); + + const code = uuidv4(); + + const apiCredentials = await apiSchema?.findOne({ + guildId: guild?.id, + }); + + if (!apiCredentials) return; + + const url = encryption.decrypt(apiCredentials?.url); + + const api = axios?.create({ + baseURL: `${url}/api/`, + headers: { + Authorization: `Bearer ${encryption.decrypt(apiCredentials.token)}`, + }, + }); + + const shopUrl = `${url}/store`; + + await api + .post("vouchers", { + uses, + code, + credits: creditAmount, + memo: `[GIVEAWAY] ${interaction?.createdTimestamp} - ${interaction?.user?.id}`, + }) + .then(async () => { + await interaction.editReply({ + embeds: [ + embed + .setColor(successColor) + .setDescription(`Successfully created code: ${code}`), + ], + }); + + const buttons = new 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; + + if (discordChannel.type !== "GUILD_TEXT") return; + + discordChannel.send({ + embeds: [ + new MessageEmbed() + .setTitle("[:parachute:] Credits!") + .addFields([ + { + name: "๐Ÿ’ถ Credits", + value: `${creditAmount}`, + inline: true, + }, + ]) + .setDescription( + `${interaction.user} dropped a voucher for a maximum **${uses}** members!` + ) + .setColor(successColor), + ], + components: [buttons], + }); + }); + }, +}; diff --git a/src/plugins/commands/manage/modules/credits/modules/index.ts b/src/plugins/commands/manage/modules/credits/modules/index.ts new file mode 100644 index 0000000..96acc29 --- /dev/null +++ b/src/plugins/commands/manage/modules/credits/modules/index.ts @@ -0,0 +1,7 @@ +import give from "./give"; +import set from "./set"; +import take from "./take"; +import transfer from "./transfer"; +import giveaway from "./giveaway"; + +export default { give, set, take, transfer, giveaway }; diff --git a/src/plugins/manage/modules/credits/modules/set/index.ts b/src/plugins/commands/manage/modules/credits/modules/set/index.ts similarity index 95% rename from src/plugins/manage/modules/credits/modules/set/index.ts rename to src/plugins/commands/manage/modules/credits/modules/set/index.ts index a7d86a2..a3be389 100644 --- a/src/plugins/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/plugins/manage/modules/credits/modules/take/index.ts b/src/plugins/commands/manage/modules/credits/modules/take/index.ts similarity index 95% rename from src/plugins/manage/modules/credits/modules/take/index.ts rename to src/plugins/commands/manage/modules/credits/modules/take/index.ts index 9c823b9..fd3076b 100644 --- a/src/plugins/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/plugins/manage/modules/credits/modules/transfer/index.ts b/src/plugins/commands/manage/modules/credits/modules/transfer/index.ts similarity index 78% rename from src/plugins/manage/modules/credits/modules/transfer/index.ts rename to src/plugins/commands/manage/modules/credits/modules/transfer/index.ts index 19de23a..dca0091 100644 --- a/src/plugins/manage/modules/credits/modules/transfer/index.ts +++ b/src/plugins/commands/manage/modules/credits/modules/transfer/index.ts @@ -1,17 +1,16 @@ // Dependencies 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"; - -// Helpers -import saveUser from "@helpers/saveUser"; +import logger from "../../../../../../../logger"; // Models -import fetchUser from "@helpers/fetchUser"; +import fetchUser from "../../../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function @@ -46,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; @@ -193,38 +191,66 @@ export default { }); } - // Withdraw amount from fromUser - fromUser.credits -= optionAmount; + const session = await mongoose.startSession(); - // Deposit amount to toUser - toUser.credits += optionAmount; + session.startTransaction(); - // Save users - await saveUser(fromUser, toUser)?.then(async () => { - logger?.silly(`Saved users`); + try { + // Withdraw amount from fromUserDB + fromUser.credits -= optionAmount; - return interaction?.editReply({ + // Deposit amount to toUserDB + toUser.credits += optionAmount; + + await fromUser.save(); + + await toUser.save(); + + await session.commitTransaction(); + } catch (error) { + await session.abortTransaction(); + session.endSession(); + logger.error(`${error}`); + + return interaction.editReply({ embeds: [ new MessageEmbed() .setTitle("[:toolbox:] Manage - Credits (Transfer)") - .setDescription(`Transferred ${optionAmount} credits.`) - .addFields( - { - name: `${optionFromUser?.username} Balance`, - value: `${fromUser?.credits}`, - inline: true, - }, - { - name: `${optionToUser?.username} Balance`, - value: `${toUser?.credits}`, - inline: true, - } + .setDescription( + "An error occurred while trying to gift credits. Please try again." ) + .setColor(errorColor) .setTimestamp(new Date()) .setColor(successColor) .setFooter({ text: footerText, iconURL: footerIcon }), ], }); + } finally { + // ending the session + session.endSession(); + } + + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription(`Transferred ${optionAmount} credits.`) + .addFields( + { + name: `${optionFromUser?.username} Balance`, + value: `${fromUser?.credits}`, + inline: true, + }, + { + name: `${optionToUser?.username} Balance`, + value: `${toUser?.credits}`, + inline: true, + } + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], }); }, }; diff --git a/src/plugins/commands/manage/modules/index.ts b/src/plugins/commands/manage/modules/index.ts new file mode 100644 index 0000000..c55982f --- /dev/null +++ b/src/plugins/commands/manage/modules/index.ts @@ -0,0 +1,4 @@ +import * as counters from "./counters"; +import * as credits from "./credits"; + +export default { counters, credits }; diff --git a/src/plugins/commands/moderation/index.ts b/src/plugins/commands/moderation/index.ts new file mode 100644 index 0000000..73b4994 --- /dev/null +++ b/src/plugins/commands/moderation/index.ts @@ -0,0 +1,22 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +import modules from "./modules"; +export const moduleData = modules; + +export const builder = new SlashCommandBuilder() + .setName("moderation") + .setDescription("Moderation.") + + .addSubcommand(modules.prune.builder); + +export const execute = async (interaction: CommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "prune": + return modules.prune.execute(interaction); + default: + throw new Error( + `Unknown subcommand: ${interaction.options.getSubcommand()}` + ); + } +}; diff --git a/src/plugins/commands/moderation/modules/index.ts b/src/plugins/commands/moderation/modules/index.ts new file mode 100644 index 0000000..e7dd532 --- /dev/null +++ b/src/plugins/commands/moderation/modules/index.ts @@ -0,0 +1,3 @@ +import prune from "./prune"; + +export default { prune }; diff --git a/src/plugins/commands/moderation/modules/prune/index.ts b/src/plugins/commands/moderation/modules/prune/index.ts new file mode 100644 index 0000000..c847e47 --- /dev/null +++ b/src/plugins/commands/moderation/modules/prune/index.ts @@ -0,0 +1,91 @@ +// Dependencies +import { + CommandInteraction, + Permissions, +} from "discord.js"; + +// Configurations +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; + +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + metadata: { + guildOnly: true, + ephemeral: false, + permissions: [Permissions.FLAGS.MANAGE_MESSAGES], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("prune") + .setDescription("Prune messages!") + .addIntegerOption((option) => + option + .setName("count") + .setDescription("How many messages you want to prune.") + .setRequired(true) + ) + .addBooleanOption((option) => + option.setName("bots").setDescription("Include bots.") + ); + }, + execute: async (interaction: CommandInteraction) => { + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const count = interaction.options.getInteger("count"); + if (count == null) return; + const bots = interaction.options.getBoolean("bots"); + + if (count < 1 || count > 100) { + const interactionEmbed = { + title: "[:police_car:] Prune", + description: `You can only prune between 1 and 100 messages.`, + color: successColor, + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, + }; + await interaction.editReply({ + embeds: [interactionEmbed], + }); + return; + } + + if (interaction?.channel?.type !== "GUILD_TEXT") return; + await interaction.channel.messages.fetch().then(async (messages) => { + const messagesToDelete = ( + bots + ? messages.filter((m) => m?.interaction?.id !== interaction.id) + : messages.filter( + (m) => + m?.interaction?.id !== interaction.id && m?.author?.bot !== true + ) + ).first(count); + + if (interaction?.channel?.type !== "GUILD_TEXT") return; + await interaction.channel + .bulkDelete(messagesToDelete, true) + .then(async () => { + const interactionEmbed = { + title: "[:police_car:] Prune", + description: `Successfully pruned \`${count}\` messages.`, + color: successColor, + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, + }; + await interaction.editReply({ + embeds: [interactionEmbed], + }); + }); + }); + }, +}; diff --git a/src/plugins/commands/profile/index.ts b/src/plugins/commands/profile/index.ts new file mode 100644 index 0000000..68cdc73 --- /dev/null +++ b/src/plugins/commands/profile/index.ts @@ -0,0 +1,29 @@ +// Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +// Modules +import modules from "../../commands/profile/modules"; + +// Handlers +import logger from "../../../logger"; + +export const moduleData = modules; + +// Function +export const builder = new SlashCommandBuilder() + .setName("profile") + .setDescription("Check a profile.") + .addSubcommand(modules.view.builder); + +export const execute = async (interaction: CommandInteraction) => { + const { options } = interaction; + + if (options?.getSubcommand() === "view") { + logger?.silly(`Executing view subcommand`); + + return modules.view.execute(interaction); + } + + logger?.silly(`No subcommand found`); +}; diff --git a/src/plugins/commands/profile/modules/index.ts b/src/plugins/commands/profile/modules/index.ts new file mode 100644 index 0000000..dc539f8 --- /dev/null +++ b/src/plugins/commands/profile/modules/index.ts @@ -0,0 +1,3 @@ +import view from "./view"; + +export default { view }; diff --git a/src/plugins/profile/modules/view.ts b/src/plugins/commands/profile/modules/view/index.ts similarity index 93% rename from src/plugins/profile/modules/view.ts rename to src/plugins/commands/profile/modules/view/index.ts index c6704a3..4fbd5c6 100644 --- a/src/plugins/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/plugins/commands/reputation/index.ts b/src/plugins/commands/reputation/index.ts new file mode 100644 index 0000000..d667343 --- /dev/null +++ b/src/plugins/commands/reputation/index.ts @@ -0,0 +1,22 @@ +// Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +// Modules +import modules from "./modules"; + +// Handlers + +export const moduleData = modules; + +// Function +export const builder = new SlashCommandBuilder() + .setName("reputation") + .setDescription("Manage reputation.") + .addSubcommand(modules.give.builder); + +export const execute = async (interaction: CommandInteraction) => { + if (interaction.options.getSubcommand() === "give") { + await modules.give.execute(interaction); + } +}; 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..f9970df --- /dev/null +++ b/src/plugins/commands/reputation/modules/give/index.ts @@ -0,0 +1,88 @@ +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 { 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.command(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/plugins/commands/reputation/modules/index.ts b/src/plugins/commands/reputation/modules/index.ts new file mode 100644 index 0000000..e891cf0 --- /dev/null +++ b/src/plugins/commands/reputation/modules/index.ts @@ -0,0 +1,3 @@ +import give from "./give"; + +export default { give }; diff --git a/src/plugins/commands/shop/index.ts b/src/plugins/commands/shop/index.ts new file mode 100644 index 0000000..697c94e --- /dev/null +++ b/src/plugins/commands/shop/index.ts @@ -0,0 +1,36 @@ +// Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +// Modules +import modules from "./modules"; + +// Handlers +import logger from "../../../logger"; + +export const moduleData = modules; + +// Function +export const builder = new SlashCommandBuilder() + .setName("shop") + .setDescription("Shop for credits and custom roles.") + .addSubcommand(modules.cpgg.builder) + .addSubcommandGroup(modules.roles.builder); + +export const execute = async (interaction: CommandInteraction) => { + const { options } = interaction; + + if (options?.getSubcommand() === "cpgg") { + logger.silly(`Executing cpgg subcommand`); + + return modules.cpgg.execute(interaction); + } + + if (options?.getSubcommandGroup() === "roles") { + logger?.silly(`Subcommand group is roles`); + + return modules.roles.execute(interaction); + } + + logger?.silly(`No subcommand found.`); +}; diff --git a/src/plugins/shop/modules/pterodactyl.ts b/src/plugins/commands/shop/modules/cpgg/index.ts similarity index 64% rename from src/plugins/shop/modules/pterodactyl.ts rename to src/plugins/commands/shop/modules/cpgg/index.ts index f3ef08b..365753f 100644 --- a/src/plugins/shop/modules/pterodactyl.ts +++ b/src/plugins/commands/shop/modules/cpgg/index.ts @@ -1,16 +1,20 @@ -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 "@schemas/api"; -import fetchUser from "@helpers/fetchUser"; +import apiSchema from "../../../../../models/api"; +import fetchUser from "../../../../../helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; export default { @@ -18,16 +22,16 @@ export default { builder: (command: SlashCommandSubcommandBuilder) => { return command - .setName("pterodactyl") - .setDescription("Buy pterodactyl power.") + .setName("cpgg") + .setDescription("Buy cpgg power.") .addIntegerOption((option) => option .setName("amount") .setDescription("How much credits you want to withdraw.") + .setRequired(true) ); }, 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: [ @@ -147,14 +151,25 @@ export default { guildId: guild?.id, }); + if (!apiCredentials) return; + const url = encryption.decrypt(apiCredentials?.url); + const api = axios?.create({ - baseURL: apiCredentials?.url, + baseURL: `${url}/api/`, headers: { - Authorization: `Bearer ${encryption.decrypt(apiCredentials?.token)}`, + Authorization: `Bearer ${encryption.decrypt(apiCredentials.token)}`, }, }); - const shopUrl = apiCredentials?.url?.replace("/api", "/store"); + const shopUrl = `${url}/store`; + + const buttons = new MessageActionRow().addComponents( + new MessageButton() + .setLabel("Redeem it here") + .setStyle("LINK") + .setEmoji("๐Ÿฆ") + .setURL(`${shopUrl}?voucher=${code}`) + ); await api @@ -176,43 +191,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) => { @@ -221,7 +240,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(), @@ -235,13 +254,13 @@ export default { }); }) - .catch(async (error: any) => { + .catch(async (error) => { logger?.silly(`Error creating voucher. - ${error}`); 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/plugins/commands/shop/modules/roles/index.ts b/src/plugins/commands/shop/modules/roles/index.ts new file mode 100644 index 0000000..f16a962 --- /dev/null +++ b/src/plugins/commands/shop/modules/roles/index.ts @@ -0,0 +1,65 @@ +// Dependencies +import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +// Handlers +import logger from "../../../../../logger"; + +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; + +// Modules +import modules from "./modules"; + +import guildSchema from "../../../../../models/guild"; + +export const moduleData = modules; + +// Function +export const builder = (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("roles") + .setDescription("Shop for custom roles.") + .addSubcommand(modules.buy.builder) + .addSubcommand(modules.cancel.builder); +}; + +export const execute = async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const { options, guild } = interaction; + + const guildDB = await guildSchema?.findOne({ + guildId: guild?.id, + }); + + if (guildDB === null) return; + + if (!guildDB.shop.roles.status) { + logger.silly(`Shop roles disabled.`); + + return interaction?.editReply({ + embeds: [ + { + title: ":dollar: Shop - Roles", + description: "This server has disabled shop roles.", + color: errorColor, + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, + }, + ], + }); + } + + if (options?.getSubcommand() === "buy") { + await modules.buy.execute(interaction); + } + + if (options?.getSubcommand() === "cancel") { + await modules.cancel.execute(interaction); + } +}; diff --git a/src/plugins/shop/modules/roles/modules/buy.ts b/src/plugins/commands/shop/modules/roles/modules/buy/index.ts similarity index 90% rename from src/plugins/shop/modules/roles/modules/buy.ts rename to src/plugins/commands/shop/modules/roles/modules/buy/index.ts index 4e4528d..23f91dc 100644 --- a/src/plugins/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 "@schemas/shopRole"; -import guildSchema from "@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/plugins/shop/modules/roles/modules/cancel.ts b/src/plugins/commands/shop/modules/roles/modules/cancel/index.ts similarity index 89% rename from src/plugins/shop/modules/roles/modules/cancel.ts rename to src/plugins/commands/shop/modules/roles/modules/cancel/index.ts index 546dbc7..5ff483e 100644 --- a/src/plugins/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 "@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/plugins/shop/modules/roles/modules/index.ts b/src/plugins/commands/shop/modules/roles/modules/index.ts similarity index 100% rename from src/plugins/shop/modules/roles/modules/index.ts rename to src/plugins/commands/shop/modules/roles/modules/index.ts diff --git a/src/plugins/commands/utility/index.ts b/src/plugins/commands/utility/index.ts new file mode 100644 index 0000000..43da584 --- /dev/null +++ b/src/plugins/commands/utility/index.ts @@ -0,0 +1,31 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +import modules from "./modules"; +export const moduleData = modules; + +export const builder = new SlashCommandBuilder() + .setName("utility") + .setDescription("Common utility.") + + .addSubcommand(modules.about.builder) + .addSubcommand(modules.stats.builder) + .addSubcommand(modules.avatar.builder) + .addSubcommand(modules.ping.builder); + +export const execute = async (interaction: CommandInteraction) => { + switch (interaction.options.getSubcommand()) { + case "about": + return modules.about.execute(interaction); + case "stats": + return modules.stats.execute(interaction); + case "avatar": + return modules.avatar.execute(interaction); + case "ping": + return modules.ping.execute(interaction); + default: + 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..92af9cb --- /dev/null +++ b/src/plugins/commands/utility/modules/about/index.ts @@ -0,0 +1,76 @@ +// 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("Documentation") + .setStyle("LINK") + .setEmoji("๐Ÿ“š") + .setURL("https://xyter.zyner.org"), + 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/plugins/utility/modules/avatar.ts b/src/plugins/commands/utility/modules/avatar/index.ts similarity index 92% rename from src/plugins/utility/modules/avatar.ts rename to src/plugins/commands/utility/modules/avatar/index.ts index b060be3..24ab6c1 100644 --- a/src/plugins/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/plugins/commands/utility/modules/index.ts b/src/plugins/commands/utility/modules/index.ts new file mode 100644 index 0000000..33fa0f5 --- /dev/null +++ b/src/plugins/commands/utility/modules/index.ts @@ -0,0 +1,11 @@ +import avatar from "./avatar"; +import about from "./about"; +import stats from "./stats"; +import ping from "./ping"; + +export default { + avatar, + about, + stats, + ping, +}; diff --git a/src/plugins/commands/utility/modules/ping/index.ts b/src/plugins/commands/utility/modules/ping/index.ts new file mode 100644 index 0000000..420329e --- /dev/null +++ b/src/plugins/commands/utility/modules/ping/index.ts @@ -0,0 +1,46 @@ +// Dependencies +import { CommandInteraction } from "discord.js"; + +// Configurations +import getEmbedConfig from "../../../../../helpers/getEmbedConfig"; + +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + metadata: { guildOnly: false, ephemeral: false }, + + builder: (command: SlashCommandSubcommandBuilder) => { + return command.setName("ping").setDescription("Ping this bot"); + }, + execute: async (interaction: CommandInteraction) => { + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const interactionEmbed = { + title: "[:tools:] Ping", + fields: [ + { + name: "๐Ÿ“ฆ Deliver Latency", + value: `${Math.abs(Date.now() - interaction.createdTimestamp)} ms`, + inline: true, + }, + { + name: "๐Ÿค– API Latency", + value: `${Math.round(interaction.client.ws.ping)} ms`, + inline: true, + }, + ], + color: successColor, + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, + }; + await interaction.editReply({ + embeds: [interactionEmbed], + }); + }, +}; diff --git a/src/plugins/utility/modules/stats.ts b/src/plugins/commands/utility/modules/stats/index.ts similarity index 95% rename from src/plugins/utility/modules/stats.ts rename to src/plugins/commands/utility/modules/stats/index.ts index a7f44a0..a2a8282 100644 --- a/src/plugins/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/plugins/config/index.ts b/src/plugins/config/index.ts deleted file mode 100644 index 9cf1ab4..0000000 --- a/src/plugins/config/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "./modules"; - -// Handlers -import logger from "@logger"; - -// Function -export default { - modules, - - 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), - - async execute(interaction: CommandInteraction) { - // Destructure member - const { options } = interaction; - - switch (options?.getSubcommand()) { - case "pterodactyl": - logger?.silly(`Subcommand is pterodactyl`); - - return modules.pterodactyl.execute(interaction); - case "credits": - logger?.silly(`Subcommand is credits`); - - return modules.credits.execute(interaction); - case "points": - logger?.silly(`Subcommand is points`); - - return modules.points.execute(interaction); - case "welcome": - logger?.silly(`Subcommand is welcome`); - - return modules.welcome.execute(interaction); - case "audits": - logger?.silly(`Subcommand is audits`); - - return modules.audits.execute(interaction); - case "shop": - logger?.silly(`Subcommand is shop`); - - return modules.shop.execute(interaction); - case "embeds": - logger?.silly(`Subcommand is shop`); - - return modules.embeds.execute(interaction); - default: - logger?.silly(`Subcommand is not found`); - } - }, -}; diff --git a/src/plugins/config/modules/audits.ts b/src/plugins/config/modules/audits.ts deleted file mode 100644 index 817c990..0000000 --- a/src/plugins/config/modules/audits.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 "@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/plugins/config/modules/credits.ts b/src/plugins/config/modules/credits.ts deleted file mode 100644 index 3e20851..0000000 --- a/src/plugins/config/modules/credits.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 "@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/plugins/config/modules/embeds.ts b/src/plugins/config/modules/embeds.ts deleted file mode 100644 index f4f3a15..0000000 --- a/src/plugins/config/modules/embeds.ts +++ /dev/null @@ -1,129 +0,0 @@ -// Dependencies -import { ColorResolvable, CommandInteraction, Permissions } from "discord.js"; - -//Handlers -import logger from "@logger"; - -// Models -import guildSchema from "@schemas/guild"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import getEmbedConfig from "@helpers/getEmbedConfig"; - -// 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) => { - // Destructure member - const { guild, options } = interaction; - - if (guild == null) return; - - const embedConfig = await getEmbedConfig(guild); - - if (embedConfig == null) return; - - logger.info(embedConfig); - - // Get options - const successColor = options?.getString("success-color") as ColorResolvable; - const waitColor = options?.getString("wait-color") as ColorResolvable; - const errorColor = options?.getString("error-color") as ColorResolvable; - const footerIcon = options?.getString("footer-icon"); - const footerText = options?.getString("footer-text"); - - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) { - return logger?.silly(`Guild is null`); - } - - // Modify values - guildDB.embeds.successColor = - successColor !== null ? successColor : guildDB?.embeds?.successColor; - guildDB.embeds.waitColor = - waitColor !== null ? waitColor : guildDB?.embeds?.waitColor; - guildDB.embeds.errorColor = - errorColor !== null ? errorColor : guildDB?.embeds?.errorColor; - guildDB.embeds.footerIcon = - footerIcon !== null ? footerIcon : guildDB?.embeds?.footerIcon; - guildDB.embeds.footerText = - footerText !== null ? footerText : guildDB?.embeds?.footerText; - - // 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 || embedConfig.successColor, - fields: [ - { - name: "๐Ÿค– Success Color", - value: `${guildDB?.embeds?.successColor}`, - inline: true, - }, - { - name: "๐Ÿ“ˆ Wait Color", - value: `${guildDB?.embeds?.waitColor}`, - inline: true, - }, - { - name: "๐Ÿ“ˆ Error Color", - value: `${guildDB?.embeds?.errorColor}`, - inline: true, - }, - { - name: "๐Ÿ”จ Footer Icon", - value: `${guildDB?.embeds?.footerIcon}`, - inline: true, - }, - { - name: "โฐ Footer Text", - value: `${guildDB?.embeds?.footerText}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon || embedConfig.footerIcon, - text: footerText || embedConfig.footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/config/modules/index.ts b/src/plugins/config/modules/index.ts deleted file mode 100644 index e14701a..0000000 --- a/src/plugins/config/modules/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import audits from "@plugins/config/modules/audits"; -import credits from "@plugins/config/modules/credits"; -import points from "@plugins/config/modules/points"; -import pterodactyl from "@plugins/config/modules/pterodactyl"; -import shop from "@plugins/config/modules/shop"; -import welcome from "@plugins/config/modules/welcome"; -import embeds from "@plugins/config/modules/embeds"; - -export default { audits, credits, points, pterodactyl, shop, welcome, embeds }; diff --git a/src/plugins/config/modules/points.ts b/src/plugins/config/modules/points.ts deleted file mode 100644 index a7e24a2..0000000 --- a/src/plugins/config/modules/points.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 "@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/plugins/config/modules/pterodactyl.ts b/src/plugins/config/modules/pterodactyl.ts deleted file mode 100644 index b7162d6..0000000 --- a/src/plugins/config/modules/pterodactyl.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Dependencies -import { CommandInteraction, Permissions } from "discord.js"; - -// Configurations -import getEmbedConfig from "@helpers/getEmbedConfig"; - -// Handlers -import logger from "@logger"; - -// Models -import apiSchema from "@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 url = options?.getString("url"); - const token = encryption.encrypt(options?.getString("token")); - - // 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/plugins/config/modules/shop.ts b/src/plugins/config/modules/shop.ts deleted file mode 100644 index e9f4d9f..0000000 --- a/src/plugins/config/modules/shop.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 "@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/plugins/config/modules/welcome.ts b/src/plugins/config/modules/welcome.ts deleted file mode 100644 index 48399c7..0000000 --- a/src/plugins/config/modules/welcome.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 "@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.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - Guild [Welcome]", - description: `Welcome settings updated.`, - color: successColor, - fields: [ - { - name: "๐Ÿค– Status", - value: `${guildDB?.welcome?.status}`, - inline: true, - }, - { - name: "๐ŸŒŠ Join Channel", - value: `${guildDB?.welcome?.joinChannel}`, - inline: true, - }, - { - name: "๐ŸŒŠ Leave Channel", - value: `${guildDB?.welcome?.leaveChannel}`, - inline: true, - }, - { - name: "๐Ÿ“„ Join Channel Message", - value: `${guildDB?.welcome?.joinChannelMessage}`, - inline: true, - }, - { - name: "๐Ÿ“„ Leave Channel Message", - value: `${guildDB?.welcome?.leaveChannelMessage}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); - }, -}; diff --git a/src/plugins/counters/index.ts b/src/plugins/counters/index.ts deleted file mode 100644 index ca3fc56..0000000 --- a/src/plugins/counters/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CommandInteraction } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; -import logger from "@logger"; - -import modules from "@plugins/counters/modules"; - -export default { - modules, - - builder: new SlashCommandBuilder() - .setName("counters") - .setDescription("View guild counters") - - .addSubcommand(modules.view.builder), - - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - if (options.getSubcommand() === "view") { - logger.silly(`Executing view subcommand`); - return modules.view.execute(interaction); - } - - logger.silly(`Unknown subcommand ${options.getSubcommand()}`); - }, -}; diff --git a/src/plugins/counters/modules/index.ts b/src/plugins/counters/modules/index.ts deleted file mode 100644 index 8c5ea76..0000000 --- a/src/plugins/counters/modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import view from "@plugins/counters/modules/view"; - -export default { view }; diff --git a/src/plugins/credits/index.ts b/src/plugins/credits/index.ts deleted file mode 100644 index 5e8033f..0000000 --- a/src/plugins/credits/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; -import logger from "@logger"; - -import modules from "@plugins/credits/modules"; - -export default { - modules, - - builder: new SlashCommandBuilder() - .setName("credits") - .setDescription("Manage your credits.") - - .addSubcommand(modules.balance.builder) - .addSubcommand(modules.gift.builder) - .addSubcommand(modules.top.builder) - .addSubcommand(modules.work.builder), - - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - switch (options.getSubcommand()) { - case "balance": - await modules.balance.execute(interaction); - break; - case "gift": - await modules.gift.execute(interaction); - break; - case "top": - await modules.top.execute(interaction); - break; - case "work": - await modules.work.execute(interaction); - break; - default: - logger.silly(`Unknown subcommand ${options.getSubcommand()}`); - } - }, -}; diff --git a/src/plugins/credits/modules/index.ts b/src/plugins/credits/modules/index.ts deleted file mode 100644 index 9b144f2..0000000 --- a/src/plugins/credits/modules/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import balance from "@plugins/credits/modules/balance"; -import gift from "@plugins/credits/modules/gift"; -import top from "@plugins/credits/modules/top"; -import work from "@plugins/credits/modules/work"; - -export default { balance, gift, top, work }; diff --git a/src/plugins/events/guildCreate/index.ts b/src/plugins/events/guildCreate/index.ts new file mode 100644 index 0000000..60d5a3c --- /dev/null +++ b/src/plugins/events/guildCreate/index.ts @@ -0,0 +1,20 @@ +import { Guild } from "discord.js"; +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", +}; + +export const execute = async (guild: Guild) => { + const { client } = guild; + + logger?.silly(`Added to guild: ${guild.name} (${guild.id})`); + + await fetchGuild(guild); + await updatePresence(client); + + logger.silly(`guildCreate: ${guild}`); +}; diff --git a/src/plugins/events/guildDelete/index.ts b/src/plugins/events/guildDelete/index.ts new file mode 100644 index 0000000..146351f --- /dev/null +++ b/src/plugins/events/guildDelete/index.ts @@ -0,0 +1,23 @@ +// 3rd party dependencies +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"; + +export const options: IEventOptions = { + type: "on", +}; + +export const execute = async (guild: Guild) => { + const { client } = guild; + + logger?.silly(`Deleted from guild: ${guild.name} (${guild.id})`); + + await dropGuild(guild); + await updatePresence(client); + + logger.silly(`guildDelete: ${guild}`); +}; diff --git a/src/plugins/events/guildMemberAdd/audits.ts b/src/plugins/events/guildMemberAdd/audits.ts new file mode 100644 index 0000000..9f94282 --- /dev/null +++ b/src/plugins/events/guildMemberAdd/audits.ts @@ -0,0 +1,62 @@ +import logger from "../../../logger"; +import { GuildMember, MessageEmbed } 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/plugins/events/guildMemberAdd/index.ts b/src/plugins/events/guildMemberAdd/index.ts new file mode 100644 index 0000000..8310579 --- /dev/null +++ b/src/plugins/events/guildMemberAdd/index.ts @@ -0,0 +1,29 @@ +// 3rd party dependencies +import { GuildMember } from "discord.js"; + +// Dependencies +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", +}; + +export const execute = async (member: GuildMember) => { + const { client, user, guild } = member; + + logger.silly( + `New member: ${user.tag} (${user.id}) added to guild: ${guild.name} (${guild.id})` + ); + + await audits.execute(member); + await joinMessage.execute(member); + await fetchUser(user, guild); + await updatePresence(client); +}; 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 bf7aec3..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 "@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..5ce4849 --- /dev/null +++ b/src/plugins/events/guildMemberRemove/audits.ts @@ -0,0 +1,62 @@ +import logger from "../../../logger"; +import { GuildMember, MessageEmbed } 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/plugins/events/guildMemberRemove/index.ts b/src/plugins/events/guildMemberRemove/index.ts new file mode 100644 index 0000000..b900a12 --- /dev/null +++ b/src/plugins/events/guildMemberRemove/index.ts @@ -0,0 +1,27 @@ +// 3rd party dependencies +import { GuildMember } from "discord.js"; + +// Dependencies +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"; + +export const options: IEventOptions = { + type: "on", +}; + +export const execute = async (member: GuildMember) => { + const { client, user, guild } = member; + + logger?.silly( + `Removed member: ${user.tag} (${user.id}) from guild: ${guild.name} (${guild.id})` + ); + + await audits.execute(member); + await leaveMessage.execute(member); + await dropUser(user, guild); + await updatePresence(client); +}; 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 efed730..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 "@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 83% rename from src/events/interactionCreate/audits.ts rename to src/plugins/events/interactionCreate/audits.ts index c6df788..183ff5a 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 "@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, @@ -50,7 +52,7 @@ export default { ], }) .then(async () => { - logger.info( + logger.debug( `Audit log sent for event interactionCreate in guild ${interaction?.guild?.name} (${interaction?.guild?.id})` ); }) diff --git a/src/plugins/events/interactionCreate/handlers/button/index.ts b/src/plugins/events/interactionCreate/handlers/button/index.ts new file mode 100644 index 0000000..3373f09 --- /dev/null +++ b/src/plugins/events/interactionCreate/handlers/button/index.ts @@ -0,0 +1,36 @@ +// Dependencies +import { Interaction } from "discord.js"; + +import deferReply from "../../../../../helpers/deferReply"; +import * as cooldown from "../../../../../helpers/cooldown"; + +export default async (interaction: Interaction) => { + if (!interaction.isButton()) return; + + const { guild, customId, memberPermissions } = interaction; + + const currentButton = await import(`../../../buttons/${customId}`); + + if (!currentButton) throw new Error(`Unknown button ${customId}`); + + const metadata = currentButton.metadata; + + await deferReply(interaction, metadata.ephemeral || false); + + if (metadata.guildOnly && !guild) + throw new Error("This command is guild only."); + + if ( + metadata.permissions && + metadata.guildOnly && + !memberPermissions?.has(metadata.permissions) + ) + throw new Error("You don't have the required permissions"); + + if (metadata.dmOnly && guild) + throw new Error("This command is only available in DM"); + + if (metadata.cooldown) await cooldown.button(interaction, metadata.cooldown); + + await currentButton.execute(interaction); +}; diff --git a/src/plugins/events/interactionCreate/handlers/command/index.ts b/src/plugins/events/interactionCreate/handlers/command/index.ts new file mode 100644 index 0000000..b6b30ec --- /dev/null +++ b/src/plugins/events/interactionCreate/handlers/command/index.ts @@ -0,0 +1,34 @@ +// Dependencies +import { Interaction } from "discord.js"; + +import deferReply from "../../../../../helpers/deferReply"; +import getCommandMetadata from "../../../../../helpers/getCommandMetadata"; +import * as cooldown from "../../../../../helpers/cooldown"; + +export default async (interaction: Interaction) => { + if (!interaction.isCommand()) return; + const { client, commandName } = interaction; + + const currentCommand = client.commands.get(commandName); + if (!currentCommand) throw new Error(`Unknown command ${commandName}`); + + const metadata = await getCommandMetadata(interaction, currentCommand); + await deferReply(interaction, metadata.ephemeral || false); + + if (metadata.guildOnly && !interaction.guild) + throw new Error("This command is guild only."); + + if ( + metadata.permissions && + metadata.guildOnly && + !interaction.memberPermissions?.has(metadata.permissions) + ) + throw new Error("You don't have the required permissions"); + + if (metadata.dmOnly && interaction.guild) + throw new Error("This command is only available in DM"); + + if (metadata.cooldown) await cooldown.command(interaction, metadata.cooldown); + + await currentCommand.execute(interaction); +}; diff --git a/src/plugins/events/interactionCreate/handlers/index.ts b/src/plugins/events/interactionCreate/handlers/index.ts new file mode 100644 index 0000000..85f1e7a --- /dev/null +++ b/src/plugins/events/interactionCreate/handlers/index.ts @@ -0,0 +1,9 @@ +import { Interaction } from "discord.js"; + +import button from "./button"; +import command from "./command"; + +export const execute = async (interaction: Interaction) => { + await button(interaction); + await command(interaction); +}; diff --git a/src/plugins/events/interactionCreate/index.ts b/src/plugins/events/interactionCreate/index.ts new file mode 100644 index 0000000..aa6e763 --- /dev/null +++ b/src/plugins/events/interactionCreate/index.ts @@ -0,0 +1,48 @@ +// 3rd party dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +// Dependencies +import * as handlers from "./handlers"; + +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); + + await handlers.execute(interaction).catch(async (err) => { + logger.debug(`${err}`); + + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle( + `[:x:] ${capitalizeFirstLetter( + interaction.options.getSubcommand() + )}` + ) + .setDescription(`${"``"}${err}${"``"}`) + .setColor(errorColor) + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); +}; diff --git a/src/plugins/events/messageCreate/index.ts b/src/plugins/events/messageCreate/index.ts new file mode 100644 index 0000000..06619f0 --- /dev/null +++ b/src/plugins/events/messageCreate/index.ts @@ -0,0 +1,14 @@ +import { Message } from "discord.js"; +import modules from "../../events/messageCreate/modules"; + +import { IEventOptions } from "../../../interfaces/EventOptions"; + +export const options: IEventOptions = { + type: "on", +}; + +export const execute = async (message: Message) => { + await modules.credits.execute(message); + await modules.points.execute(message); + await modules.counters.execute(message); +}; 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 a65a4db..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 "@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/plugins/events/messageCreate/modules/index.ts b/src/plugins/events/messageCreate/modules/index.ts new file mode 100644 index 0000000..aff2dd3 --- /dev/null +++ b/src/plugins/events/messageCreate/modules/index.ts @@ -0,0 +1,9 @@ +import counters from "./counters"; +import credits from "./credits"; +import points from "./points"; + +export default { + counters, + credits, + points, +}; 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..ed18003 --- /dev/null +++ b/src/plugins/events/messageCreate/modules/points/index.ts @@ -0,0 +1,48 @@ +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 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 1f37b59..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 "@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/plugins/events/messageDelete/index.ts b/src/plugins/events/messageDelete/index.ts new file mode 100644 index 0000000..3cb7f6e --- /dev/null +++ b/src/plugins/events/messageDelete/index.ts @@ -0,0 +1,13 @@ +import { Message } from "discord.js"; +import audits from "../../events/messageDelete/audits"; +import counter from "./modules/counter"; +import { IEventOptions } from "../../../interfaces/EventOptions"; + +export const options: IEventOptions = { + type: "on", +}; + +export const execute = async (message: Message) => { + await audits.execute(message); + await counter(message); +}; 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 6169b45..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 "@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 c9bed8a..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 "@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/plugins/events/messageUpdate/index.ts b/src/plugins/events/messageUpdate/index.ts new file mode 100644 index 0000000..9c9bf47 --- /dev/null +++ b/src/plugins/events/messageUpdate/index.ts @@ -0,0 +1,27 @@ +// Dependencies +import { Message } from "discord.js"; +import logger from "../../../logger"; + +// Modules +import counter from "./modules/counter"; + +import audits from "./audits"; +import { IEventOptions } from "../../../interfaces/EventOptions"; + +export const options: IEventOptions = { + type: "on", +}; + +export const execute = async (oldMessage: Message, newMessage: Message) => { + const { author, guild } = newMessage; + + await audits.execute(oldMessage, newMessage); + + logger?.silly( + `Message update event fired by ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` + ); + + if (author?.bot) return logger?.silly(`Message update event fired by bot`); + + await counter(newMessage); +}; diff --git a/src/events/messageUpdate/modules/counter.ts b/src/plugins/events/messageUpdate/modules/counter.ts similarity index 86% rename from src/events/messageUpdate/modules/counter.ts rename to src/plugins/events/messageUpdate/modules/counter.ts index 465b00a..4a01749 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 "@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; @@ -29,7 +29,7 @@ export default async (message: Message) => { await channel?.send(`${author} said **${word}**.`); logger?.silly(`${author} said ${word} in ${channel}`); }) - ?.catch(async (error: any) => { + ?.catch(async (error) => { logger?.error(error); }); }; diff --git a/src/plugins/events/rateLimit/index.ts b/src/plugins/events/rateLimit/index.ts new file mode 100644 index 0000000..ca9aab5 --- /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 (${client?.user?.tag}) is rate-limited!`); +}; diff --git a/src/plugins/events/ready/index.ts b/src/plugins/events/ready/index.ts new file mode 100644 index 0000000..125bdc8 --- /dev/null +++ b/src/plugins/events/ready/index.ts @@ -0,0 +1,21 @@ +// Dependencies +import { Client } from "discord.js"; +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"; + +export const options: IEventOptions = { + type: "once", +}; + +export const execute = async (client: Client) => { + logger.info("Discord's API client is ready!"); + + await updatePresence(client); + await devMode(client); + await deployCommands(client); +}; diff --git a/src/plugins/fun/index.ts b/src/plugins/fun/index.ts deleted file mode 100644 index 9ba8e81..0000000 --- a/src/plugins/fun/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; -import logger from "@logger"; - -import modules from "@plugins/fun/modules"; - -export default { - modules, - - builder: new SlashCommandBuilder() - .setName("fun") - .setDescription("Fun commands.") - - .addSubcommand(modules.meme.builder), - - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - if (options.getSubcommand() === "meme") { - await modules.meme.execute(interaction); - } else { - logger.silly(`Unknown subcommand ${options.getSubcommand()}`); - } - }, -}; diff --git a/src/plugins/fun/modules/index.ts b/src/plugins/fun/modules/index.ts deleted file mode 100644 index 2b59097..0000000 --- a/src/plugins/fun/modules/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import meme from "@plugins/fun/modules/meme"; - -export default { - meme, -}; diff --git a/src/plugins/fun/modules/meme.ts b/src/plugins/fun/modules/meme.ts deleted file mode 100644 index be62672..0000000 --- a/src/plugins/fun/modules/meme.ts +++ /dev/null @@ -1,41 +0,0 @@ -import getEmbedConfig from "@helpers/getEmbedConfig"; - -import axios from "axios"; -import { CommandInteraction, MessageEmbed } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -import logger from "@logger"; - -export default { - metadata: { guildOnly: false, ephemeral: false }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command.setName("meme").setDescription("Get a meme from r/memes)"); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { successColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - await axios - .get("https://www.reddit.com/r/memes/random/.json") - .then(async (res) => { - const response = res.data[0].data.children; - const content = response[0].data; - - const embed = new MessageEmbed() - .setTitle(content.title) - .setTimestamp(new Date()) - .setImage(content.url) - .setFooter({ - text: `๐Ÿ‘ ${content.ups}๏ธฑ๐Ÿ‘Ž ${content.downs}\n${footerText}`, - iconURL: footerIcon, - }) - .setColor(successColor); - - return interaction.editReply({ embeds: [embed] }); - }) - .catch((error) => { - logger.error(`${error}`); - }); - }, -}; diff --git a/src/plugins/manage/index.ts b/src/plugins/manage/index.ts deleted file mode 100644 index 993ba9f..0000000 --- a/src/plugins/manage/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -//Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Groups -import modules from "@plugins/manage/modules"; -import logger from "@logger"; - -// Function -export default { - modules, - - builder: new SlashCommandBuilder() - .setName("manage") - .setDescription("Manage the bot.") - .addSubcommandGroup(modules.counters.builder) - .addSubcommandGroup(modules.credits.builder), - - async execute(interaction: CommandInteraction) { - // Destructure - const { options } = interaction; - - if (options?.getSubcommandGroup() === "credits") { - logger?.silly(`Subcommand group is credits`); - - return modules.credits.execute(interaction); - } - - if (options?.getSubcommandGroup() === "counters") { - logger?.silly(`Subcommand group is counters`); - - return modules.counters.execute(interaction); - } - - logger?.silly(`Subcommand group is not credits or counters`); - }, -}; diff --git a/src/plugins/manage/modules/counters/index.ts b/src/plugins/manage/modules/counters/index.ts deleted file mode 100644 index 3fb9d3e..0000000 --- a/src/plugins/manage/modules/counters/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Dependencies -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -import logger from "@logger"; - -// Modules -import modules from "./modules"; - -// Function -export default { - modules, - - builder: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("counters") - .setDescription("Manage guild counters.") - .addSubcommand(modules.add.builder) - .addSubcommand(modules.remove.builder); - }, - - execute: async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options?.getSubcommand() === "add") { - logger?.silly(`Executing create subcommand`); - - return modules.add.execute(interaction); - } - - if (options?.getSubcommand() === "remove") { - logger?.silly(`Executing delete subcommand`); - - return modules.remove.execute(interaction); - } - - logger?.silly(`Unknown subcommand ${options?.getSubcommand()}`); - }, -}; diff --git a/src/plugins/manage/modules/counters/modules/index.ts b/src/plugins/manage/modules/counters/modules/index.ts deleted file mode 100644 index 2f55183..0000000 --- a/src/plugins/manage/modules/counters/modules/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import add from "@plugins/manage/modules/counters/modules/add"; -import remove from "@plugins/manage/modules/counters/modules/remove"; - -export default { add, remove }; diff --git a/src/plugins/manage/modules/credits/index.ts b/src/plugins/manage/modules/credits/index.ts deleted file mode 100644 index a12cf11..0000000 --- a/src/plugins/manage/modules/credits/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CommandInteraction } from "discord.js"; -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; -import logger from "@logger"; - -import modules from "./modules"; - -export default { - modules, - - builder: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("credits") - .setDescription("Manage the credits of a user.") - .addSubcommand(modules.give.builder) - .addSubcommand(modules.set.builder) - .addSubcommand(modules.take.builder) - .addSubcommand(modules.transfer.builder); - }, - execute: async (interaction: CommandInteraction) => { - const { options } = interaction; - - switch (options.getSubcommand()) { - case "give": - logger.silly(`Executing give subcommand`); - - return modules.give.execute(interaction); - case "set": - logger.silly(`Executing set subcommand`); - - return modules.set.execute(interaction); - case "take": - logger.silly(`Executing take subcommand`); - - return modules.take.execute(interaction); - case "transfer": - logger.silly(`Executing transfer subcommand`); - - return modules.transfer.execute(interaction); - default: - logger.silly(`Unknown subcommand ${options.getSubcommand()}`); - } - }, -}; diff --git a/src/plugins/manage/modules/credits/modules/index.ts b/src/plugins/manage/modules/credits/modules/index.ts deleted file mode 100644 index 1dd2f3d..0000000 --- a/src/plugins/manage/modules/credits/modules/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import give from "@plugins/manage/modules/credits/modules/give"; -import set from "@plugins/manage/modules/credits/modules/set"; -import take from "@plugins/manage/modules/credits/modules/take"; -import transfer from "@plugins/manage/modules/credits/modules/transfer"; - -export default { give, set, take, transfer }; diff --git a/src/plugins/manage/modules/index.ts b/src/plugins/manage/modules/index.ts deleted file mode 100644 index 61797b1..0000000 --- a/src/plugins/manage/modules/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import counters from "@plugins/manage/modules/counters"; -import credits from "@plugins/manage/modules/credits"; - -export default { counters, credits }; diff --git a/src/plugins/profile/index.ts b/src/plugins/profile/index.ts deleted file mode 100644 index d37ad1b..0000000 --- a/src/plugins/profile/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "@plugins/profile/modules"; - -// Handlers -import logger from "@logger"; - -// Function -export default { - modules, - - builder: new SlashCommandBuilder() - .setName("profile") - .setDescription("Check a profile.") - .addSubcommand(modules.view.builder), - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - if (options?.getSubcommand() === "view") { - logger?.silly(`Executing view subcommand`); - - return modules.view.execute(interaction); - } - - logger?.silly(`No subcommand found`); - }, -}; diff --git a/src/plugins/profile/modules/index.ts b/src/plugins/profile/modules/index.ts deleted file mode 100644 index 1dc8e1b..0000000 --- a/src/plugins/profile/modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import view from "@plugins/profile/modules/view"; - -export default { view }; diff --git a/src/plugins/reputation/index.ts b/src/plugins/reputation/index.ts deleted file mode 100644 index f5237ab..0000000 --- a/src/plugins/reputation/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "./modules"; - -// Handlers -import logger from "@logger"; - -// Function -export default { - modules, - builder: new SlashCommandBuilder() - .setName("reputation") - .setDescription("Manage reputation.") - .addSubcommand(modules.give.builder), - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - if (options?.getSubcommand() === "give") { - logger?.silly(`Executing give subcommand`); - - await modules.give.execute(interaction); - } - - logger?.silly(`No subcommand found`); - }, -}; diff --git a/src/plugins/reputation/modules/give.ts b/src/plugins/reputation/modules/give.ts deleted file mode 100644 index 949f9c9..0000000 --- a/src/plugins/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 "@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/plugins/reputation/modules/index.ts b/src/plugins/reputation/modules/index.ts deleted file mode 100644 index f6746fd..0000000 --- a/src/plugins/reputation/modules/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import give from "@plugins/reputation/modules/give"; - -export default { give }; diff --git a/src/plugins/shop/index.ts b/src/plugins/shop/index.ts deleted file mode 100644 index 330306e..0000000 --- a/src/plugins/shop/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "./modules"; - -// Handlers -import logger from "../../logger"; - -// Function -export default { - modules, - - builder: new SlashCommandBuilder() - .setName("shop") - .setDescription("Shop for credits and custom roles.") - .addSubcommand(modules.pterodactyl.builder) - .addSubcommandGroup(modules.roles.builder), - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - if (options?.getSubcommand() === "pterodactyl") { - logger.silly(`Executing pterodactyl subcommand`); - - return modules.pterodactyl.execute(interaction); - } - - if (options?.getSubcommandGroup() === "roles") { - logger?.silly(`Subcommand group is roles`); - - return modules.roles.execute(interaction); - } - - logger?.silly(`No subcommand found.`); - }, -}; diff --git a/src/plugins/shop/modules/index.ts b/src/plugins/shop/modules/index.ts deleted file mode 100644 index c356cae..0000000 --- a/src/plugins/shop/modules/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import pterodactyl from "@plugins/shop/modules/pterodactyl"; -import roles from "@plugins/shop/modules/roles"; - -export default { pterodactyl, roles }; diff --git a/src/plugins/shop/modules/roles/index.ts b/src/plugins/shop/modules/roles/index.ts deleted file mode 100644 index 33ece41..0000000 --- a/src/plugins/shop/modules/roles/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Dependencies -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Handlers -import logger from "@logger"; - -import getEmbedConfig from "@helpers/getEmbedConfig"; - -// Modules -import modules from "./modules"; - -import guildSchema from "@schemas/guild"; - -// Function -export default { - modules, - - builder: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("roles") - .setDescription("Shop for custom roles.") - .addSubcommand(modules.buy.builder) - .addSubcommand(modules.cancel.builder); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { errorColor, footerText, footerIcon } = await getEmbedConfig( - interaction.guild - ); - const { options, guild } = interaction; - - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - if (guildDB === null) return; - - if (!guildDB.shop.roles.status) { - logger.silly(`Shop roles disabled.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":dollar: Shop - Roles", - description: "This server has disabled shop roles.", - color: errorColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - } - - if (options?.getSubcommand() === "buy") { - logger.silly(`Executing buy subcommand`); - - await modules.buy.execute(interaction); - } - - if (options?.getSubcommand() === "cancel") { - logger.silly(`Executing cancel subcommand`); - - await modules.cancel.execute(interaction); - } - }, -}; diff --git a/src/plugins/utility/index.ts b/src/plugins/utility/index.ts deleted file mode 100644 index 6dc6fff..0000000 --- a/src/plugins/utility/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import modules from "@plugins/utility/modules"; - -// Handlers -import logger from "../../logger"; - -// Function -export default { - modules, - - builder: new SlashCommandBuilder() - .setName("utility") - .setDescription("Common utility.") - - .addSubcommand(modules.lookup.builder) - .addSubcommand(modules.about.builder) - .addSubcommand(modules.stats.builder) - .addSubcommand(modules.avatar.builder), - - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - switch (options.getSubcommand()) { - case "lookup": - return modules.lookup.execute(interaction); - case "about": - return modules.about.execute(interaction); - case "stats": - return modules.stats.execute(interaction); - case "avatar": - return modules.avatar.execute(interaction); - default: - logger.error(`Unknown subcommand ${options.getSubcommand()}`); - } - }, -}; diff --git a/src/plugins/utility/modules/about.ts b/src/plugins/utility/modules/about.ts deleted file mode 100644 index 8011d51..0000000 --- a/src/plugins/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/plugins/utility/modules/index.ts b/src/plugins/utility/modules/index.ts deleted file mode 100644 index 80c5a64..0000000 --- a/src/plugins/utility/modules/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import avatar from "@plugins/utility/modules/avatar"; -import about from "@plugins/utility/modules/about"; -import lookup from "@plugins/utility/modules/lookup"; -import stats from "@plugins/utility/modules/stats"; - -export default { - avatar, - about, - lookup, - stats, -}; diff --git a/src/plugins/utility/modules/lookup.ts b/src/plugins/utility/modules/lookup.ts deleted file mode 100644 index 241b7ce..0000000 --- a/src/plugins/utility/modules/lookup.ts +++ /dev/null @@ -1,122 +0,0 @@ -import axios from "axios"; -import { CommandInteraction } from "discord.js"; - -import getEmbedConfig from "@helpers/getEmbedConfig"; - -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -import embedBuilder from "@root/helpers/embedBuilder"; - -export default { - metadata: { guildOnly: false, ephemeral: false }, - - builder: (command: SlashCommandSubcommandBuilder) => { - return command - .setName("lookup") - .setDescription( - "Lookup a domain or ip. (Request sent over HTTP, proceed with caution!)" - ) - .addStringOption((option) => - option - .setName("query") - .setDescription("The query you want to look up.") - .setRequired(true) - ); - }, - execute: async (interaction: CommandInteraction) => { - if (interaction.guild == null) return; - const { errorColor, successColor, footerText, footerIcon } = - await getEmbedConfig(interaction.guild); - const embedTitle = "[:hammer:] Utility (Lookup)"; - - embedBuilder.setTitle(embedTitle); - - const { options } = interaction; - const query = options.getString("query"); - - await axios - .get(`http://ip-api.com/json/${query}`) - .then(async (response) => { - if (response.data.status !== "success") { - await interaction.editReply({ - embeds: [ - embedBuilder - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }) - .setDescription( - `${response?.data?.message}: ${response?.data?.query}` - ), - ], - }); - return; - } - - await interaction.editReply({ - embeds: [ - embedBuilder.setColor(successColor).setFields([ - { - name: ":classical_building: AS", - value: `${response.data.as || "Unknown"}`, - inline: true, - }, - { - name: ":classical_building: ISP", - value: `${response.data.isp || "Unknown"}`, - inline: true, - }, - { - name: ":classical_building: Organization", - value: `${response.data.org || "Unknown"}`, - inline: true, - }, - { - name: ":compass: Latitude", - value: `${response.data.lat || "Unknown"}`, - inline: true, - }, - { - name: ":compass: Longitude", - value: `${response.data.lon || "Unknown"}`, - inline: true, - }, - { - name: ":clock4: Timezone", - value: `${response.data.timezone || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: Country", - value: `${response.data.country || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: Region", - value: `${response.data.regionName || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: City", - value: `${response.data.city || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: Country Code", - value: `${response.data.countryCode || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: Region Code", - value: `${response.data.region || "Unknown"}`, - inline: true, - }, - { - name: ":globe_with_meridians: ZIP", - value: `${response.data.zip || "Unknown"}`, - inline: true, - }, - ]), - ], - }); - }); - }, -}; diff --git a/src/types/common/discord.d.ts b/src/types/common/discord.d.ts index 1e4926f..b354c9d 100644 --- a/src/types/common/discord.d.ts +++ b/src/types/common/discord.d.ts @@ -1,7 +1,9 @@ -import { Collection, Client as DJSClient } from "discord.js"; +import { Collection } from "discord.js"; +import ICommand from "../../interfaces/Command"; + declare module "discord.js" { export interface Client extends DJSClient { - commands: Collection; + commands: Collection; } } diff --git a/tsconfig.json b/tsconfig.json index 8f7076a..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"]