diff --git a/.deepsource.toml b/.deepsource.toml index 1f05ea0..da97bab 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -1,5 +1,16 @@ version = 1 +[[analyzers]] +name = "javascript" +enabled = true + [[analyzers]] name = "shell" -enabled = true \ No newline at end of file +enabled = true + +[[transformers]] +name = "standardjs" +enabled = true + +[[transformers]] +name = "prettier" \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4008731..a17e74d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,52 +1,52 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - // List of extensions which should be recommended for users of this workspace. - "recommendations": [ - "abrahamwilliam007.es7-javascript-class-snippets", - "christian-kohler.npm-intellisense", - "christian-kohler.path-intellisense", - "dbaeumer.vscode-eslint", - "donjayamanne.githistory", - "eamodio.gitlens", - "esbenp.prettier-vscode", - "github.github-vscode-theme", - "irongeek.vscode-env", - "xabikos.javascriptsnippets", - "wix.vscode-import-cost", - "vscode-icons-team.vscode-icons", - "visualstudioexptteam.vscodeintellicode", - "teledemic.branch-warnings", - "tabnine.tabnine-vscode", - "streetsidesoftware.code-spell-checker", - "seatonjiang.gitmoji-vscode", - "sburg.vscode-javascript-booster", - "kisstkondoros.vscode-codemetrics", - "mgmcdermott.vscode-language-babel", - "mhutchie.git-graph", - "mikestead.dotenv", - "mongodb.mongodb-vscode", - "ms-vscode-remote.remote-wsl-recommender", - "ms-vscode.js-debug", - "ms-vscode.js-debug-companion", - "ms-vscode.references-view", - "ms-vscode.vscode-js-profile-table", - "pflannery.vscode-versionlens", - "adpyke.codesnap", - "anan.devdocstab", - "axosoft.gitkraken-glo", - "gruntfuggly.todo-tree", - "kiteco.kite", - "lkytal.pomodoro", - "wayou.vscode-todo-highlight", - "johnpapa.vscode-peacock", - "stepsize.stepsize", - "nicoespeon.abracadabra", - "sonarsource.sonarlint-vscode", - "nicoespeon.hocus-pocus", - "aaron-bond.better-comments", - "oouo-diogo-perdigao.docthis" - ], - // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [] -} +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "abrahamwilliam007.es7-javascript-class-snippets", + "christian-kohler.npm-intellisense", + "christian-kohler.path-intellisense", + "dbaeumer.vscode-eslint", + "donjayamanne.githistory", + "eamodio.gitlens", + "esbenp.prettier-vscode", + "github.github-vscode-theme", + "irongeek.vscode-env", + "xabikos.javascriptsnippets", + "wix.vscode-import-cost", + "vscode-icons-team.vscode-icons", + "visualstudioexptteam.vscodeintellicode", + "teledemic.branch-warnings", + "tabnine.tabnine-vscode", + "streetsidesoftware.code-spell-checker", + "seatonjiang.gitmoji-vscode", + "sburg.vscode-javascript-booster", + "kisstkondoros.vscode-codemetrics", + "mgmcdermott.vscode-language-babel", + "mhutchie.git-graph", + "mikestead.dotenv", + "mongodb.mongodb-vscode", + "ms-vscode-remote.remote-wsl-recommender", + "ms-vscode.js-debug", + "ms-vscode.js-debug-companion", + "ms-vscode.references-view", + "ms-vscode.vscode-js-profile-table", + "pflannery.vscode-versionlens", + "adpyke.codesnap", + "anan.devdocstab", + "axosoft.gitkraken-glo", + "gruntfuggly.todo-tree", + "kiteco.kite", + "lkytal.pomodoro", + "wayou.vscode-todo-highlight", + "johnpapa.vscode-peacock", + "stepsize.stepsize", + "nicoespeon.abracadabra", + "sonarsource.sonarlint-vscode", + "nicoespeon.hocus-pocus", + "aaron-bond.better-comments", + "oouo-diogo-perdigao.docthis" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 76d8e57..38d1af9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,26 +1,26 @@ -{ - "editor.bracketPairColorization.enabled": true, - "editor.codeActionsOnSave": ["source.formatDocument", "source.fixAll.eslint"], - "editor.cursorBlinking": "phase", - "editor.cursorSmoothCaretAnimation": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.fontFamily": "Cascadia Code", - "editor.fontLigatures": true, - "editor.formatOnSave": true, - "editor.guides.bracketPairs": "active", - "editor.minimap.maxColumn": 200, - "editor.minimap.renderCharacters": false, - "editor.minimap.showSlider": "always", - "editor.tabSize": 2, - "editor.wordWrapColumn": 100, - "files.eol": "\n", - "files.trimTrailingWhitespace": true, - "cSpell.customDictionaries": { - "custom-dictionary-workspace": { - "name": "custom-dictionary-workspace", - "path": "${workspaceFolder:xyter}/.cspell/custom-dictionary-workspace.txt", - "addWords": true, - "scope": "workspace" - } - } -} +{ + "editor.bracketPairColorization.enabled": true, + "editor.codeActionsOnSave": ["source.formatDocument", "source.fixAll.eslint"], + "editor.cursorBlinking": "phase", + "editor.cursorSmoothCaretAnimation": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.fontFamily": "Cascadia Code", + "editor.fontLigatures": true, + "editor.formatOnSave": true, + "editor.guides.bracketPairs": "active", + "editor.minimap.maxColumn": 200, + "editor.minimap.renderCharacters": false, + "editor.minimap.showSlider": "always", + "editor.tabSize": 2, + "editor.wordWrapColumn": 100, + "files.eol": "\n", + "files.trimTrailingWhitespace": true, + "cSpell.customDictionaries": { + "custom-dictionary-workspace": { + "name": "custom-dictionary-workspace", + "path": "${workspaceFolder:xyter}/.cspell/custom-dictionary-workspace.txt", + "addWords": true, + "scope": "workspace" + } + } +} diff --git a/PRIVACY.md b/PRIVACY.md index f07fa94..4bffac4 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -7,54 +7,50 @@ This document entails the privacy policy and agreement that you accept when addi This privacy policy applies to **Xyter**#7721 (949998000401436673) - - - ### Terminology -* **Server Manager** - Anyone who has the ability to add a bot to a server or configure the bot for the server. This is usually an administrator or moderator -* **Server Member** - Anyone who is a member of server to which one of the bots has been added -* **Service User** - Anyone who authorizes an application (logs in) for a scope that provides additional information - +- **Server Manager** - Anyone who has the ability to add a bot to a server or configure the bot for the server. This is usually an administrator or moderator +- **Server Member** - Anyone who is a member of server to which one of the bots has been added +- **Service User** - Anyone who authorizes an application (logs in) for a scope that provides additional information ### Data Collected By Command -The following items may be collected and stored when intentionally provided by a user (usually by means of a command). This data will not be collected automatically. When providing data in this way, you forego any rights to the content of the data provided. -* Server configurations (/settings guild) -* Data and content for automated tasks (/shop roles) -* Locale (/settings user language) -* Reputation (/reputation give) +The following items may be collected and stored when intentionally provided by a user (usually by means of a command). This data will not be collected automatically. When providing data in this way, you forego any rights to the content of the data provided. + +- Server configurations (/settings guild) +- Data and content for automated tasks (/shop roles) +- Locale (/settings user language) +- Reputation (/reputation give) ### Data Collected When Enabled + These items will be automatically collected if a bot is configured to perform certain actions by a server manager. These features are always opt-in, and thus this data will not be collected unless the corresponding feature is enabled. -* API Token & URL (/settings guild pterodactyl) -* Counters (/admin counter) - +- API Token & URL (/settings guild pterodactyl) +- Counters (/admin counter) ### Data Collected Automatically + This data may be collected automatically. This data is used to provide statistics or history data. For any bots that collect this data, it is necessary for features of said bot. -* Username ID (Unique identifier) -* Guild ID (Unique identifier) -* Any data needed for standard operation of Discord bots, such as server permissions -* Credits (/profile view) -* Points (/profile view) -* Levels (/profile view) -* Timestamps (Audit logs and all your data that we store) - +- Username ID (Unique identifier) +- Guild ID (Unique identifier) +- Any data needed for standard operation of Discord bots, such as server permissions +- Credits (/profile view) +- Points (/profile view) +- Levels (/profile view) +- Timestamps (Audit logs and all your data that we store) ### Data Storage + All stored data is kept on protected servers. While storage methods vary, most data is kept within password-protected databases such as [MongoDB Atlas](https://atlas.mongodb.com/). Please keep in mind that even with these protections, no data can ever be 100% secure. All efforts are taken to keep your data secure and private, but its absolute security cannot be guaranteed. - - ### Feedback + Feedback on Xyter bot and service is appreciated. When you submit comments, suggestions, bug reports, and any other forms of feedback, you forego any rights to the content, title, or intent of the provided feedback. Additionally, the feedback may be utilized in any way. - - ### Agreement + By adding Xyter bot to your server or using this bot or service in any way, you are consenting to the policies outlined in this document. In addition, you (the server manager) are agreeing to inform your members of the [Developer Terms of Service](https://discordapp.com/developers/docs/legal) and the contents of this document. If you, the server manager, do not agree to this document, you may remove the bot(s) from the server. If you, the server member, do not agree to this document, you may leave the server that contains the bot(s). If you, the service user, do not agree to this document, you may revoke authorization of the application(s) in your 'Authorized Apps' menu. Changes to This Privacy Policy diff --git a/SECURITY.md b/SECURITY.md index 5ad31c6..4933326 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,10 +2,10 @@ ## Supported Versions -| Version | Supported | -| ------- | ------------------ | +| Version | Supported | +| ---------- | ------------------ | | 2022.4.x | :white_check_mark: | -| < 2022.4.x | :x: | +| < 2022.4.x | :x: | ## Reporting a Vulnerability diff --git a/crowdin.yml b/crowdin.yml deleted file mode 100644 index 59811b2..0000000 --- a/crowdin.yml +++ /dev/null @@ -1,3 +0,0 @@ -files: - - source: /lang/en/*.json - translation: /lang/%two_letters_code%/%original_file_name% diff --git a/lang/en/plugins.json b/lang/en/plugins.json deleted file mode 100644 index 24e05e9..0000000 --- a/lang/en/plugins.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "test":"test" -} diff --git a/package.json b/package.json index b5ca226..38dca3b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/index.ts", "scripts": { "test": "jest", - "start": "nodemon | pino-pretty -i pid,hostname -t yyyy-mm-dd HH:MM:s", + "start": "node .", "prettier-format": "prettier \"src/**/*.ts\" --write", "lint": "eslint ./src --ext .ts", "prepare": "husky install" @@ -27,37 +27,43 @@ "email": "vermium@zyner.org" }, "dependencies": { - "@discordjs/builders": "^0.12.0", + "@discordjs/builders": "^0.13.0", "@discordjs/rest": "^0.4.0", - "axios": "^0.26.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.31.0", + "discord-api-types": "^0.33.0", "discord.js": "^13.6.0", + "i18n": "^0.14.2", "i18next": "^21.6.13", + "i18next-async-backend": "^2.0.0", + "i18next-fs-backend": "^1.1.4", + "i18next-http-backend": "^1.4.0", + "i18next-resources-to-backend": "^1.0.0", "mongoose": "^6.2.3", "node-schedule": "^2.1.0", "ts-node": "^10.7.0", - "tsconfig-paths": "^3.14.1", + "tsconfig-paths": "^4.0.0", "typescript": "^4.6.3", "uuid": "^8.3.2", "winston-daily-rotate-file": "^4.6.1" }, "devDependencies": { "@types/chance": "^1.1.3", - "@types/node-schedule": "^1.3.2", + "@types/node-schedule": "2.1.0", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/parser": "^5.15.0", - "eslint": "8.13.0", + "eslint": "8.15.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": "^7.0.0", - "jest": "^27.5.1", + "husky": "8.0.1", + "jest": "28.0.0", "lint-staged": "^12.3.7", "prettier": "^2.6.0" }, diff --git a/src/database/index.ts b/src/database/index.ts index 4fd3870..b7dd3b6 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -8,13 +8,15 @@ import logger from "@logger"; import { url } from "@config/database"; export default async () => { - mongoose.connect(url).then(async (connection) => { - logger?.info(`Connected to database: ${connection.connection.name}`); + await mongoose.connect(url).then(async (connection) => { + logger.info(`Connected to database: ${connection.connection.name}`); }); - mongoose.connection.on("error", (error) => { - logger?.error(error); + + mongoose.connection.on("error", async (error) => { + logger.error(`${error}`); }); - mongoose.connection.on("warn", (warning) => { - logger?.warn(warning); + + mongoose.connection.on("warn", async (warning) => { + logger.warn(warning); }); }; diff --git a/src/database/schemas/guild.ts b/src/database/schemas/guild.ts index 849845b..0b7a78d 100644 --- a/src/database/schemas/guild.ts +++ b/src/database/schemas/guild.ts @@ -1,3 +1,4 @@ +import { ColorResolvable } from "discord.js"; import { Schema, model } from "mongoose"; interface IGuild { @@ -10,6 +11,13 @@ interface IGuild { minimumLength: number; workTimeout: number; }; + embeds: { + successColor: ColorResolvable; + waitColor: ColorResolvable; + errorColor: ColorResolvable; + footerIcon: string; + footerText: string; + }; shop: { roles: { status: boolean; pricePerHour: number } }; points: { status: boolean; @@ -61,6 +69,28 @@ const guildSchema = new Schema( default: 900000, }, }, + embeds: { + successColor: { + type: String, + default: "#22bb33", + }, + waitColor: { + type: String, + default: "#f0ad4e", + }, + errorColor: { + type: String, + default: "#bb2124", + }, + footerText: { + type: String, + default: "https://github.com/ZynerOrg/xyter", + }, + footerIcon: { + type: String, + default: "https://github.com/ZynerOrg.png", + }, + }, shop: { roles: { status: { diff --git a/src/events/guildCreate/index.ts b/src/events/guildCreate/index.ts index a55d9d5..01b70f6 100644 --- a/src/events/guildCreate/index.ts +++ b/src/events/guildCreate/index.ts @@ -7,13 +7,14 @@ import fetchGuild from "@helpers/fetchGuild"; import logger from "@logger"; export default { - name: "guildCreate", async execute(guild: Guild) { const { client } = guild; - logger?.verbose(`Added to guild: ${guild.name} (${guild.id})`); + 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 index 9a0ceec..123393c 100644 --- a/src/events/guildDelete/index.ts +++ b/src/events/guildDelete/index.ts @@ -7,13 +7,14 @@ import dropGuild from "@helpers/dropGuild"; import logger from "@logger"; export default { - name: "guildDelete", async execute(guild: Guild) { const { client } = guild; - logger?.verbose(`Deleted from guild: ${guild.name} (${guild.id})`); + 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 index 6d76893..c47c78e 100644 --- a/src/events/guildMemberAdd/audits.ts +++ b/src/events/guildMemberAdd/audits.ts @@ -3,10 +3,14 @@ import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; import guildSchema from "@schemas/guild"; -import { footerText, footerIcon, successColor } from "@config/embed"; +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; @@ -20,24 +24,35 @@ export default { 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, - }), - ], - }); + (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 index 4b2cbe0..9ece541 100644 --- a/src/events/guildMemberAdd/index.ts +++ b/src/events/guildMemberAdd/index.ts @@ -9,11 +9,10 @@ import joinMessage from "../guildMemberAdd/joinMessage"; import audits from "../guildMemberAdd/audits"; export default { - name: "guildMemberAdd", async execute(member: GuildMember) { const { client, user, guild } = member; - logger?.verbose( + logger?.silly( `New member: ${user.tag} (${user.id}) added to guild: ${guild.name} (${guild.id})` ); diff --git a/src/events/guildMemberAdd/joinMessage.ts b/src/events/guildMemberAdd/joinMessage.ts index 8c7e9bb..bf7aec3 100644 --- a/src/events/guildMemberAdd/joinMessage.ts +++ b/src/events/guildMemberAdd/joinMessage.ts @@ -1,13 +1,14 @@ -import logger from "@logger"; import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; import guildSchema from "@schemas/guild"; -import { footerText, footerIcon, successColor } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; export default { execute: async (member: GuildMember) => { - logger.info(member); + const { footerText, footerIcon, successColor } = await getEmbedConfig( + member.guild + ); const guildData = await guildSchema.findOne({ guildId: member.guild.id }); diff --git a/src/events/guildMemberRemove/audits.ts b/src/events/guildMemberRemove/audits.ts index e096814..620e52e 100644 --- a/src/events/guildMemberRemove/audits.ts +++ b/src/events/guildMemberRemove/audits.ts @@ -3,10 +3,14 @@ import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; import guildSchema from "@schemas/guild"; -import { footerText, footerIcon, errorColor } from "@config/embed"; +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; @@ -20,21 +24,32 @@ export default { 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, - }), - ], - }); + (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 index e2fe667..3d28c31 100644 --- a/src/events/guildMemberRemove/index.ts +++ b/src/events/guildMemberRemove/index.ts @@ -9,11 +9,10 @@ import leaveMessage from "./leaveMessage"; import audits from "./audits"; export default { - name: "guildMemberRemove", async execute(member: GuildMember) { const { client, user, guild } = member; - logger?.verbose( + logger?.silly( `Removed member: ${user.tag} (${user.id}) from guild: ${guild.name} (${guild.id})` ); diff --git a/src/events/guildMemberRemove/leaveMessage.ts b/src/events/guildMemberRemove/leaveMessage.ts index b4f6100..efed730 100644 --- a/src/events/guildMemberRemove/leaveMessage.ts +++ b/src/events/guildMemberRemove/leaveMessage.ts @@ -1,13 +1,14 @@ -import logger from "@logger"; import { GuildMember, MessageEmbed, TextChannel } from "discord.js"; import guildSchema from "@schemas/guild"; -import { footerText, footerIcon, errorColor } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; export default { execute: async (member: GuildMember) => { - logger.info(member); + const { footerText, footerIcon, errorColor } = await getEmbedConfig( + member.guild + ); const guildData = await guildSchema.findOne({ guildId: member.guild.id }); diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..b7dd3b6 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,22 @@ +// 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/audits.ts b/src/events/interactionCreate/audits.ts index 5fe29e6..c6df788 100644 --- a/src/events/interactionCreate/audits.ts +++ b/src/events/interactionCreate/audits.ts @@ -3,7 +3,7 @@ import { Interaction, MessageEmbed, TextChannel } from "discord.js"; import guildSchema from "@schemas/guild"; -import { footerText, footerIcon, successColor } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; export default { execute: async (interaction: Interaction) => { @@ -11,6 +11,10 @@ export default { if (interaction.guild === null) return; + const { footerText, footerIcon, successColor } = await getEmbedConfig( + interaction.guild + ); + const guildData = await guildSchema.findOne({ guildId: interaction.guild.id, }); @@ -26,23 +30,34 @@ export default { if (channel === null) return; - (channel as TextChannel).send({ - embeds: [ - new MessageEmbed() - .setColor(successColor) - .setDescription( - ` + (channel as TextChannel) + .send({ + embeds: [ + new MessageEmbed() + .setColor(successColor) + .setDescription( + ` **Interaction created by** ${interaction.user.username} **in** ${interaction.channel} ` - ) - .setThumbnail(interaction.user.displayAvatarURL()) - .addFields([{ name: "Event", value: "interactionCreate" }]) - .setTimestamp() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), - ], - }); + ) + .setThumbnail(interaction.user.displayAvatarURL()) + .addFields([{ name: "Event", value: "interactionCreate" }]) + .setTimestamp() + .setFooter({ + text: footerText, + iconURL: footerIcon, + }), + ], + }) + .then(async () => { + logger.info( + `Audit log sent for event interactionCreate in guild ${interaction?.guild?.name} (${interaction?.guild?.id})` + ); + }) + .catch(async () => { + logger.error( + `Audit log failed to send for event interactionCreate in guild ${interaction?.guild?.name} (${interaction?.guild?.id})` + ); + }); }, }; diff --git a/src/events/interactionCreate/components/isCommand.ts b/src/events/interactionCreate/components/isCommand.ts index ac1e19d..7af8e7e 100644 --- a/src/events/interactionCreate/components/isCommand.ts +++ b/src/events/interactionCreate/components/isCommand.ts @@ -3,27 +3,88 @@ import { CommandInteraction, MessageEmbed } from "discord.js"; import logger from "@logger"; -import { errorColor, footerText, footerIcon } from "@config/embed"; +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 { client, guild, commandName, user } = interaction; + const { errorColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const { client, guild, commandName, user, memberPermissions } = interaction; const currentCommand = client.commands.get(commandName); - if (!currentCommand) return; - await interaction.deferReply({ ephemeral: true }); + 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?.verbose( + 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); + logger?.error(`${error}`); return interaction.editReply({ embeds: [ diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index fb35bc9..2848f39 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -7,11 +7,10 @@ import logger from "@logger"; import audits from "./audits"; export default { - name: "interactionCreate", async execute(interaction: CommandInteraction) { const { guild, id } = interaction; - logger?.verbose( + logger?.silly( `New interaction: ${id} in guild: ${guild?.name} (${guild?.id})` ); diff --git a/src/events/messageCreate/index.ts b/src/events/messageCreate/index.ts index 029568a..004ca71 100644 --- a/src/events/messageCreate/index.ts +++ b/src/events/messageCreate/index.ts @@ -2,7 +2,6 @@ import { Message } from "discord.js"; import modules from "@events/messageCreate/modules"; export default { - name: "messageCreate", async execute(message: Message) { await modules.credits.execute(message); await modules.points.execute(message); diff --git a/src/events/messageCreate/modules/counters/index.ts b/src/events/messageCreate/modules/counters/index.ts index 6f852c7..a65a4db 100644 --- a/src/events/messageCreate/modules/counters/index.ts +++ b/src/events/messageCreate/modules/counters/index.ts @@ -23,7 +23,7 @@ export default { }); if (counter === null) { - logger.verbose( + logger.silly( `No counter found for guild ${guildId} and channel ${channelId}` ); return; @@ -33,7 +33,7 @@ export default { lastMessage?.author.id === author.id && channel.id === counter.channelId ) { - logger.verbose( + logger.silly( `${author.username} sent the last message therefor not allowing again.` ); await message.delete(); @@ -41,7 +41,7 @@ export default { } if (content !== counter.word) { - logger.verbose( + logger.silly( `Counter word ${counter.word} does not match message ${content}` ); @@ -53,7 +53,7 @@ export default { await counter .save() .then(async () => { - logger.verbose( + logger.silly( `Counter for guild ${guildId} and channel ${channelId} is now ${counter.counter}` ); }) @@ -64,7 +64,7 @@ export default { ); }); - logger.verbose( + logger.silly( `Counter word ${counter.word} was found in message ${content} from ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` ); }, diff --git a/src/events/messageCreate/modules/credits/index.ts b/src/events/messageCreate/modules/credits/index.ts index e7f4de4..856c308 100644 --- a/src/events/messageCreate/modules/credits/index.ts +++ b/src/events/messageCreate/modules/credits/index.ts @@ -30,7 +30,7 @@ export default { const timeout = await timeouts.findOne(timeoutData); if (timeout) { - logger.verbose( + logger.silly( `User ${userId} in guild ${guildId} is on timeout 2022-04-14-13-51-00` ); return; @@ -41,7 +41,7 @@ export default { await userData .save() .then(async () => { - logger.verbose( + logger.silly( `User ${userId} in guild ${guildId} has ${userData.credits} credits` ); }) @@ -55,7 +55,7 @@ export default { await timeouts .create(timeoutData) .then(async () => { - logger.verbose( + logger.silly( `Timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId} has been created` ); }) @@ -70,7 +70,7 @@ export default { await timeouts .deleteOne(timeoutData) .then(async () => { - logger.verbose( + logger.silly( `Timeout 2022-04-14-13-51-00 for user ${userId} in guild ${guildId} has been deleted` ); }) diff --git a/src/events/messageCreate/modules/points/index.ts b/src/events/messageCreate/modules/points/index.ts index f9a45f1..e4d3aa4 100644 --- a/src/events/messageCreate/modules/points/index.ts +++ b/src/events/messageCreate/modules/points/index.ts @@ -30,7 +30,7 @@ export default { const timeout = await timeouts.findOne(timeoutData); if (timeout) { - logger.verbose( + logger.silly( `User ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id} is on timeout 2022-04-14-14-15-00` ); return; @@ -41,7 +41,7 @@ export default { await userData .save() .then(async () => { - logger.verbose( + logger.silly( `Successfully saved user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` ); }) @@ -52,14 +52,14 @@ export default { ); }); - logger.verbose( + logger.silly( `User ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id}) has ${userData.points} points` ); await timeouts .create(timeoutData) .then(async () => { - logger.verbose( + logger.silly( `Successfully created timeout for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` ); }) @@ -74,7 +74,7 @@ export default { await timeouts .deleteOne(timeoutData) .then(async () => { - logger.verbose( + logger.silly( `Successfully deleted timeout 2022-04-14-14-15-00 for user ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` ); }) diff --git a/src/events/messageDelete/audits.ts b/src/events/messageDelete/audits.ts index f7f0ed1..1f37b59 100644 --- a/src/events/messageDelete/audits.ts +++ b/src/events/messageDelete/audits.ts @@ -3,7 +3,7 @@ import { Message, MessageEmbed, TextChannel } from "discord.js"; import guildSchema from "@schemas/guild"; -import { footerText, footerIcon, successColor } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; export default { execute: async (message: Message) => { @@ -11,6 +11,10 @@ export default { if (message.guild === null) return; + const { footerText, footerIcon, successColor } = await getEmbedConfig( + message.guild + ); + const guildData = await guildSchema.findOne({ guildId: message.guild.id, }); @@ -26,26 +30,37 @@ export default { if (channel === null) return; - (channel as TextChannel).send({ - embeds: [ - new MessageEmbed() - .setColor(successColor) - .setAuthor({ - name: message.author.username, - iconURL: message.author.displayAvatarURL(), - }) - .setDescription( - ` + (channel as TextChannel) + .send({ + embeds: [ + new MessageEmbed() + .setColor(successColor) + .setAuthor({ + name: message.author.username, + iconURL: message.author.displayAvatarURL(), + }) + .setDescription( + ` **Message sent by** ${message.author} **deleted in** ${message.channel} ${message.content} ` - ) - .setTimestamp() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), - ], - }); + ) + .setTimestamp() + .setFooter({ + text: footerText, + iconURL: footerIcon, + }), + ], + }) + .then(async () => { + logger.info( + `Audit log sent for event messageDelete in guild ${message?.guild?.name} (${message?.guild?.id})` + ); + }) + .catch(async () => { + logger.error( + `Audit log failed to send for event messageDelete in guild ${message?.guild?.name} (${message?.guild?.id})` + ); + }); }, }; diff --git a/src/events/messageDelete/index.ts b/src/events/messageDelete/index.ts index 286c99a..77223d2 100644 --- a/src/events/messageDelete/index.ts +++ b/src/events/messageDelete/index.ts @@ -3,7 +3,6 @@ import audits from "@events/messageDelete/audits"; import counter from "./modules/counter"; export default { - name: "messageDelete", async execute(message: Message) { await audits.execute(message); await counter(message); diff --git a/src/events/messageDelete/modules/counter.ts b/src/events/messageDelete/modules/counter.ts index d2c207e..6169b45 100644 --- a/src/events/messageDelete/modules/counter.ts +++ b/src/events/messageDelete/modules/counter.ts @@ -14,7 +14,7 @@ export default async (message: Message) => { }); if (counter === null) - return logger?.verbose( + return logger?.silly( `No counter found for guild: ${guild?.name} (${guild?.id})` ); const { word } = counter; @@ -29,8 +29,8 @@ export default async (message: Message) => { if (lastMessage.author.id === message.author.id) return; channel?.send(`${author} said **${word}**.`); - logger?.verbose(`${author} said ${word} in ${channel}`); - return logger?.verbose( + logger?.silly(`${author} said ${word} in ${channel}`); + return logger?.silly( `User: ${author?.tag} (${author?.id}) in guild: ${guild?.name} (${guild?.id}) said the counter word: ${word}` ); }; diff --git a/src/events/messageUpdate/audits.ts b/src/events/messageUpdate/audits.ts index 50c76b5..c9bed8a 100644 --- a/src/events/messageUpdate/audits.ts +++ b/src/events/messageUpdate/audits.ts @@ -4,7 +4,7 @@ import { Message, MessageEmbed, TextChannel } from "discord.js"; import guildSchema from "@schemas/guild"; -import { footerText, footerIcon, successColor } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; export default { execute: async (oldMessage: Message, newMessage: Message) => { @@ -14,6 +14,10 @@ export default { if (oldMessage.guild === null) return; if (newMessage.guild === null) return; + const { footerText, footerIcon, successColor } = await getEmbedConfig( + newMessage.guild + ); + const guildData = await guildSchema.findOne({ guildId: oldMessage.guild.id, }); @@ -29,25 +33,36 @@ export default { if (channel === null) return; - (channel as TextChannel).send({ - embeds: [ - new MessageEmbed() - .setColor(successColor) - .setAuthor({ - name: newMessage.author.username, - iconURL: newMessage.author.displayAvatarURL(), - }) - .setDescription( - ` + (channel as TextChannel) + .send({ + embeds: [ + new MessageEmbed() + .setColor(successColor) + .setAuthor({ + name: newMessage.author.username, + iconURL: newMessage.author.displayAvatarURL(), + }) + .setDescription( + ` **Message edited in** ${newMessage.channel} [jump to message](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${newMessage.id}) ` - ) - .setTimestamp() - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), - ], - }); + ) + .setTimestamp() + .setFooter({ + text: footerText, + iconURL: footerIcon, + }), + ], + }) + .then(async () => { + logger.info( + `Audit log sent for event messageUpdate in guild ${newMessage?.guild?.name} (${newMessage?.guild?.id})` + ); + }) + .catch(async () => { + logger.error( + `Audit log failed to send for event messageUpdate in guild ${newMessage?.guild?.name} (${newMessage?.guild?.id})` + ); + }); }, }; diff --git a/src/events/messageUpdate/index.ts b/src/events/messageUpdate/index.ts index 2ea1e42..9238805 100644 --- a/src/events/messageUpdate/index.ts +++ b/src/events/messageUpdate/index.ts @@ -8,18 +8,16 @@ import counter from "./modules/counter"; import audits from "./audits"; export default { - name: "messageUpdate", async execute(oldMessage: Message, newMessage: Message) { const { author, guild } = newMessage; await audits.execute(oldMessage, newMessage); - logger?.verbose( + logger?.silly( `Message update event fired by ${author.tag} (${author.id}) in guild: ${guild?.name} (${guild?.id})` ); - if (author?.bot) - return logger?.verbose(`Message update event fired by bot`); + 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/events/messageUpdate/modules/counter.ts index 9c65ab3..465b00a 100644 --- a/src/events/messageUpdate/modules/counter.ts +++ b/src/events/messageUpdate/modules/counter.ts @@ -14,12 +14,12 @@ export default async (message: Message) => { }); if (counter === null) - return logger?.verbose( + return logger?.silly( `No counter found for guild: ${guild?.name} (${guild?.id})` ); const { word } = counter; if (content === word) - return logger?.verbose( + return logger?.silly( `User: ${author?.tag} (${author?.id}) in guild: ${guild?.name} (${guild?.id}) said the counter word: ${word}` ); @@ -27,7 +27,7 @@ export default async (message: Message) => { ?.delete() ?.then(async () => { await channel?.send(`${author} said **${word}**.`); - logger?.verbose(`${author} said ${word} in ${channel}`); + logger?.silly(`${author} said ${word} in ${channel}`); }) ?.catch(async (error: any) => { logger?.error(error); diff --git a/src/events/ready/index.ts b/src/events/ready/index.ts index b840bb3..b312f73 100644 --- a/src/events/ready/index.ts +++ b/src/events/ready/index.ts @@ -8,17 +8,16 @@ import deployCommands from "@handlers/deployCommands"; import devMode from "@handlers/devMode"; export default { - name: "ready", once: true, async execute(client: Client) { - logger.info(`${client.user?.tag} (${client.user?.id}) is ready`); + logger.info("Ready!"); await updatePresence(client); await devMode(client); await deployCommands(client); client.guilds?.cache.forEach((guild) => { - logger.verbose( + 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 index dd4df30..81b1eeb 100644 --- a/src/handlers/commands.ts +++ b/src/handlers/commands.ts @@ -12,22 +12,25 @@ export default async (client: Client) => { } await Promise.all( - plugins.map(async (pluginName) => { + plugins.map(async (pluginName, index) => { const plugin = await import(`../plugins/${pluginName}`); - await client?.commands?.set( - plugin?.default?.data?.name, - plugin?.default + await client.commands.set( + plugin.default.builder.name, + plugin.default, + plugin.default.metadata ); - logger.verbose(`Loaded plugin: ${pluginName}`); + logger.verbose( + `Loaded plugin ${index + 1}/${plugins.length}: ${pluginName}` + ); }) ) .then(async () => { - logger.debug("Successfully loaded plugins."); + logger.info(`Started all ${plugins.length} plugins.`); }) .catch(async (err) => { - logger.error(err); + logger.error(`${err}`); }); }); }; diff --git a/src/handlers/deployCommands.ts b/src/handlers/deployCommands.ts index d408886..8af167e 100644 --- a/src/handlers/deployCommands.ts +++ b/src/handlers/deployCommands.ts @@ -12,17 +12,17 @@ export default async (client: Client) => { await Promise.all( client.commands.map(async (pluginData: any) => { - pluginList.push(pluginData.data.toJSON()); + pluginList.push(pluginData.builder.toJSON()); logger.verbose( - `${pluginData.data.name} successfully pushed to plugin list.` + `Plugin is ready for deployment: ${pluginData.builder.name}` ); }) ) .then(async () => { - logger.debug("Successfully pushed all plugins to plugin list."); + logger.info("All plugins are ready to be deployed."); }) .catch(async (error) => { - logger.error(error); + logger.error(`${error}`); }); const rest = new REST({ version: "9" }).setToken(token); @@ -32,10 +32,10 @@ export default async (client: Client) => { body: pluginList, }) .then(async () => { - logger.debug(`Successfully deployed plugins to Discord`); + logger.info(`Successfully deployed plugins to Discord's API`); }) .catch(async (error) => { - logger.error(error); + logger.error(`${error}`); }); if (devMode) { @@ -44,10 +44,10 @@ export default async (client: Client) => { body: pluginList, }) .then(async () => - logger.debug(`Successfully deployed guild plugins to Discord`) + logger.info(`Successfully deployed guild plugins to Discord's API`) ) .catch(async (error) => { - logger.error(error); + logger.error(`${error}`); }); } }; diff --git a/src/handlers/devMode.ts b/src/handlers/devMode.ts index 3d4c78f..53a2cd6 100644 --- a/src/handlers/devMode.ts +++ b/src/handlers/devMode.ts @@ -9,11 +9,9 @@ import { devMode, guildId } from "@config/other"; export default async (client: Client) => { if (!devMode) { return client?.application?.commands?.set([], guildId).then(async () => { - return logger.debug( - `Development commands disabled for guild: ${guildId}` - ); + return logger.verbose(`Development mode is disabled.`); }); } - return logger.debug(`Development commands enabled for guild: ${guildId}`); + return logger.info(`Development mode is enabled.`); }; diff --git a/src/handlers/encryption.ts b/src/handlers/encryption.ts index 4624d74..5e07d89 100644 --- a/src/handlers/encryption.ts +++ b/src/handlers/encryption.ts @@ -4,7 +4,7 @@ import { secretKey, algorithm } from "@config/encryption"; const iv = crypto.randomBytes(16); -const encrypt = (text: any) => { +const encrypt = (text: any): { iv: error; content: error } => { const cipher = crypto.createCipheriv(algorithm, secretKey, iv); const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); diff --git a/src/handlers/events.ts b/src/handlers/events.ts index 1428325..f7da9c1 100644 --- a/src/handlers/events.ts +++ b/src/handlers/events.ts @@ -9,27 +9,29 @@ export default async (client: Client) => { } await Promise.all( - events.map(async (eventName) => { + events.map(async (eventName, index) => { const event = await import(`../events/${eventName}`); - logger.verbose(`Loaded event: ${eventName}`); + logger.verbose( + `Loaded event ${index + 1}/${events.length}: ${eventName}` + ); if (event.once) { - return client.once(event.default.name, async (...args) => + return client.once(eventName, async (...args) => event.default.execute(...args) ); } - return client.on(event.default.name, async (...args) => + return client.on(eventName, async (...args) => event.default.execute(...args) ); }) ) .then(async () => { - logger.debug("Successfully loaded events."); + logger.info(`Started all ${events.length} events.`); }) .catch(async (err) => { - logger.error(err); + logger.error(`${err}`); }); }); }; diff --git a/src/schedules/index.ts b/src/handlers/schedules/index.ts similarity index 89% rename from src/schedules/index.ts rename to src/handlers/schedules/index.ts index 71ce35b..20ed685 100644 --- a/src/schedules/index.ts +++ b/src/handlers/schedules/index.ts @@ -5,7 +5,7 @@ import schedule from "node-schedule"; import logger from "@logger"; // Jobs -import shopRoles from "@root/schedules/jobs/shopRoles"; +import shopRoles from "@jobs/shopRoles"; export default async (client: Client) => { const expression = "*/5 * * * *"; diff --git a/src/helpers/deferReply.ts b/src/helpers/deferReply.ts new file mode 100644 index 0000000..feec1a7 --- /dev/null +++ b/src/helpers/deferReply.ts @@ -0,0 +1,28 @@ +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/dropGuild.ts b/src/helpers/dropGuild.ts index fc17d0a..d69d09b 100644 --- a/src/helpers/dropGuild.ts +++ b/src/helpers/dropGuild.ts @@ -13,7 +13,7 @@ export default async (guild: Guild) => { await guildSchema .deleteMany({ guildId: guild.id }) .then(async () => { - return logger?.verbose(`Deleted guild: ${guild.id}`); + return logger?.silly(`Deleted guild: ${guild.id}`); }) .catch(async (error) => { logger?.error(`Error deleting guild: ${guild.id} - ${error}`); @@ -22,7 +22,7 @@ export default async (guild: Guild) => { await userSchema .deleteMany({ guildId: guild.id }) .then(async () => { - logger?.verbose(`Deleted users for guild: ${guild.id} from database`); + logger?.silly(`Deleted users for guild: ${guild.id} from database`); }) .catch(async (error) => { logger?.error(`Error deleting users for guild: ${guild.id} - ${error}`); @@ -31,7 +31,7 @@ export default async (guild: Guild) => { await apiSchema .deleteMany({ guildId: guild.id }) .then(async () => { - logger?.verbose(`Deleted apis for guild: ${guild.id} from database`); + logger?.silly(`Deleted apis for guild: ${guild.id} from database`); }) .catch(async (error) => { logger?.error(`Error deleting apis for guild: ${guild.id} - ${error}`); @@ -40,7 +40,7 @@ export default async (guild: Guild) => { await counterSchema .deleteMany({ guildId: guild.id }) .then(async () => { - logger?.verbose(`Deleted counters for guild: ${guild.id} from database`); + logger?.silly(`Deleted counters for guild: ${guild.id} from database`); }) .catch(async (error) => { logger?.error( @@ -51,9 +51,7 @@ export default async (guild: Guild) => { await shopRoleSchema .deleteMany({ guildId: guild.id }) .then(async () => { - logger?.verbose( - `Deleted shop roles for guild: ${guild.id} from database` - ); + logger?.silly(`Deleted shop roles for guild: ${guild.id} from database`); }) .catch(async (error) => { logger?.error( @@ -64,7 +62,7 @@ export default async (guild: Guild) => { await timeoutSchema .deleteMany({ guildId: guild.id }) .then(async () => { - logger?.verbose(`Deleted timeouts for guild: ${guild.id} from database`); + logger?.silly(`Deleted timeouts for guild: ${guild.id} from database`); }) .catch(async (error) => { logger?.error( diff --git a/src/helpers/dropUser.ts b/src/helpers/dropUser.ts index 10662c9..8306285 100644 --- a/src/helpers/dropUser.ts +++ b/src/helpers/dropUser.ts @@ -8,7 +8,7 @@ export default async (user: User, guild: Guild) => { await userSchema .deleteOne({ userId: user.id, guildId: guild.id }) .then(async () => { - logger?.verbose(`Deleted user: ${user?.id} from guild: ${guild?.id}`); + logger?.silly(`Deleted user: ${user?.id} from guild: ${guild?.id}`); }) .catch(async (error) => { logger?.error( diff --git a/src/helpers/embedBuilder.ts b/src/helpers/embedBuilder.ts new file mode 100644 index 0000000..8d15d90 --- /dev/null +++ b/src/helpers/embedBuilder.ts @@ -0,0 +1,9 @@ +import { footerText, footerIcon } from "@config/embed"; +import { MessageEmbed } from "discord.js"; + +export default new MessageEmbed() + .setFooter({ + text: footerText, + iconURL: footerIcon, + }) + .setTimestamp(new Date()); diff --git a/src/helpers/fetchGuild.ts b/src/helpers/fetchGuild.ts index 6b9c439..4c8f107 100644 --- a/src/helpers/fetchGuild.ts +++ b/src/helpers/fetchGuild.ts @@ -16,7 +16,7 @@ export default async (guild: Guild) => { await newGuildObj .save() .then(async () => { - logger?.verbose(`Created guild: ${guild.id}`); + logger?.silly(`Created guild: ${guild.id}`); }) .catch(async (error) => { logger?.error(`Error creating guild: ${guild.id} - ${error}`); diff --git a/src/helpers/fetchUser.ts b/src/helpers/fetchUser.ts index 98cc927..96386ef 100644 --- a/src/helpers/fetchUser.ts +++ b/src/helpers/fetchUser.ts @@ -22,7 +22,7 @@ export default async (user: User, guild: Guild) => { await newUserObj .save() .then(async () => { - logger?.verbose(`Created user: ${user.id} for guild: ${guild.id}`); + logger?.silly(`Created user: ${user.id} for guild: ${guild.id}`); }) .catch(async (error) => { logger?.error( diff --git a/src/helpers/getCommandMetadata.ts b/src/helpers/getCommandMetadata.ts new file mode 100644 index 0000000..7894c00 --- /dev/null +++ b/src/helpers/getCommandMetadata.ts @@ -0,0 +1,12 @@ +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/getEmbedConfig.ts b/src/helpers/getEmbedConfig.ts new file mode 100644 index 0000000..3532542 --- /dev/null +++ b/src/helpers/getEmbedConfig.ts @@ -0,0 +1,18 @@ +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/pluralize.ts b/src/helpers/pluralize.ts index c255427..3c5d55f 100644 --- a/src/helpers/pluralize.ts +++ b/src/helpers/pluralize.ts @@ -1,7 +1,7 @@ import logger from "@root/logger"; -export default (count: number, noun: string, suffix?: string) => { +export default (count: number, noun: string, suffix?: string): string => { const result = `${count} ${noun}${count !== 1 ? suffix || "s" : ""}`; - logger?.verbose(`Pluralized ${count} to ${result}`); + logger?.silly(`Pluralized ${count} to ${result}`); return result; }; diff --git a/src/helpers/saveUser.ts b/src/helpers/saveUser.ts index 48e4a7a..8feb92e 100644 --- a/src/helpers/saveUser.ts +++ b/src/helpers/saveUser.ts @@ -23,7 +23,7 @@ export default async function saveUser(data: any, data2: any) { 50 )}\n${`${_}\n${"=".repeat(50)}`}` ) - : logger?.verbose(`Saved user: ${data.id} (saveUser)`) + : logger?.silly(`Saved user: ${data.id} (saveUser)`) ); if (data2) { data2.save((_: any) => @@ -33,7 +33,7 @@ export default async function saveUser(data: any, data2: any) { 50 )}\n${`${_}\n${"=".repeat(50)}`}` ) - : logger?.verbose(`Saved user: ${data2.id} (saveUser)`) + : logger?.silly(`Saved user: ${data2.id} (saveUser)`) ); } }, diff --git a/src/helpers/sleep.ts b/src/helpers/sleep.ts index bd209e1..5e53d79 100644 --- a/src/helpers/sleep.ts +++ b/src/helpers/sleep.ts @@ -3,6 +3,6 @@ import logger from "@logger"; export default function sleep(milliseconds: any) { return new Promise((resolve) => { setTimeout(resolve, milliseconds); - logger?.verbose(`Sleeping for ${milliseconds} milliseconds`); + logger?.silly(`Sleeping for ${milliseconds} milliseconds`); }); } diff --git a/src/helpers/updatePresence.ts b/src/helpers/updatePresence.ts index b44682b..7f23885 100644 --- a/src/helpers/updatePresence.ts +++ b/src/helpers/updatePresence.ts @@ -10,5 +10,5 @@ export default async (client: Client) => { activities: [{ type: "WATCHING", name: status }], status: "online", }); - logger?.verbose(`Updated client presence to: ${status}`); + logger?.debug(`Updated client presence to: ${status}`); }; diff --git a/src/index.ts b/src/index.ts index 270ab96..573ff72 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,25 +4,56 @@ import { token, intents } from "@config/discord"; import { Client } from "discord.js"; // discord.js -import locale from "@locale"; -import database from "@database"; -import schedules from "@schedules"; +import database from "@root/events"; +import schedules from "@handlers/schedules"; import events from "@handlers/events"; import commands from "@handlers/commands"; -async function main() { +// Main process that starts all other sub processes +const main = async () => { + // Initiate client object const client = new Client({ intents, }); - await locale(); - await database(); - await schedules(client); + // Start database manager + await database() + .then(async () => { + await logger.silly("Database process started"); + }) + .catch(async (err) => { + await logger.error(`${err}`); + }); - await commands(client); - await events(client); + // Start schedule manager + await schedules(client) + .then(async () => { + await logger.silly("Schedules process started"); + }) + .catch(async (err) => { + await logger.error(`${err}`); + }); + // Start command handler + await commands(client) + .then(async () => { + await logger.silly("Commands process started"); + }) + .catch(async (err) => { + await logger.error(`${err}`); + }); + + // Start event handler + await events(client) + .then(async () => { + await logger.silly("Events process started"); + }) + .catch(async (err) => { + await logger.error(`${err}`); + }); + + // Authorize with Discord's API await client.login(token); -} +}; -main(); +main() \ No newline at end of file diff --git a/src/schedules/jobs/shopRoles.ts b/src/jobs/shopRoles.ts similarity index 78% rename from src/schedules/jobs/shopRoles.ts rename to src/jobs/shopRoles.ts index 877e3f1..b42a3f5 100644 --- a/src/schedules/jobs/shopRoles.ts +++ b/src/jobs/shopRoles.ts @@ -55,18 +55,30 @@ export default async (client: Client) => { const rRole = rMember.roles.cache.get(roleId); - if (!rRole) { - logger.error(`Role ${roleId} not found for shop role ${roleId}.`); - return; - } - - if (!rMember) { + 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.verbose( + logger.silly( `Shop role ${roleId} is due for payment. Withdrawing credits from user ${userId}.` ); @@ -79,23 +91,6 @@ export default async (client: Client) => { if (!rMember) { logger.error(`Member ${userId} not found for shop role ${roleId}.`); - await shopRoleSchema - .deleteOne({ - userId, - roleId, - guildId, - }) - .then(async () => { - logger.verbose( - `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; } @@ -114,7 +109,7 @@ export default async (client: Client) => { await role .save() .then(async () => { - logger.verbose(`Shop role ${roleId} has been paid for.`); + logger.silly(`Shop role ${roleId} has been paid for.`); }) .catch(async (err) => { logger.error( @@ -123,7 +118,7 @@ export default async (client: Client) => { ); }); - logger.verbose( + logger.silly( `Shop role ${roleId} has been paid for. Keeping role ${roleId} for user ${userId}.` ); }) diff --git a/src/locale/index.ts b/src/locale/index.ts deleted file mode 100644 index 536b91f..0000000 --- a/src/locale/index.ts +++ /dev/null @@ -1,205 +0,0 @@ -import i18next from "i18next"; -import logger from "@logger"; - -export default async () => { - await i18next - .init({ - lng: "en", // if you're using a language detector, do not define the lng option - // debug: true, - fallbackLng: "en", - resources: { - en: { - general: { not_available: "Not Available" }, - commands: { - credits: { - general: { - credits_one: "{{count}} credit", - credits_other: "{{count}} credits", - }, - addons: { - balance: { embed: { title: "Credits" } }, - gift: { embed: { title: "Gift" } }, - }, - }, - reputation: { - addons: { - give: { - version01: { - embed: { - title: ":medal: Reputation", - description: - "You have given reputation within the last day, you can not repute now!", - }, - }, - version02: { - embed: { - title: ":medal: Reputation", - description: - "You have given {{user}} a {{type}} reputation!", - }, - }, - version03: { - embed: { - title: ":medal: Reputation", - description: "You can not repute yourself.", - }, - }, - }, - }, - }, - profile: { - addons: { - view: { - embed: { - title: "Profile", - reputation: "Reputation (Global)", - level: "Level (Guild)", - points: "Points (Guild)", - credits: "Credits (Guild)", - language_code: "Language Code (Global)", - }, - }, - settings: { - embed: { - title: "Profile", - description: "Following settings is set", - fields: { language: "Language" }, - }, - }, - }, - }, - }, - }, - sv: { - general: { not_available: "Otillgänglig" }, - commands: { - credits: { - general: { - credits_one: "{{count}} krona", - credits_other: "{{count}} kronor", - }, - addons: { - balance: { embed: { title: "Krediter" } }, - gift: { embed: { title: "Gåva" } }, - }, - }, - reputation: { - addons: { - give: { - version01: { - embed: { - title: ":medal: Omdöme", - description: - "Du har redan gett omdöme inom den senaste dagen, du kan inte ge ett omdöme just nu!", - }, - }, - version02: { - embed: { - title: ":medal: Omdöme", - description: "Du har gett {{user}} ett {{type}} omdöme!", - }, - }, - version03: { - embed: { - title: ":medal: Omdöme", - description: "Du kan inte ge dig själv ett omdöme.", - }, - }, - }, - }, - }, - - profile: { - addons: { - view: { - embed: { - title: "Profil", - reputation: "Omdöme (Globalt)", - level: "Nivå (Server)", - points: "Poäng (Server)", - credits: "Krediter (Server)", - language_code: "Språkkod (Globalt)", - }, - }, - settings: { - embed: { - title: "Profil", - description: "Följande inställningar är satta", - fields: { language: "Språk" }, - }, - }, - }, - }, - }, - }, - de: { - general: { not_available: "Nicht verfügbar" }, - commands: { - credits: { - general: { - credits_one: "{{count}} Guthaben", - credits_other: "{{count}} Guthaben", - }, - addons: { - balance: { embed: { title: "Guthaben" } }, - gift: { embed: { title: "Geschenk" } }, - }, - }, - reputation: { - addons: { - give: { - version01: { - embed: { - title: ":medal: Ruf", - description: - "Du hast dir am letzten Tag einen Ruf verschafft, den du jetzt nicht rühmen kannst!", - }, - }, - version02: { - embed: { - title: ":medal: Ruf", - description: - "Du hast {{user}} einen {{type}} Ruf gegeben!", - }, - }, - version03: { - embed: { - title: ":medal: Ruf", - description: "Du kannst dich nicht selbst rühmen.", - }, - }, - }, - }, - }, - profile: { - addons: { - view: { - embed: { - title: "Profil", - reputation: "Ruf (Weltweit)", - level: "Level (Gilde)", - points: "Punkte (Gilde)", - credits: "Guthaben (Gilde)", - language_code: "Sprachcode (Weltweit)", - }, - }, - settings: { - embed: { - title: "Profile", - description: "Folgende Einstellungen werden vorgenommen", - fields: { language: "Sprache" }, - }, - }, - }, - }, - }, - }, - }, - }) - .then(async () => { - logger.debug(`i18next initialized`); - }) - .catch(async (error) => { - logger.error(`i18next failed to initialize: ${error}`); - }); -}; diff --git a/src/logger/index.ts b/src/logger/index.ts index ae96924..c82f2ae 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -3,24 +3,31 @@ import "winston-daily-rotate-file"; const { combine, timestamp, printf, colorize, align, json } = winston.format; -export default winston.createLogger({ - level: process.env.LOG_LEVEL || "silly", - transports: [ - new winston.transports.DailyRotateFile({ - filename: "logs/combined-%DATE%.log", - datePattern: "YYYY-MM-DD", - maxFiles: "14d", - format: combine(timestamp(), json()), - }), - new winston.transports.Console({ - format: combine( - colorize({ all: true }), - timestamp({ - format: "YYYY-MM-DD HH:MM:ss", +module.exports = { + // Logger initialized async-hronously + logger: async () => { + return winston.createLogger({ + level: process.env.LOG_LEVEL || "silly", + transports: [ + new winston.transports.DailyRotateFile({ + filename: "logs/combined-%DATE%.log", + datePattern: "YYYY-MM-DD", + maxFiles: "14d", + format: combine(timestamp(), json()), }), - align(), - printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`) - ), - }), - ], -}); + new winston.transports.Console({ + format: combine( + colorize({ all: true }), + timestamp({ + format: "YYYY-MM-DD HH:MM:ss", + }), + align(), + printf( + (info) => `[${info.timestamp}] ${info.level}: ${info.message}` + ) + ), + }), + ], + }); + }, +}; diff --git a/src/plugins/config/index.ts b/src/plugins/config/index.ts new file mode 100644 index 0000000..9cf1ab4 --- /dev/null +++ b/src/plugins/config/index.ts @@ -0,0 +1,64 @@ +// 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/settings/guild/modules/audits.ts b/src/plugins/config/modules/audits.ts similarity index 75% rename from src/plugins/settings/guild/modules/audits.ts rename to src/plugins/config/modules/audits.ts index f04ff97..817c990 100644 --- a/src/plugins/settings/guild/modules/audits.ts +++ b/src/plugins/config/modules/audits.ts @@ -1,8 +1,8 @@ // Dependencies -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, Permissions } from "discord.js"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -14,7 +14,13 @@ import { ChannelType } from "discord-api-types/v10"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("audits") .setDescription("Audits") @@ -25,12 +31,16 @@ export default { option .setName("channel") .setDescription("Channel for audit messages.") - .addChannelType(ChannelType.GuildText as number) + .addChannelTypes(ChannelType.GuildText) ); }, execute: async (interaction: CommandInteraction) => { - // Destructure member - const { options, guild } = interaction; + if (interaction.guild == null) return; + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + + const { guild, options } = interaction; // Get options const status = options?.getBoolean("status"); @@ -42,7 +52,7 @@ export default { }); if (guildDB === null) { - return logger?.verbose(`Guild not found in database.`); + return logger?.silly(`Guild not found in database.`); } // Modify values @@ -52,7 +62,7 @@ export default { // Save guild await guildDB?.save()?.then(async () => { - logger?.verbose(`Guild audits updated.`); + logger?.silly(`Guild audits updated.`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/settings/guild/modules/credits.ts b/src/plugins/config/modules/credits.ts similarity index 87% rename from src/plugins/settings/guild/modules/credits.ts rename to src/plugins/config/modules/credits.ts index f7bd893..3e20851 100644 --- a/src/plugins/settings/guild/modules/credits.ts +++ b/src/plugins/config/modules/credits.ts @@ -1,8 +1,8 @@ // Dependencies -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, Permissions } from "discord.js"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; //Handlers import logger from "@logger"; @@ -13,7 +13,13 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("credits") .setDescription(`Credits`) @@ -45,9 +51,14 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - // Destructure member + 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"); @@ -62,7 +73,7 @@ export default { }); if (guildDB === null) { - return logger?.verbose(`Guild is null`); + return logger?.silly(`Guild is null`); } // Modify values @@ -80,7 +91,7 @@ export default { // Save guild await guildDB?.save()?.then(async () => { - logger?.verbose(`Guild saved`); + logger?.silly(`Guild saved`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/config/modules/embeds.ts b/src/plugins/config/modules/embeds.ts new file mode 100644 index 0000000..f4f3a15 --- /dev/null +++ b/src/plugins/config/modules/embeds.ts @@ -0,0 +1,129 @@ +// 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 new file mode 100644 index 0000000..e14701a --- /dev/null +++ b/src/plugins/config/modules/index.ts @@ -0,0 +1,9 @@ +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/settings/guild/modules/points.ts b/src/plugins/config/modules/points.ts similarity index 84% rename from src/plugins/settings/guild/modules/points.ts rename to src/plugins/config/modules/points.ts index f484f22..a7e24a2 100644 --- a/src/plugins/settings/guild/modules/points.ts +++ b/src/plugins/config/modules/points.ts @@ -1,8 +1,8 @@ // Dependencies -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, Permissions } from "discord.js"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -13,7 +13,13 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("points") .setDescription("Points") @@ -35,6 +41,11 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + // Destructure member const { options, guild } = interaction; @@ -50,7 +61,7 @@ export default { }); if (guildDB === null) { - return logger?.verbose(`Guild not found in database.`); + return logger?.silly(`Guild not found in database.`); } // Modify values @@ -63,7 +74,7 @@ export default { // Save guild await guildDB?.save()?.then(async () => { - logger?.verbose(`Guild points updated.`); + logger?.silly(`Guild points updated.`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/settings/guild/modules/pterodactyl.ts b/src/plugins/config/modules/pterodactyl.ts similarity index 75% rename from src/plugins/settings/guild/modules/pterodactyl.ts rename to src/plugins/config/modules/pterodactyl.ts index 14b0bf4..b7162d6 100644 --- a/src/plugins/settings/guild/modules/pterodactyl.ts +++ b/src/plugins/config/modules/pterodactyl.ts @@ -1,8 +1,8 @@ // Dependencies -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, Permissions } from "discord.js"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -14,7 +14,13 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("pterodactyl") .setDescription("Controlpanel.gg") @@ -32,7 +38,10 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - // Destructure member + if (interaction.guild == null) return; + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure member const { options, guild } = interaction; // Get options @@ -47,7 +56,7 @@ export default { { new: true, upsert: true } ) .then(async () => { - logger?.verbose(`Updated API credentials.`); + logger?.silly(`Updated API credentials.`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/settings/guild/modules/shop.ts b/src/plugins/config/modules/shop.ts similarity index 79% rename from src/plugins/settings/guild/modules/shop.ts rename to src/plugins/config/modules/shop.ts index 5c633a0..e9f4d9f 100644 --- a/src/plugins/settings/guild/modules/shop.ts +++ b/src/plugins/config/modules/shop.ts @@ -1,8 +1,8 @@ // Dependencies -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, Permissions } from "discord.js"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -10,11 +10,16 @@ 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 { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("shop") .setDescription("Shop") @@ -30,7 +35,10 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - // Destructure member + if (interaction.guild == null) return; + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure member const { options, guild } = interaction; // Get options @@ -43,7 +51,7 @@ export default { }); if (guildDB === null) { - return logger?.verbose(`Guild not found in database.`); + return logger?.silly(`Guild not found in database.`); } // Modify values @@ -56,7 +64,7 @@ export default { // Save guild await guildDB?.save()?.then(async () => { - logger?.verbose(`Guild shop updated.`); + logger?.silly(`Guild shop updated.`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/settings/guild/modules/welcome.ts b/src/plugins/config/modules/welcome.ts similarity index 84% rename from src/plugins/settings/guild/modules/welcome.ts rename to src/plugins/config/modules/welcome.ts index d45b642..48399c7 100644 --- a/src/plugins/settings/guild/modules/welcome.ts +++ b/src/plugins/config/modules/welcome.ts @@ -1,8 +1,8 @@ // Dependencies -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, Permissions } from "discord.js"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -14,7 +14,13 @@ import { ChannelType } from "discord-api-types/v10"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("welcome") .setDescription("Welcome") @@ -25,14 +31,16 @@ export default { option .setName("join-channel") .setDescription("Channel for join messages.") - .addChannelType(ChannelType.GuildText as number) + .addChannelTypes(ChannelType.GuildText) ) + .addChannelOption((option) => option .setName("leave-channel") .setDescription("Channel for leave messages.") - .addChannelType(ChannelType.GuildText as number) + .addChannelTypes(ChannelType.GuildText) ) + .addStringOption((option) => option .setName("leave-message") @@ -45,7 +53,10 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - // Destructure member + if (interaction.guild == null) return; + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure member const { options, guild } = interaction; // Get options @@ -61,7 +72,7 @@ export default { }); if (guildDB === null) { - return logger?.verbose(`Guild not found in database.`); + return logger?.silly(`Guild not found in database.`); } // Modify values @@ -83,7 +94,7 @@ export default { // Save guild await guildDB?.save()?.then(async () => { - logger?.verbose(`Guild welcome updated.`); + logger?.silly(`Guild welcome updated.`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/counters/index.ts b/src/plugins/counters/index.ts index 4da895c..ca3fc56 100644 --- a/src/plugins/counters/index.ts +++ b/src/plugins/counters/index.ts @@ -1,25 +1,26 @@ -// Dependencies import { CommandInteraction } from "discord.js"; import { SlashCommandBuilder } from "@discordjs/builders"; - import logger from "@logger"; -import modules from "@root/plugins/counters/modules"; +import modules from "@plugins/counters/modules"; export default { - metadata: { author: "Zyner" }, - data: new SlashCommandBuilder() + modules, + + builder: new SlashCommandBuilder() .setName("counters") .setDescription("View guild counters") - .addSubcommand(modules.view.data), + + .addSubcommand(modules.view.builder), + async execute(interaction: CommandInteraction) { const { options } = interaction; - if (options?.getSubcommand() === "view") { - logger?.verbose(`Executing view subcommand`); + if (options.getSubcommand() === "view") { + logger.silly(`Executing view subcommand`); return modules.view.execute(interaction); } - logger?.verbose(`Unknown subcommand ${options?.getSubcommand()}`); + logger.silly(`Unknown subcommand ${options.getSubcommand()}`); }, }; diff --git a/src/plugins/counters/modules/index.ts b/src/plugins/counters/modules/index.ts index dc539f8..8c5ea76 100644 --- a/src/plugins/counters/modules/index.ts +++ b/src/plugins/counters/modules/index.ts @@ -1,3 +1,3 @@ -import view from "./view"; +import view from "@plugins/counters/modules/view"; export default { view }; diff --git a/src/plugins/counters/modules/view/index.ts b/src/plugins/counters/modules/view/index.ts index e7635c2..d672f0d 100644 --- a/src/plugins/counters/modules/view/index.ts +++ b/src/plugins/counters/modules/view/index.ts @@ -1,20 +1,15 @@ -// Dependencies +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"; -// Configuration -import { - errorColor, - successColor, - footerText, - footerIcon, -} from "@config/embed"; - export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: true, ephemeral: false }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("view") .setDescription(`View a guild counter`) @@ -25,14 +20,26 @@ export default { `The channel that contains the counter you want to view` ) .setRequired(true) - .addChannelType(ChannelType.GuildText as number) + .addChannelTypes(ChannelType.GuildText) ); }, + execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); const { options, guild } = interaction; const discordChannel = options?.getChannel("channel"); + const embed = new MessageEmbed() + .setTitle("[:1234:] Counters (View)") + .setTimestamp(new Date()) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }); + const counter = await counterSchema?.findOne({ guildId: guild?.id, channelId: discordChannel?.id, @@ -41,32 +48,20 @@ export default { if (counter === null) { return interaction?.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:1234:] Counters (View)") + embed .setDescription(`No counter found for channel ${discordChannel}!`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), + .setColor(errorColor), ], }); } return interaction?.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:1234:] Counters (View)") + embed .setDescription( - `Viewing counter for channel ${discordChannel} with count ${counter.counter}.` + `Viewing counter for channel ${discordChannel}: ${counter.counter}!` ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ - text: footerText, - iconURL: footerIcon, - }), + .setColor(successColor), ], }); }, diff --git a/src/plugins/credits/index.ts b/src/plugins/credits/index.ts index 4566feb..5e8033f 100644 --- a/src/plugins/credits/index.ts +++ b/src/plugins/credits/index.ts @@ -1,44 +1,39 @@ -// Dependencies import { SlashCommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; - import logger from "@logger"; -// Modules -import modules from "@root/plugins/credits/modules"; +import modules from "@plugins/credits/modules"; export default { - metadata: { author: "Zyner" }, - data: new SlashCommandBuilder() + modules, + + builder: new SlashCommandBuilder() .setName("credits") .setDescription("Manage your credits.") - .addSubcommand(modules.balance.data) - .addSubcommand(modules.gift.data) - .addSubcommand(modules.top.data) - .addSubcommand(modules.work.data), + + .addSubcommand(modules.balance.builder) + .addSubcommand(modules.gift.builder) + .addSubcommand(modules.top.builder) + .addSubcommand(modules.work.builder), + async execute(interaction: CommandInteraction) { const { options } = interaction; - if (options?.getSubcommand() === "balance") { - logger?.verbose(`Executing balance subcommand`); - return modules.balance.execute(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()}`); } - - if (options?.getSubcommand() === "gift") { - logger?.verbose(`Executing gift subcommand`); - return modules.gift.execute(interaction); - } - - if (options?.getSubcommand() === "top") { - logger?.verbose(`Executing top command`); - return modules.top.execute(interaction); - } - - if (options?.getSubcommand() === "work") { - logger?.verbose(`Executing work command`); - return modules.work.execute(interaction); - } - - logger?.verbose(`Unknown subcommand ${options?.getSubcommand()}`); }, }; diff --git a/src/plugins/credits/modules/balance/index.ts b/src/plugins/credits/modules/balance/index.ts index 0c4f2c2..60ee17e 100644 --- a/src/plugins/credits/modules/balance/index.ts +++ b/src/plugins/credits/modules/balance/index.ts @@ -1,52 +1,42 @@ -// Dependencies +import getEmbedConfig from "@helpers/getEmbedConfig"; + import { CommandInteraction, MessageEmbed } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - import logger from "@logger"; -// Configurations -import { - errorColor, - successColor, - footerText, - footerIcon, -} from "@config/embed"; - -// Helpers -import pluralize from "@helpers/pluralize"; import fetchUser from "@helpers/fetchUser"; export default { - data: (command: SlashCommandSubcommandBuilder) => { - return ( - command - .setName("balance") - .setDescription(`View a user's balance`) - - // User - .addUserOption((option) => - option - .setName("user") - .setDescription(`The user whose balance you want to view`) - ) - ); + metadata: { guildOnly: true, ephemeral: true }, + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("balance") + .setDescription(`View a user's balance`) + .addUserOption((option) => + option + .setName("user") + .setDescription(`The user whose balance you want to view`) + ); }, execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); const { options, user, guild } = interaction; - const discordUser = options?.getUser("user"); + const discordUser = options.getUser("user"); + + const embed = new MessageEmbed() + .setTitle("[:dollar:] Balance") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); if (guild === null) { - logger?.verbose(`Guild is null`); + logger.silly(`Guild is null`); - return interaction?.editReply({ + return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Balance)") - .setDescription(`You can only use this command in a guild!`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed.setDescription("Guild is not found").setColor(errorColor), ], }); } @@ -54,50 +44,38 @@ export default { const userObj = await fetchUser(discordUser || user, guild); if (userObj === null) { - logger?.verbose(`User not found`); + logger.silly(`User not found`); - return interaction?.editReply({ + return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Balance)") - .setDescription(`Could not find user ${discordUser || user}`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription( + "User is not found. Please try again with a valid user." + ) + .setColor(errorColor), ], }); } if (userObj.credits === null) { - logger?.verbose(`User has no credits`); + logger.silly(`User has no credits`); - return interaction?.editReply({ + return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Balance)") - .setDescription(`${discordUser || user} has no credits!`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed.setDescription("Credits not found").setColor(errorColor), ], }); } - logger?.verbose(`Found user ${discordUser || user}`); + logger.silly(`Found user ${discordUser || user}`); - return interaction?.editReply({ + return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Balance)") + embed .setDescription( - `${discordUser || user} has ${pluralize( - userObj.credits, - `credit` - )}!` + `${discordUser || user} currently has ${userObj.credits} credits.` ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(successColor), ], }); }, diff --git a/src/plugins/credits/modules/gift/index.ts b/src/plugins/credits/modules/gift/index.ts index 2ce6a62..e0766bd 100644 --- a/src/plugins/credits/modules/gift/index.ts +++ b/src/plugins/credits/modules/gift/index.ts @@ -2,12 +2,7 @@ import { CommandInteraction, MessageEmbed } from "discord.js"; // Configurations -import { - errorColor, - successColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -21,7 +16,9 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: true, ephemeral: true }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("gift") .setDescription(`Gift a user credits`) @@ -42,38 +39,38 @@ 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; - const optionUser = options?.getUser("user"); - const optionAmount = options?.getInteger("amount"); - const optionReason = options?.getString("reason"); + const optionUser = options.getUser("user"); + const optionAmount = options.getInteger("amount"); + const optionReason = options.getString("reason"); + + const embed = new MessageEmbed() + .setTitle("[:dollar:] Gift") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); if (guild === null) { - logger?.verbose(`Guild is null`); + logger.silly(`Guild is null`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") - .setDescription(`We can not find your guild!`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed.setDescription("Guild is not found").setColor(errorColor), ], }); } if (optionUser === null) { - logger?.verbose(`User not found`); + logger.silly(`User not found`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") - .setDescription(`We can not find your requested user!`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription(`User is not found in this guild`) + .setColor(errorColor), ], }); } @@ -85,170 +82,147 @@ export default { const toUserDB = await fetchUser(optionUser, guild); if (fromUserDB === null) { - logger?.verbose(`User not found`); + logger.silly(`User not found`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") + embed .setDescription( - `We can not find your requested from user in our database!` + "You do not have any credits. Please write something in the chat to get some." ) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(errorColor), ], }); } if (toUserDB === null) { - logger?.verbose(`User not found`); + logger.silly(`User not found`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") + embed .setDescription( - `We can not find your requested to user in our database!` + "The user you want to gift credits to does not have any credits. Please wait until that user has typed something in the chat to get some." ) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(errorColor), ], }); } // If receiver is same as sender - if (optionUser?.id === user?.id) { - logger?.verbose(`User is same as sender`); + if (optionUser.id === user.id) { + logger.silly(`User is same as sender`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") - .setDescription(`You can not pay yourself!`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription( + "You can't gift credits to yourself. Please choose a different user." + ) + .setColor(errorColor), ], }); } // If amount is null if (optionAmount === null) { - logger?.verbose(`Amount is null`); + logger.silly(`Amount is null`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") - .setDescription(`We could not read your requested amount!`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription( + "Please specify the amount of credits you want to gift." + ) + .setColor(errorColor), ], }); } // If amount is zero or below if (optionAmount <= 0) { - logger?.verbose(`Amount is zero or below`); + logger.silly(`Amount is zero or below`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") - .setDescription(`You can't gift zero or below!`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription( + "Please specify a valid amount of credits you want to gift." + ) + .setColor(errorColor), ], }); } // If user has below gifting amount - if (fromUserDB?.credits < optionAmount) { - logger?.verbose(`User has below gifting amount`); + if (fromUserDB.credits < optionAmount) { + logger.silly(`User has below gifting amount`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") + embed .setDescription( - `You have insufficient credits. Your balance is ${fromUserDB?.credits}!` + "You don't have enough credits to gift that amount. Please try again with a lower amount." ) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(errorColor), ], }); } // If toUserDB has no credits if (toUserDB === null) { - logger?.verbose(`User has no credits`); + logger.silly(`User has no credits`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") + embed .setDescription( - `We can not find your requested to user in our database!` + "The user you want to gift credits to does not have any credits. Please wait until that user has typed something in the chat to get some." ) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(errorColor), ], }); } - // Withdraw amount from fromUserDB - fromUserDB.credits -= optionAmount; - - // Deposit amount to toUserDB - toUserDB.credits += optionAmount; - // Save users - await saveUser(fromUserDB, toUserDB)?.then(async () => { + await saveUser(fromUserDB, toUserDB).then(async () => { // Get DM user object - const dmUser = client?.users?.cache?.get(optionUser?.id); + const dmUser = client.users.cache.get(optionUser.id); + + if (dmUser == null) return; // Send DM to user await dmUser - ?.send({ + .send({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") + embed .setDescription( - `You have received ${optionAmount} credits from ${ - user?.tag - } with reason ${ - optionReason ? ` with reason: ${optionReason}` : "" - }!` + `${ + user.tag + } has gifted you ${optionAmount} credits with reason: ${ + optionReason || "unspecified" + }` ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(successColor), ], }) .catch(async (error) => - logger?.error(`[Gift] Error sending DM to user: ${error}`) + logger.error(`[Gift] Error sending DM to user: ${error}`) ); - logger?.verbose( - `[Gift] Successfully gifted ${optionAmount} credits to ${optionUser?.tag}` + logger.silly( + `[Gift] Successfully gifted ${optionAmount} credits to ${optionUser.tag}` ); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Gift)") + embed .setDescription( - `Successfully gifted ${optionAmount} credits to ${optionUser?.tag}!` + `Successfully gifted ${optionAmount} credits to ${ + optionUser.tag + } with reason: ${optionReason || "unspecified"}` ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(successColor), ], }); }); diff --git a/src/plugins/credits/modules/index.ts b/src/plugins/credits/modules/index.ts index 20edcaf..9b144f2 100644 --- a/src/plugins/credits/modules/index.ts +++ b/src/plugins/credits/modules/index.ts @@ -1,6 +1,6 @@ -import balance from "./balance"; -import gift from "./gift"; -import top from "./top"; -import work from "./work"; +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/credits/modules/top/index.ts b/src/plugins/credits/modules/top/index.ts index c27f4df..747f1bc 100644 --- a/src/plugins/credits/modules/top/index.ts +++ b/src/plugins/credits/modules/top/index.ts @@ -1,48 +1,66 @@ -// Dependencies -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import getEmbedConfig from "@helpers/getEmbedConfig"; + import { CommandInteraction, MessageEmbed } from "discord.js"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import logger from "@logger"; -import userSchema from "@schemas/user"; - -// Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; - -// Helpers -import pluralize from "@helpers/pluralize"; +import userSchema, { IUser } from "@schemas/user"; export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: true, ephemeral: false }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command.setName("top").setDescription(`View the top users`); }, execute: async (interaction: CommandInteraction) => { - // Get all users in the guild + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); + const { guild } = interaction; - const usersDB = await userSchema.find({ guildId: interaction?.guild?.id }); + const embed = new MessageEmbed() + .setTitle("[:dollar:] Top") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + if (guild === null) { + logger.silly(`Guild is null`); + + return interaction.editReply({ + embeds: [ + embed + .setDescription( + "Guild is not found. Please try again with a valid guild." + ) + .setColor(errorColor), + ], + }); + } + + const usersDB = await userSchema.find({ guildId: guild.id }); const topTen = usersDB // Sort them after credits amount (ascending) - .sort((a, b) => (a?.credits > b?.credits ? -1 : 1)) + .sort((a, b) => (a.credits > b.credits ? -1 : 1)) // Return the top 10 .slice(0, 10); // Create entry object - const entry = (x: any, index: number) => - `${index + 1}. <@${x?.userId}> - ${pluralize(x?.credits, "credit")}`; + const entry = (x: IUser, index: number) => + `${index + 1}. <@${x.userId}> - ${x.credits} credits`; return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Top)") + embed .setDescription( - `Top 10 users with the most credits. + `Below are the top 10 users in this guild. - ${topTen.map(entry).join("\n")}` + ${topTen.map(entry).join("\n")} + ` ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(successColor), ], }); }, diff --git a/src/plugins/credits/modules/work/index.ts b/src/plugins/credits/modules/work/index.ts index 2ffb20d..1cde8cb 100644 --- a/src/plugins/credits/modules/work/index.ts +++ b/src/plugins/credits/modules/work/index.ts @@ -4,7 +4,7 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import Chance from "chance"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -17,13 +17,25 @@ import fetchUser from "@helpers/fetchUser"; import fetchGuild from "@helpers/fetchGuild"; export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: true, ephemeral: true }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command.setName("work").setDescription(`Work to earn credits`); }, execute: async (interaction: CommandInteraction) => { - // Destructure member + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); // Destructure member const { guild, user } = interaction; + const embed = new MessageEmbed() + .setTitle("[:dollar:] Work") + .setTimestamp(new Date()) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }); + // Chance module const chance = new Chance(); @@ -35,25 +47,22 @@ export default { }); if (guild === null) { - return logger?.verbose(`Guild is null`); + return logger?.silly(`Guild is null`); } const guildDB = await fetchGuild(guild); // If user is not on timeout if (isTimeout) { - logger?.verbose(`User ${user?.id} is on timeout`); + logger?.silly(`User ${user?.id} is on timeout`); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Work)") + embed .setDescription( - `You can not work while on timeout, please wait ${guildDB?.credits.workTimeout} seconds.` + `You are on timeout, please wait ${guildDB?.credits.workTimeout} seconds.` ) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(errorColor), ], }); } @@ -66,24 +75,21 @@ export default { const userDB = await fetchUser(user, guild); if (userDB === null) { - return logger?.verbose(`User not found`); + return logger?.silly(`User not found`); } userDB.credits += creditsEarned; await userDB?.save()?.then(async () => { - logger?.verbose( + logger?.silly( `User ${userDB?.userId} worked and earned ${creditsEarned} credits` ); return interaction.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:dollar:] Credits (Work)") - .setDescription(`You worked and earned ${creditsEarned} credits`) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription(`You worked and earned ${creditsEarned} credits.`) + .setColor(successColor), ], }); }); @@ -96,7 +102,7 @@ export default { }); setTimeout(async () => { - logger?.verbose(`Removing timeout for user ${user?.id}`); + logger?.silly(`Removing timeout for user ${user?.id}`); // When timeout is out, remove it from the database await timeoutSchema?.deleteOne({ diff --git a/src/plugins/fun/index.ts b/src/plugins/fun/index.ts new file mode 100644 index 0000000..9ba8e81 --- /dev/null +++ b/src/plugins/fun/index.ts @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..2b59097 --- /dev/null +++ b/src/plugins/fun/modules/index.ts @@ -0,0 +1,5 @@ +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 new file mode 100644 index 0000000..be62672 --- /dev/null +++ b/src/plugins/fun/modules/meme.ts @@ -0,0 +1,41 @@ +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/groups/counters/index.ts b/src/plugins/manage/groups/counters/index.ts deleted file mode 100644 index dc3c255..0000000 --- a/src/plugins/manage/groups/counters/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Dependencies -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -import logger from "@logger"; - -// Modules -import moduleCreate from "./modules/create"; -import moduleDelete from "./modules/delete"; - -// Function -export default { - data: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("counters") - .setDescription("Manage guild counters.") - .addSubcommand(moduleCreate.data) - .addSubcommand(moduleDelete.data); - }, - execute: async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options?.getSubcommand() === "create") { - logger?.verbose(`Executing create subcommand`); - - return moduleCreate.execute(interaction); - } - - if (options?.getSubcommand() === "delete") { - logger?.verbose(`Executing delete subcommand`); - - return moduleDelete.execute(interaction); - } - - logger?.verbose(`Unknown subcommand ${options?.getSubcommand()}`); - }, -}; diff --git a/src/plugins/manage/groups/credits/index.ts b/src/plugins/manage/groups/credits/index.ts deleted file mode 100644 index 9209f8a..0000000 --- a/src/plugins/manage/groups/credits/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; - -import logger from "@logger"; - -// Modules -import moduleGive from "./modules/give"; -import moduleSet from "./modules/set"; -import moduleTake from "./modules/take"; -import moduleTransfer from "./modules/transfer"; - -// Function -export default { - data: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("credits") - .setDescription("Manage the credits of a user.") - .addSubcommand(moduleGive.data) - .addSubcommand(moduleSet.data) - .addSubcommand(moduleTake.data) - .addSubcommand(moduleTransfer.data); - }, - execute: async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options?.getSubcommand() === "give") { - logger?.verbose(`Executing give subcommand`); - - return moduleGive.execute(interaction); - } - - if (options?.getSubcommand() === "set") { - logger?.verbose(`Executing set subcommand`); - - return moduleSet.execute(interaction); - } - - if (options?.getSubcommand() === "take") { - logger?.verbose(`Executing take subcommand`); - - return moduleTake.execute(interaction); - } - - if (options?.getSubcommand() === "transfer") { - logger?.verbose(`Executing transfer subcommand`); - - return moduleTransfer.execute(interaction); - } - - logger?.verbose(`No subcommand found`); - }, -}; diff --git a/src/plugins/manage/index.ts b/src/plugins/manage/index.ts index 32d066d..993ba9f 100644 --- a/src/plugins/manage/index.ts +++ b/src/plugins/manage/index.ts @@ -1,54 +1,37 @@ //Dependencies import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction, Permissions, MessageEmbed } from "discord.js"; - -// Configurations -import { errorColor, footerText, footerIcon } from "@config/embed"; +import { CommandInteraction } from "discord.js"; // Groups -import credits from "./groups/credits"; -import counters from "./groups/counters"; +import modules from "@plugins/manage/modules"; import logger from "@logger"; // Function export default { - metadata: { author: "Zyner" }, - data: new SlashCommandBuilder() + modules, + + builder: new SlashCommandBuilder() .setName("manage") .setDescription("Manage the bot.") - .addSubcommandGroup(counters.data) - .addSubcommandGroup(credits.data), + .addSubcommandGroup(modules.counters.builder) + .addSubcommandGroup(modules.credits.builder), async execute(interaction: CommandInteraction) { // Destructure - const { memberPermissions, options } = interaction; - - // Check permission - if (!memberPermissions?.has(Permissions?.FLAGS?.MANAGE_GUILD)) { - return interaction?.editReply({ - embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage") - .setDescription(`You do not have the permission to manage the bot.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), - ], - }); - } + const { options } = interaction; if (options?.getSubcommandGroup() === "credits") { - logger?.verbose(`Subcommand group is credits`); + logger?.silly(`Subcommand group is credits`); - return credits.execute(interaction); + return modules.credits.execute(interaction); } if (options?.getSubcommandGroup() === "counters") { - logger?.verbose(`Subcommand group is counters`); + logger?.silly(`Subcommand group is counters`); - return counters.execute(interaction); + return modules.counters.execute(interaction); } - logger?.verbose(`Subcommand group is not credits or counters`); + 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 new file mode 100644 index 0000000..3fb9d3e --- /dev/null +++ b/src/plugins/manage/modules/counters/index.ts @@ -0,0 +1,39 @@ +// 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/groups/counters/modules/create/index.ts b/src/plugins/manage/modules/counters/modules/add/index.ts similarity index 64% rename from src/plugins/manage/groups/counters/modules/create/index.ts rename to src/plugins/manage/modules/counters/modules/add/index.ts index 32c83fb..b4c6e99 100644 --- a/src/plugins/manage/groups/counters/modules/create/index.ts +++ b/src/plugins/manage/modules/counters/modules/add/index.ts @@ -1,17 +1,11 @@ // Dependencies -import { MessageEmbed, CommandInteraction } from "discord.js"; +import { MessageEmbed, CommandInteraction, Permissions } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { ChannelType } from "discord-api-types/v10"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; -// Handlers import logger from "@logger"; // Models @@ -19,16 +13,22 @@ import counterSchema from "@schemas/counter"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command - .setName("create") + .setName("add") .setDescription("Add a counter to your guild.") .addChannelOption((option) => option .setName("channel") .setDescription("The channel to send the counter to.") .setRequired(true) - .addChannelType(ChannelType.GuildText as number) + .addChannelTypes(ChannelType.GuildText) ) .addStringOption((option) => option @@ -43,12 +43,20 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); const { options, guild } = interaction; const discordChannel = options?.getChannel("channel"); const countingWord = options?.getString("word"); const startValue = options?.getNumber("start"); + const embed = new MessageEmbed() + .setTitle("[:toolbox:] Counters - Add") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + const counter = await counterSchema?.findOne({ guildId: guild?.id, channelId: discordChannel?.id, @@ -57,12 +65,9 @@ export default { if (counter) { return interaction?.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Counters (Create)") + embed .setDescription(`A counter already exists for this channel.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + .setColor(errorColor), ], }); } @@ -75,16 +80,15 @@ export default { counter: startValue || 0, }) .then(async () => { - logger?.verbose(`Created counter`); + logger?.silly(`Created counter`); return interaction?.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Counters (Create)") - .setDescription(`Created counter for ${discordChannel}`) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription( + `Successfully created counter for ${discordChannel?.name}.` + ) + .setColor(successColor), ], }); }); diff --git a/src/plugins/manage/modules/counters/modules/index.ts b/src/plugins/manage/modules/counters/modules/index.ts new file mode 100644 index 0000000..2f55183 --- /dev/null +++ b/src/plugins/manage/modules/counters/modules/index.ts @@ -0,0 +1,4 @@ +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/groups/counters/modules/delete/index.ts b/src/plugins/manage/modules/counters/modules/remove/index.ts similarity index 53% rename from src/plugins/manage/groups/counters/modules/delete/index.ts rename to src/plugins/manage/modules/counters/modules/remove/index.ts index 67bd222..8a61687 100644 --- a/src/plugins/manage/groups/counters/modules/delete/index.ts +++ b/src/plugins/manage/modules/counters/modules/remove/index.ts @@ -1,13 +1,8 @@ // Dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; +import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -19,39 +14,52 @@ import { ChannelType } from "discord-api-types/v10"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command - .setName("delete") + .setName("remove") .setDescription(`Delete a counter from your guild.`) .addChannelOption((option) => option .setName("channel") .setDescription("The channel to delete the counter from.") .setRequired(true) - .addChannelType(ChannelType.GuildText as number) + .addChannelTypes(ChannelType.GuildText) ); }, execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); const { options, guild } = interaction; const discordChannel = options?.getChannel("channel"); + const embed = new MessageEmbed() + .setTitle("[:toolbox:] Counters - Remove") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + const counter = await counterSchema?.findOne({ guildId: guild?.id, channelId: discordChannel?.id, }); if (counter === null) { - logger?.verbose(`Counter is null`); + logger?.silly(`Counter is null`); return interaction?.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Counters (Delete)") - .setDescription(`The counter for this channel does not exist.`) - .setTimestamp(new Date()) - .setColor(errorColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription( + ":x: There is no counter in this channel. Please add a counter first." + ) + .setColor(errorColor), ], }); } @@ -62,16 +70,15 @@ export default { channelId: discordChannel?.id, }) ?.then(async () => { - logger?.verbose(`Counter deleted`); + logger?.silly(`Counter deleted`); return interaction?.editReply({ embeds: [ - new MessageEmbed() - .setTitle("[:toolbox:] Manage - Counters (Delete)") - .setDescription(`The counter for this channel has been deleted.`) - .setTimestamp(new Date()) - .setColor(successColor) - .setFooter({ text: footerText, iconURL: footerIcon }), + embed + .setDescription( + ":white_check_mark: Counter deleted successfully." + ) + .setColor(successColor), ], }); }) diff --git a/src/plugins/manage/modules/credits/index.ts b/src/plugins/manage/modules/credits/index.ts new file mode 100644 index 0000000..a12cf11 --- /dev/null +++ b/src/plugins/manage/modules/credits/index.ts @@ -0,0 +1,43 @@ +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/groups/credits/modules/give/index.ts b/src/plugins/manage/modules/credits/modules/give/index.ts similarity index 85% rename from src/plugins/manage/groups/credits/modules/give/index.ts rename to src/plugins/manage/modules/credits/modules/give/index.ts index 2345852..cec0376 100644 --- a/src/plugins/manage/groups/credits/modules/give/index.ts +++ b/src/plugins/manage/modules/credits/modules/give/index.ts @@ -1,14 +1,9 @@ // Dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; +import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -21,7 +16,13 @@ import fetchUser from "@helpers/fetchUser"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("give") .setDescription("Give credits to a user.") @@ -39,7 +40,9 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - // Destructure + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); // Destructure const { guild, options } = interaction; const discordReceiver = options?.getUser("user"); @@ -47,7 +50,7 @@ export default { // If amount option is null if (creditAmount === null) { - logger?.verbose(`Amount is null`); + logger?.silly(`Amount is null`); return interaction?.editReply({ embeds: [ @@ -63,7 +66,7 @@ export default { // If amount is zero or below if (creditAmount <= 0) { - logger?.verbose(`Amount is zero or below`); + logger?.silly(`Amount is zero or below`); return interaction?.editReply({ embeds: [ @@ -78,7 +81,7 @@ export default { } if (discordReceiver === null) { - logger?.verbose(`Discord receiver is null`); + logger?.silly(`Discord receiver is null`); return interaction?.editReply({ embeds: [ @@ -92,7 +95,7 @@ export default { }); } if (guild === null) { - logger?.verbose(`Guild is null`); + logger?.silly(`Guild is null`); return interaction?.editReply({ embeds: [ @@ -109,7 +112,7 @@ export default { const toUser = await fetchUser(discordReceiver, guild); if (toUser === null) { - logger?.verbose(`To user is null`); + logger?.silly(`To user is null`); return interaction?.editReply({ embeds: [ @@ -141,7 +144,7 @@ export default { // Save toUser await toUser?.save()?.then(async () => { - logger?.verbose(`Saved toUser`); + logger?.silly(`Saved toUser`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/manage/modules/credits/modules/index.ts b/src/plugins/manage/modules/credits/modules/index.ts new file mode 100644 index 0000000..1dd2f3d --- /dev/null +++ b/src/plugins/manage/modules/credits/modules/index.ts @@ -0,0 +1,6 @@ +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/groups/credits/modules/set/index.ts b/src/plugins/manage/modules/credits/modules/set/index.ts similarity index 84% rename from src/plugins/manage/groups/credits/modules/set/index.ts rename to src/plugins/manage/modules/credits/modules/set/index.ts index 53103a4..a7d86a2 100644 --- a/src/plugins/manage/groups/credits/modules/set/index.ts +++ b/src/plugins/manage/modules/credits/modules/set/index.ts @@ -1,13 +1,8 @@ // Dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; +import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -20,7 +15,13 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("set") .setDescription("Set the amount of credits a user has.") @@ -38,6 +39,9 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); const { options, guild } = interaction; const discordUser = options.getUser("user"); @@ -45,7 +49,7 @@ export default { // If amount is null if (creditAmount === null) { - logger?.verbose(`Amount is null`); + logger?.silly(`Amount is null`); return interaction?.editReply({ embeds: [ @@ -60,7 +64,7 @@ export default { } if (discordUser === null) { - logger?.verbose(`User is null`); + logger?.silly(`User is null`); return interaction?.editReply({ embeds: [ @@ -74,7 +78,7 @@ export default { }); } if (guild === null) { - logger?.verbose(`Guild is null`); + logger?.silly(`Guild is null`); return interaction?.editReply({ embeds: [ @@ -93,7 +97,7 @@ export default { // If toUser does not exist if (toUser === null) { - logger?.verbose(`User does not exist`); + logger?.silly(`User does not exist`); return interaction?.editReply({ embeds: [ @@ -109,7 +113,7 @@ export default { // If toUser.credits does not exist if (toUser?.credits === null) { - logger?.verbose(`User does not have any credits`); + logger?.silly(`User does not have any credits`); return interaction?.editReply({ embeds: [ @@ -128,7 +132,7 @@ export default { // Save toUser await toUser?.save()?.then(async () => { - logger?.verbose(`Saved user`); + logger?.silly(`Saved user`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/manage/groups/credits/modules/take/index.ts b/src/plugins/manage/modules/credits/modules/take/index.ts similarity index 84% rename from src/plugins/manage/groups/credits/modules/take/index.ts rename to src/plugins/manage/modules/credits/modules/take/index.ts index 40e74bb..9c823b9 100644 --- a/src/plugins/manage/groups/credits/modules/take/index.ts +++ b/src/plugins/manage/modules/credits/modules/take/index.ts @@ -1,13 +1,8 @@ // Dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; +import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -21,7 +16,13 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("take") .setDescription("Take credits from a user.") @@ -39,7 +40,9 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - // Destructure + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); // Destructure const { guild, options } = interaction; // User option @@ -50,7 +53,7 @@ export default { // If amount is null if (optionAmount === null) { - logger?.verbose(`Amount is null`); + logger?.silly(`Amount is null`); return interaction?.editReply({ embeds: [ @@ -66,7 +69,7 @@ export default { // If amount is zero or below if (optionAmount <= 0) { - logger?.verbose(`Amount is zero or below`); + logger?.silly(`Amount is zero or below`); return interaction?.editReply({ embeds: [ @@ -81,7 +84,7 @@ export default { } if (optionUser === null) { - logger?.verbose(`Discord receiver is null`); + logger?.silly(`Discord receiver is null`); return interaction?.editReply({ embeds: [ @@ -95,7 +98,7 @@ export default { }); } if (guild === null) { - logger?.verbose(`Guild is null`); + logger?.silly(`Guild is null`); return interaction?.editReply({ embeds: [ @@ -114,7 +117,7 @@ export default { // If toUser does not exist if (toUser === null) { - logger?.verbose(`ToUser is null`); + logger?.silly(`ToUser is null`); return interaction?.editReply({ embeds: [ @@ -130,7 +133,7 @@ export default { // If toUser.credits does not exist if (toUser?.credits === null) { - logger?.verbose(`ToUser.credits is null`); + logger?.silly(`ToUser.credits is null`); return interaction?.editReply({ embeds: [ @@ -149,7 +152,7 @@ export default { // Save toUser await toUser?.save()?.then(async () => { - logger?.verbose(`Saved toUser`); + logger?.silly(`Saved toUser`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/manage/groups/credits/modules/transfer/index.ts b/src/plugins/manage/modules/credits/modules/transfer/index.ts similarity index 87% rename from src/plugins/manage/groups/credits/modules/transfer/index.ts rename to src/plugins/manage/modules/credits/modules/transfer/index.ts index 0dbd07d..19de23a 100644 --- a/src/plugins/manage/groups/credits/modules/transfer/index.ts +++ b/src/plugins/manage/modules/credits/modules/transfer/index.ts @@ -1,13 +1,8 @@ // Dependencies -import { CommandInteraction, MessageEmbed } from "discord.js"; +import { CommandInteraction, MessageEmbed, Permissions } from "discord.js"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Handlers import logger from "@logger"; @@ -21,7 +16,13 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { + guildOnly: true, + ephemeral: true, + permissions: [Permissions.FLAGS.MANAGE_GUILD], + }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("transfer") .setDescription("Transfer credits from one user to another.") @@ -45,7 +46,9 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { - // Destructure member + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); // Destructure member const { guild, options } = interaction; // Get options @@ -55,7 +58,7 @@ export default { // If amount is null if (optionAmount === null) { - logger?.verbose(`Amount is null`); + logger?.silly(`Amount is null`); return interaction?.editReply({ embeds: [ @@ -70,7 +73,7 @@ export default { } if (guild === null) { - logger?.verbose(`Guild is null`); + logger?.silly(`Guild is null`); return interaction?.editReply({ embeds: [ @@ -84,7 +87,7 @@ export default { }); } if (optionFromUser === null) { - logger?.verbose(`From user is null`); + logger?.silly(`From user is null`); return interaction?.editReply({ embeds: [ @@ -98,7 +101,7 @@ export default { }); } if (optionToUser === null) { - logger?.verbose(`To user is null`); + logger?.silly(`To user is null`); return interaction?.editReply({ embeds: [ @@ -120,7 +123,7 @@ export default { // If toUser does not exist if (fromUser === null) { - logger?.verbose(`From user does not exist`); + logger?.silly(`From user does not exist`); return interaction?.editReply({ embeds: [ @@ -138,7 +141,7 @@ export default { // If toUser.credits does not exist if (!fromUser?.credits) { - logger?.verbose(`From user does not have credits`); + logger?.silly(`From user does not have credits`); return interaction?.editReply({ embeds: [ @@ -156,7 +159,7 @@ export default { // If toUser does not exist if (toUser === null) { - logger?.verbose(`To user does not exist`); + logger?.silly(`To user does not exist`); return interaction?.editReply({ embeds: [ @@ -174,7 +177,7 @@ export default { // If toUser.credits does not exist if (toUser?.credits === null) { - logger?.verbose(`To user does not have credits`); + logger?.silly(`To user does not have credits`); return interaction?.editReply({ embeds: [ @@ -198,7 +201,7 @@ export default { // Save users await saveUser(fromUser, toUser)?.then(async () => { - logger?.verbose(`Saved users`); + logger?.silly(`Saved users`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/manage/modules/index.ts b/src/plugins/manage/modules/index.ts new file mode 100644 index 0000000..61797b1 --- /dev/null +++ b/src/plugins/manage/modules/index.ts @@ -0,0 +1,4 @@ +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 index a255a7a..d37ad1b 100644 --- a/src/plugins/profile/index.ts +++ b/src/plugins/profile/index.ts @@ -3,36 +3,28 @@ import { SlashCommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; // Modules -import view from "./modules/view"; +import modules from "@plugins/profile/modules"; // Handlers import logger from "@logger"; // Function export default { - metadata: { author: "Zyner" }, - data: new SlashCommandBuilder() + modules, + + builder: new SlashCommandBuilder() .setName("profile") .setDescription("Check a profile.") - .addSubcommand((subcommand) => - subcommand - .setName("view") - .setDescription("View a profile.") - .addUserOption((option) => - option - .setName("target") - .setDescription("The profile you wish to view") - ) - ), + .addSubcommand(modules.view.builder), async execute(interaction: CommandInteraction) { const { options } = interaction; if (options?.getSubcommand() === "view") { - logger?.verbose(`Executing view subcommand`); + logger?.silly(`Executing view subcommand`); - return view(interaction); + return modules.view.execute(interaction); } - logger?.verbose(`No subcommand found`); + logger?.silly(`No subcommand found`); }, }; diff --git a/src/plugins/profile/modules/index.ts b/src/plugins/profile/modules/index.ts new file mode 100644 index 0000000..1dc8e1b --- /dev/null +++ b/src/plugins/profile/modules/index.ts @@ -0,0 +1,3 @@ +import view from "@plugins/profile/modules/view"; + +export default { view }; diff --git a/src/plugins/profile/modules/view.ts b/src/plugins/profile/modules/view.ts index 6e05df5..c6704a3 100644 --- a/src/plugins/profile/modules/view.ts +++ b/src/plugins/profile/modules/view.ts @@ -2,74 +2,91 @@ import { CommandInteraction } from "discord.js"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Models import fetchUser from "@helpers/fetchUser"; import logger from "@logger"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { client, options, user, guild } = interaction; +export default { + metadata: { guildOnly: true, ephemeral: false }, - // Target information - const target = options?.getUser("target"); + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("view") + .setDescription("View a profile.") + .addUserOption((option) => + option.setName("target").setDescription("The profile you wish to view") + ); + }, - // Discord User Information - const discordUser = await client?.users?.fetch( - `${target ? target?.id : user?.id}` - ); + execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); // Destructure + const { client, options, user, guild } = interaction; - if (guild === null) { - return logger?.verbose(`Guild is null`); - } + // Target information + const target = options?.getUser("target"); - // User Information - const userObj = await fetchUser(discordUser, guild); + // Discord User Information + const discordUser = await client?.users?.fetch( + `${target ? target?.id : user?.id}` + ); - // Embed object - const embed = { - author: { - name: `${discordUser?.username}#${discordUser?.discriminator}`, - icon_url: discordUser?.displayAvatarURL(), - }, - color: successColor, - fields: [ - { - name: `:dollar: Credits`, - value: `${userObj?.credits || "Not found"}`, - inline: true, + if (guild === null) { + return logger?.silly(`Guild is null`); + } + + // User Information + const userObj = await fetchUser(discordUser, guild); + + // Embed object + const embed = { + author: { + name: `${discordUser?.username}#${discordUser?.discriminator}`, + icon_url: discordUser?.displayAvatarURL(), }, - { - name: `:squeeze_bottle: Level`, - value: `${userObj?.level || "Not found"}`, - inline: true, + color: successColor, + fields: [ + { + name: `:dollar: Credits`, + value: `${userObj?.credits || "Not found"}`, + inline: true, + }, + { + name: `:squeeze_bottle: Level`, + value: `${userObj?.level || "Not found"}`, + inline: true, + }, + { + name: `:squeeze_bottle: Points`, + value: `${userObj?.points || "Not found"}`, + inline: true, + }, + { + name: `:loudspeaker: Reputation`, + value: `${userObj?.reputation || "Not found"}`, + inline: true, + }, + { + name: `:rainbow_flag: Language`, + value: `${userObj?.language || "Not found"}`, + inline: true, + }, + ], + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, }, - { - name: `:squeeze_bottle: Points`, - value: `${userObj?.points || "Not found"}`, - inline: true, - }, - { - name: `:loudspeaker: Reputation`, - value: `${userObj?.reputation || "Not found"}`, - inline: true, - }, - { - name: `:rainbow_flag: Language`, - value: `${userObj?.language || "Not found"}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; + }; - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); + // Return interaction reply + return interaction?.editReply({ embeds: [embed] }); + }, }; diff --git a/src/plugins/reputation/index.ts b/src/plugins/reputation/index.ts index 4908641..f5237ab 100644 --- a/src/plugins/reputation/index.ts +++ b/src/plugins/reputation/index.ts @@ -3,27 +3,27 @@ import { SlashCommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; // Modules -import give from "./modules/give"; +import modules from "./modules"; // Handlers import logger from "@logger"; // Function export default { - metadata: { author: "Zyner" }, - data: new SlashCommandBuilder() + modules, + builder: new SlashCommandBuilder() .setName("reputation") .setDescription("Manage reputation.") - .addSubcommand(give.data), + .addSubcommand(modules.give.builder), async execute(interaction: CommandInteraction) { const { options } = interaction; if (options?.getSubcommand() === "give") { - logger?.verbose(`Executing give subcommand`); + logger?.silly(`Executing give subcommand`); - await give.execute(interaction); + await modules.give.execute(interaction); } - logger?.verbose(`No subcommand found`); + logger?.silly(`No subcommand found`); }, }; diff --git a/src/plugins/reputation/modules/give.ts b/src/plugins/reputation/modules/give.ts index 4da08db..949f9c9 100644 --- a/src/plugins/reputation/modules/give.ts +++ b/src/plugins/reputation/modules/give.ts @@ -2,12 +2,7 @@ import { CommandInteraction } from "discord.js"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; import { timeout } from "@config/reputation"; @@ -21,7 +16,9 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: true, ephemeral: true }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("give") .setDescription("Give reputation to a user") @@ -36,12 +33,19 @@ export default { .setName("type") .setDescription("What type of reputation you want to repute") .setRequired(true) - .addChoice("Positive", "positive") - .addChoice("Negative", "negative") + .addChoices( + { name: "Positive", value: "positive" }, + { + name: "Negative", + value: "negative", + } + ) ); }, execute: async (interaction: CommandInteraction) => { - // Destructure + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); // Destructure const { options, user, guild } = interaction; // Target option @@ -51,14 +55,14 @@ export default { const optionType = options?.getString("type"); if (guild === null) { - return logger?.verbose(`Guild is null`); + return logger?.silly(`Guild is null`); } // User information const userObj = await fetchUser(user, guild); if (userObj === null) { - return logger?.verbose(`User is null`); + return logger?.silly(`User is null`); } // Check if user has a timeout @@ -70,7 +74,7 @@ export default { // If user is not on timeout if (isTimeout) { - logger?.verbose(`User is on timeout`); + logger?.silly(`User is on timeout`); return interaction?.editReply({ embeds: [ @@ -90,7 +94,7 @@ export default { // Do not allow self reputation if (optionTarget?.id === user?.id) { - logger?.verbose(`User is trying to give reputation to self`); + logger?.silly(`User is trying to give reputation to self`); return interaction?.editReply({ embeds: [ @@ -110,21 +114,21 @@ export default { // If type is positive if (optionType === "positive") { - logger?.verbose(`User is giving positive reputation`); + logger?.silly(`User is giving positive reputation`); userObj.reputation += 1; } // If type is negative else if (optionType === "negative") { - logger?.verbose(`User is giving negative reputation`); + logger?.silly(`User is giving negative reputation`); userObj.reputation -= 1; } // Save user await userObj?.save()?.then(async () => { - logger?.verbose(`User reputation has been updated`); + logger?.silly(`User reputation has been updated`); await timeoutSchema?.create({ guildId: guild?.id, @@ -149,7 +153,7 @@ export default { }); setTimeout(async () => { - logger?.verbose(`Removing timeout`); + logger?.silly(`Removing timeout`); await timeoutSchema?.deleteOne({ guildId: guild?.id, diff --git a/src/plugins/reputation/modules/index.ts b/src/plugins/reputation/modules/index.ts new file mode 100644 index 0000000..f6746fd --- /dev/null +++ b/src/plugins/reputation/modules/index.ts @@ -0,0 +1,3 @@ +import give from "@plugins/reputation/modules/give"; + +export default { give }; diff --git a/src/plugins/settings/guild/index.ts b/src/plugins/settings/guild/index.ts deleted file mode 100644 index 37218bd..0000000 --- a/src/plugins/settings/guild/index.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Dependencies -import { Permissions, CommandInteraction } from "discord.js"; - -// Configurations -import { errorColor, footerText, footerIcon } from "@config/embed"; - -// Handlers -import logger from "@logger"; - -// Modules -import pterodactyl from "./modules/pterodactyl"; -import credits from "./modules/credits"; -import points from "./modules/points"; -import welcome from "./modules/welcome"; -import audits from "./modules/audits"; -import shop from "./modules/shop"; -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; - -// Function -export default { - data: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("guild") - .setDescription("Guild settings.") - .addSubcommand(pterodactyl.data) - .addSubcommand(credits.data) - .addSubcommand(points.data) - .addSubcommand(welcome.data) - .addSubcommand(audits.data) - .addSubcommand(shop.data); - }, - execute: async (interaction: CommandInteraction) => { - // Destructure member - const { memberPermissions, options } = interaction; - - // Check permission - if (!memberPermissions?.has(Permissions?.FLAGS?.MANAGE_GUILD)) { - logger?.verbose(`User does not have permission to execute command.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":tools: Settings - Guild", - color: errorColor, - description: "You do not have permission to use this command.", - timestamp: new Date(), - footer: { - iconURL: footerIcon as string, - text: footerText as string, - }, - }, - ], - }); - } - - if (options?.getSubcommand() === "pterodactyl") { - logger?.verbose(`Executing pterodactyl subcommand`); - - return pterodactyl.execute(interaction); - } - - if (options?.getSubcommand() === "credits") { - logger?.verbose(`Executing credits subcommand`); - - return credits.execute(interaction); - } - - if (options?.getSubcommand() === "points") { - logger?.verbose(`Executing points subcommand`); - - return points.execute(interaction); - } - - if (options?.getSubcommand() === "welcome") { - logger?.verbose(`Executing welcome subcommand`); - - return welcome.execute(interaction); - } - - if (options?.getSubcommand() === "audits") { - logger?.verbose(`Executing audit subcommand`); - - return audits.execute(interaction); - } - - if (options?.getSubcommand() === "shop") { - logger?.verbose(`Executing shop subcommand`); - - return shop.execute(interaction); - } - - logger?.verbose(`No subcommand found`); - }, -}; diff --git a/src/plugins/settings/index.ts b/src/plugins/settings/index.ts deleted file mode 100644 index 682fc30..0000000 --- a/src/plugins/settings/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Groups -import guildGroup from "./guild"; -import userGroup from "./user"; - -// Handlers -import logger from "@logger"; - -// Function -export default { - metadata: { author: "Zyner" }, - data: new SlashCommandBuilder() - .setName("settings") - .setDescription("Manage settings.") - .addSubcommandGroup(guildGroup.data) - .addSubcommandGroup(userGroup.data), - - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - if (options.getSubcommandGroup() === "guild") { - logger.verbose(`Executing guild subcommand`); - - return guildGroup.execute(interaction); - } - - if (options.getSubcommandGroup() === "user") { - logger.verbose(`Executing user subcommand`); - - return userGroup.execute(interaction); - } - - logger.verbose(`No subcommand group found`); - }, -}; diff --git a/src/plugins/settings/user/index.ts b/src/plugins/settings/user/index.ts deleted file mode 100644 index e3c572f..0000000 --- a/src/plugins/settings/user/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Dependencies -import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Handlers -import logger from "@logger"; - -// Modules -import appearance from "./modules/appearance"; - -// Function -export default { - data: (group: SlashCommandSubcommandGroupBuilder) => { - return group - .setName("user") - .setDescription("User settings.") - .addSubcommand((command) => - command - .setName("appearance") - .setDescription("User appearance settings.") - .addStringOption((option) => - option - .setName("language") - .setDescription("Set the language.") - .addChoice("English", "en") - .addChoice("Swedish", "sv") - ) - ); - }, - execute: async (interaction: CommandInteraction) => { - const { options } = interaction; - - if (options?.getSubcommand() === "appearance") { - logger?.verbose(`Executing appearance subcommand`); - - await appearance(interaction); - } - - logger?.verbose(`No subcommand found`); - }, -}; diff --git a/src/plugins/settings/user/modules/appearance.ts b/src/plugins/settings/user/modules/appearance.ts deleted file mode 100644 index bf0a4eb..0000000 --- a/src/plugins/settings/user/modules/appearance.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; - -// Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; - -// Handlers -import logger from "@logger"; - -// Models -import fetchUser from "@helpers/fetchUser"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { options, user, guild } = interaction; - - // Get options - const language = options?.getString("language"); - - if (guild === null) { - return logger?.verbose(`Guild is null`); - } - - // Get user object - const userDB = await fetchUser(user, guild); - - if (userDB === null) { - return logger?.verbose(`User is null`); - } - - // Modify values - userDB.language = language !== null ? language : userDB?.language; - - // Save guild - await userDB?.save()?.then(async () => { - logger?.verbose(`Updated user language.`); - - return interaction?.editReply({ - embeds: [ - { - title: ":hammer: Settings - User [Appearance]", - description: "Successfully updated user settings.", - color: successColor, - fields: [ - { - name: "🏳️‍🌈 Language", - value: `${userDB?.language}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }, - ], - }); - }); -}; diff --git a/src/plugins/shop/index.ts b/src/plugins/shop/index.ts index 7d222a8..330306e 100644 --- a/src/plugins/shop/index.ts +++ b/src/plugins/shop/index.ts @@ -3,37 +3,35 @@ import { SlashCommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; // Modules -import pterodactyl from "./modules/pterodactyl"; - -// Groups -import roles from "./roles"; +import modules from "./modules"; // Handlers import logger from "../../logger"; // Function export default { - metadata: { author: "Zyner" }, - data: new SlashCommandBuilder() + modules, + + builder: new SlashCommandBuilder() .setName("shop") .setDescription("Shop for credits and custom roles.") - .addSubcommand(pterodactyl.data) - .addSubcommandGroup(roles.data), + .addSubcommand(modules.pterodactyl.builder) + .addSubcommandGroup(modules.roles.builder), async execute(interaction: CommandInteraction) { const { options } = interaction; if (options?.getSubcommand() === "pterodactyl") { - logger.verbose(`Executing pterodactyl subcommand`); + logger.silly(`Executing pterodactyl subcommand`); - return pterodactyl.execute(interaction); + return modules.pterodactyl.execute(interaction); } if (options?.getSubcommandGroup() === "roles") { - logger?.verbose(`Subcommand group is roles`); + logger?.silly(`Subcommand group is roles`); - return roles.execute(interaction); + return modules.roles.execute(interaction); } - logger?.verbose(`No subcommand found.`); + logger?.silly(`No subcommand found.`); }, }; diff --git a/src/plugins/shop/modules/index.ts b/src/plugins/shop/modules/index.ts new file mode 100644 index 0000000..c356cae --- /dev/null +++ b/src/plugins/shop/modules/index.ts @@ -0,0 +1,4 @@ +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/pterodactyl.ts b/src/plugins/shop/modules/pterodactyl.ts index 01cd120..f3ef08b 100644 --- a/src/plugins/shop/modules/pterodactyl.ts +++ b/src/plugins/shop/modules/pterodactyl.ts @@ -1,31 +1,22 @@ -// Dependencies import { CommandInteraction } from "discord.js"; import { v4 as uuidv4 } from "uuid"; import axios from "axios"; -// Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; -// Handlers import logger from "@logger"; import encryption from "@handlers/encryption"; -// Helpers import pluralize from "@helpers/pluralize"; -// Models import apiSchema from "@schemas/api"; import fetchUser from "@helpers/fetchUser"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -// Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: true, ephemeral: true }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("pterodactyl") .setDescription("Buy pterodactyl power.") @@ -36,14 +27,15 @@ export default { ); }, execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); const { options, guild, user, client } = interaction; - // Get options const optionAmount = options?.getInteger("amount"); - // If amount is null if (optionAmount === null) { - logger?.verbose(`Amount is null.`); + logger?.silly(`Amount is null.`); return interaction?.editReply({ embeds: [ @@ -62,22 +54,19 @@ export default { } if (guild === null) { - return logger?.verbose(`Guild is null`); + return logger?.silly(`Guild is null`); } - // Get user object const userDB = await fetchUser(user, guild); if (userDB === null) { - return logger?.verbose(`User is null`); + return logger?.silly(`User is null`); } - // Get DM user object const dmUser = client?.users?.cache?.get(user?.id); - // Stop if amount or user credits is below 100 if ((optionAmount || userDB?.credits) < 100) { - logger?.verbose(`Amount or user credits is below 100.`); + logger?.silly(`Amount or user credits is below 100.`); return interaction?.editReply({ embeds: [ @@ -101,9 +90,8 @@ export default { }); } - // Stop if amount or user credits is above 1.000.000 if ((optionAmount || userDB?.credits) > 1000000) { - logger?.verbose(`Amount or user credits is above 1.000.000.`); + logger?.silly(`Amount or user credits is above 1.000.000.`); return interaction?.editReply({ embeds: [ @@ -128,9 +116,8 @@ export default { }); } - // Stop if user credits is below amount if (userDB?.credits < optionAmount) { - logger?.verbose(`User credits is below amount.`); + logger?.silly(`User credits is below amount.`); return interaction?.editReply({ embeds: [ @@ -154,15 +141,12 @@ export default { }); } - // Generate a unique voucher for the user const code = uuidv4(); - // Get api object const apiCredentials = await apiSchema?.findOne({ guildId: guild?.id, }); - // Create a api instance const api = axios?.create({ baseURL: apiCredentials?.url, headers: { @@ -170,13 +154,10 @@ export default { }, }); - // Get shop URL const shopUrl = apiCredentials?.url?.replace("/api", "/store"); - // Make API request await api - // Make a post request to the API ?.post("vouchers", { uses: 1, code, @@ -184,19 +165,16 @@ export default { memo: `${interaction?.createdTimestamp} - ${interaction?.user?.id}`, }) - // If successful ?.then(async () => { - logger?.verbose(`Successfully created voucher.`); + logger?.silly(`Successfully created voucher.`); - // Withdraw amount from user credits userDB.credits -= optionAmount || userDB?.credits; - // Save new credits await userDB ?.save() - // If successful + ?.then(async () => { - logger?.verbose(`Successfully saved new credits.`); + logger?.silly(`Successfully saved new credits.`); await dmUser?.send({ embeds: [ @@ -237,9 +215,8 @@ export default { }); }) - // If error occurs .catch(async (error) => { - logger?.verbose(`Error saving new credits. - ${error}`); + logger?.silly(`Error saving new credits. - ${error}`); return interaction?.editReply({ embeds: [ @@ -258,9 +235,8 @@ export default { }); }) - // If error occurs .catch(async (error: any) => { - logger?.verbose(`Error creating voucher. - ${error}`); + logger?.silly(`Error creating voucher. - ${error}`); return interaction?.editReply({ embeds: [ diff --git a/src/plugins/shop/roles/index.ts b/src/plugins/shop/modules/roles/index.ts similarity index 62% rename from src/plugins/shop/roles/index.ts rename to src/plugins/shop/modules/roles/index.ts index 96ae1ca..33ece41 100644 --- a/src/plugins/shop/roles/index.ts +++ b/src/plugins/shop/modules/roles/index.ts @@ -3,26 +3,31 @@ import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; // Handlers -import logger from "../../../logger"; +import logger from "@logger"; -import { errorColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; // Modules -import buy from "./modules/buy"; -import cancel from "./modules/cancel"; +import modules from "./modules"; import guildSchema from "@schemas/guild"; // Function export default { - data: (group: SlashCommandSubcommandGroupBuilder) => { + modules, + + builder: (group: SlashCommandSubcommandGroupBuilder) => { return group .setName("roles") .setDescription("Shop for custom roles.") - .addSubcommand(buy.data) - .addSubcommand(cancel.data); + .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({ @@ -32,7 +37,7 @@ export default { if (guildDB === null) return; if (!guildDB.shop.roles.status) { - logger.verbose(`Shop roles disabled.`); + logger.silly(`Shop roles disabled.`); return interaction?.editReply({ embeds: [ @@ -51,15 +56,15 @@ export default { } if (options?.getSubcommand() === "buy") { - logger.verbose(`Executing buy subcommand`); + logger.silly(`Executing buy subcommand`); - await buy.execute(interaction); + await modules.buy.execute(interaction); } if (options?.getSubcommand() === "cancel") { - logger.verbose(`Executing cancel subcommand`); + logger.silly(`Executing cancel subcommand`); - await cancel.execute(interaction); + await modules.cancel.execute(interaction); } }, }; diff --git a/src/plugins/shop/roles/modules/buy.ts b/src/plugins/shop/modules/roles/modules/buy.ts similarity index 81% rename from src/plugins/shop/roles/modules/buy.ts rename to src/plugins/shop/modules/roles/modules/buy.ts index 0baf7ed..4e4528d 100644 --- a/src/plugins/shop/roles/modules/buy.ts +++ b/src/plugins/shop/modules/roles/modules/buy.ts @@ -6,12 +6,8 @@ import { } from "discord.js"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; + // Models import shopRolesSchema from "@schemas/shopRole"; import guildSchema from "@schemas/guild"; @@ -25,7 +21,9 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: true, ephemeral: true }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("buy") .setDescription("Buy a custom role.") @@ -33,14 +31,19 @@ export default { option .setName("name") .setDescription("Name of the role you wish to buy.") + .setRequired(true) ) .addStringOption((option) => option .setName("color") .setDescription("Color of the role you wish to buy.") + .setRequired(true) ); }, execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); const { options, guild, user, member } = interaction; const optionName = options?.getString("name"); @@ -48,7 +51,7 @@ export default { // If amount is null if (optionName === null) { - logger?.verbose(`Name is null.`); + logger?.silly(`Name is null.`); return interaction?.editReply({ embeds: [ @@ -81,15 +84,15 @@ export default { const userDB = await fetchUser(user, guild); if (userDB === null) { - return logger?.verbose(`User is null`); + return logger?.silly(`User is null`); } if (guildDB === null) { - return logger?.verbose(`Guild is null`); + return logger?.silly(`Guild is null`); } if (guildDB.shop === null) { - return logger?.verbose(`Shop is null`); + return logger?.silly(`Shop is null`); } const { pricePerHour } = guildDB.shop.roles; @@ -108,7 +111,7 @@ export default { await (member?.roles as GuildMemberRoleManager)?.add(role?.id); - logger?.verbose(`Role ${role?.name} was bought by ${user?.tag}`); + logger?.silly(`Role ${role?.name} was bought by ${user?.tag}`); return interaction?.editReply({ embeds: [ @@ -135,7 +138,7 @@ export default { }); }) .catch(async (error) => { - return logger?.verbose(`Role could not be created. ${error}`); + return logger?.silly(`Role could not be created. ${error}`); }); }, }; diff --git a/src/plugins/shop/roles/modules/cancel.ts b/src/plugins/shop/modules/roles/modules/cancel.ts similarity index 80% rename from src/plugins/shop/roles/modules/cancel.ts rename to src/plugins/shop/modules/roles/modules/cancel.ts index 359d79c..546dbc7 100644 --- a/src/plugins/shop/roles/modules/cancel.ts +++ b/src/plugins/shop/modules/roles/modules/cancel.ts @@ -2,12 +2,8 @@ import { CommandInteraction, GuildMemberRoleManager } from "discord.js"; // Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; + // Models import shopRolesSchema from "@schemas/shopRole"; @@ -20,21 +16,29 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: true, ephemeral: true }, + + builder: (command: SlashCommandSubcommandBuilder) => { return command .setName("cancel") .setDescription("Cancel a purchase.") .addRoleOption((option) => - option.setName("role").setDescription("Role you wish to cancel.") + option + .setName("role") + .setDescription("Role you wish to cancel.") + .setRequired(true) ); }, execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { errorColor, successColor, footerText, footerIcon } = + await getEmbedConfig(interaction.guild); const { options, guild, user, member } = interaction; const optionRole = options.getRole("role"); if (optionRole === null) { - logger?.verbose(`Role is null.`); + logger?.silly(`Role is null.`); return interaction?.editReply({ embeds: [ @@ -68,7 +72,7 @@ export default { const userDB = await fetchUser(user, guild); if (userDB === null) { - return logger?.verbose(`User is null`); + return logger?.silly(`User is null`); } await shopRolesSchema?.deleteOne({ @@ -99,7 +103,7 @@ export default { }); }) .catch(async (error) => { - return logger?.verbose(`Role could not be deleted. ${error}`); + return logger?.silly(`Role could not be deleted. ${error}`); }); }, }; diff --git a/src/plugins/shop/modules/roles/modules/index.ts b/src/plugins/shop/modules/roles/modules/index.ts new file mode 100644 index 0000000..b9e1626 --- /dev/null +++ b/src/plugins/shop/modules/roles/modules/index.ts @@ -0,0 +1,7 @@ +import buy from "./buy"; +import cancel from "./cancel"; + +export default { + buy, + cancel, +}; diff --git a/src/plugins/utilities/index.ts b/src/plugins/utilities/index.ts deleted file mode 100644 index 23c43c3..0000000 --- a/src/plugins/utilities/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Modules -import lookup from "./modules/lookup"; -import about from "./modules/about"; -import stats from "./modules/stats"; - -// Handlers -import logger from "../../logger"; - -// Function -export default { - metadata: { author: "Zyner" }, - data: new SlashCommandBuilder() - .setName("utilities") - .setDescription("Common utilities.") - .addSubcommand(lookup.data) - .addSubcommand(about.data) - .addSubcommand(stats.data), - async execute(interaction: CommandInteraction) { - const { options } = interaction; - - if (options?.getSubcommand() === "lookup") { - logger.verbose(`Executing lookup subcommand`); - - return lookup.execute(interaction); - } - - if (options?.getSubcommand() === "about") { - logger.verbose(`Executing about subcommand`); - - return about.execute(interaction); - } - - if (options?.getSubcommand() === "stats") { - logger.verbose(`Executing stats subcommand`); - - return stats.execute(interaction); - } - - logger.verbose(`No subcommand found.`); - }, -}; diff --git a/src/plugins/utilities/modules/lookup.ts b/src/plugins/utilities/modules/lookup.ts deleted file mode 100644 index 6175418..0000000 --- a/src/plugins/utilities/modules/lookup.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Dependencies -import axios from "axios"; -import { CommandInteraction } from "discord.js"; - -// Configurations -import { - successColor, - errorColor, - footerText, - footerIcon, -} from "@config/embed"; - -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; - -// Handlers -import logger from "@logger"; - -// Function -export default { - data: (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 { options } = interaction; - // Get lookup query - const query = options?.getString("query"); - - // Make API request - await axios - // Make a get request - ?.get(`http://ip-api.com/json/${query}`) - - // If successful - ?.then(async (res) => { - // If query failed - if (res?.data?.status === "fail") { - // Create embed object - const embed = { - title: ":hammer: Utilities - Lookup", - description: `${res?.data?.message}: ${res?.data?.query}`, - color: errorColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; - - // Send interaction reply - await interaction?.editReply({ embeds: [embed] }); - } - - // If query is successful - else if (res?.data?.status === "success") { - // Create embed object - const embed = { - title: ":hammer: Utilities - Lookup", - fields: [ - { - name: "AS", - value: `${res?.data?.as || "Not available"}`, - }, - { - name: "Country", - value: `${res?.data?.country || "Not available"}`, - }, - { - name: "Country Code", - value: `${res?.data?.countryCode || "Not available"}`, - }, - { - name: "Region", - value: `${res?.data?.region || "Not available"}`, - }, - { - name: "Region Name", - value: `${res?.data?.regionName || "Not available"}`, - }, - { - name: "City", - value: `${res?.data?.city || "Not available"}`, - }, - { - name: "ZIP Code", - value: `${res?.data?.zip || "Not available"}`, - }, - { - name: "Latitude", - value: `${res?.data?.lat || "Not available"}`, - }, - { - name: "Longitude", - value: `${res?.data?.lon || "Not available"}`, - }, - { - name: "Timezone", - value: `${res?.data?.timezone || "Not available"}`, - }, - { - name: "ISP", - value: `${res?.data?.isp || "Not available"}`, - }, - { - name: "Organization", - value: `${res?.data?.org || "Not available"}`, - }, - ], - color: successColor, - timestamp: new Date(), - footer: { - iconURL: footerIcon, - text: footerText, - }, - }; - - // Send interaction reply - await interaction?.editReply({ embeds: [embed] }); - } - }) - .catch(async (e) => { - logger?.error(e); - }); - }, -}; diff --git a/src/plugins/utility/index.ts b/src/plugins/utility/index.ts new file mode 100644 index 0000000..6dc6fff --- /dev/null +++ b/src/plugins/utility/index.ts @@ -0,0 +1,40 @@ +// 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/utilities/modules/about.ts b/src/plugins/utility/modules/about.ts similarity index 75% rename from src/plugins/utilities/modules/about.ts rename to src/plugins/utility/modules/about.ts index f618149..8011d51 100644 --- a/src/plugins/utilities/modules/about.ts +++ b/src/plugins/utility/modules/about.ts @@ -2,17 +2,23 @@ import { CommandInteraction } from "discord.js"; // Configurations -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; import { hosterName, hosterUrl } from "@config/other"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; // Function export default { - data: (command: SlashCommandSubcommandBuilder) => { + 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 ${ diff --git a/src/plugins/utility/modules/avatar.ts b/src/plugins/utility/modules/avatar.ts new file mode 100644 index 0000000..b060be3 --- /dev/null +++ b/src/plugins/utility/modules/avatar.ts @@ -0,0 +1,42 @@ +import getEmbedConfig from "@helpers/getEmbedConfig"; + +import { CommandInteraction, MessageEmbed } from "discord.js"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +export default { + metadata: { guildOnly: false, ephemeral: false }, + + builder: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("avatar") + .setDescription("Check someones avatar!)") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user whose avatar you want to check") + ); + }, + execute: async (interaction: CommandInteraction) => { + if (interaction.guild == null) return; + const { successColor, footerText, footerIcon } = await getEmbedConfig( + interaction.guild + ); + const userOption = interaction.options.getUser("user"); + + const targetUser = userOption || interaction.user; + + const embed = new MessageEmbed() + .setTitle("[:tools:] Avatar") + .setTimestamp(new Date()) + .setFooter({ text: footerText, iconURL: footerIcon }); + + return interaction.editReply({ + embeds: [ + embed + .setDescription(`${targetUser.username}'s avatar:`) + .setThumbnail(targetUser.displayAvatarURL()) + .setColor(successColor), + ], + }); + }, +}; diff --git a/src/plugins/utility/modules/index.ts b/src/plugins/utility/modules/index.ts new file mode 100644 index 0000000..80c5a64 --- /dev/null +++ b/src/plugins/utility/modules/index.ts @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000..241b7ce --- /dev/null +++ b/src/plugins/utility/modules/lookup.ts @@ -0,0 +1,122 @@ +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/plugins/utilities/modules/stats.ts b/src/plugins/utility/modules/stats.ts similarity index 85% rename from src/plugins/utilities/modules/stats.ts rename to src/plugins/utility/modules/stats.ts index 277f670..a7f44a0 100644 --- a/src/plugins/utilities/modules/stats.ts +++ b/src/plugins/utility/modules/stats.ts @@ -1,11 +1,18 @@ -import { successColor, footerText, footerIcon } from "@config/embed"; +import getEmbedConfig from "@helpers/getEmbedConfig"; + import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { CommandInteraction } from "discord.js"; export default { - data: (command: SlashCommandSubcommandBuilder) => { + metadata: { guildOnly: false, ephemeral: false }, + + builder: (command: SlashCommandSubcommandBuilder) => { 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 + ); const { client } = interaction; if (client?.uptime === null) return; let totalSeconds = client?.uptime / 1000; diff --git a/tsconfig.json b/tsconfig.json index 93449a5..8f7076a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "moduleResolution": "node", "isolatedModules": true, "outDir": "./build", + "resolveJsonModule": true, "baseUrl": "./src", "typeRoots": ["/types/common", "./node_modules/@types"], "paths": { @@ -21,7 +22,7 @@ "@events/*": ["events/*"], "@logger": ["logger"], "@database": ["database"], - "@schedules": ["schedules"], + "@jobs/*": ["jobs/*"], "@handlers/*": ["handlers/*"], "@helpers/*": ["helpers/*"], "@locale": ["locale"],