diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index 8afb368..a1e5544 100644 --- a/.cspell/custom-dictionary-workspace.txt +++ b/.cspell/custom-dictionary-workspace.txt @@ -1,5 +1,6 @@ # Custom Dictionary Words Controlpanel +cpgg dagen discordjs Följande @@ -11,6 +12,7 @@ inom inställningar inte Krediter +multistream Nivå omdöme Omdöme diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 81c7b58..6debbd7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,38 +1,32 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: 'bug' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "bug" +assignees: "VermiumSifell" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + +- Commit: [git rev-parse HEAD] +- Branch: [git branch --show-current] + +**Additional context** +Add any other context about the problem here. diff --git a/.gitignore b/.gitignore index 3a26c16..0ae1541 100644 --- a/.gitignore +++ b/.gitignore @@ -1,136 +1,142 @@ -json.sqlite -node_modules -.env -config.json -package-lock.json - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* +json.sqlite +node_modules +.env +config.json +package-lock.json + + +**/config/*.ts +!**/config/index.ts +!**/config/example.*.ts + + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/package.json b/package.json index 22bd099..ca1435c 100644 --- a/package.json +++ b/package.json @@ -39,14 +39,17 @@ "discord.js": "^13.6.0", "dotenv": "^16.0.0", "i18next": "^21.6.13", + "module-alias": "^2.2.2", "mongoose": "^6.2.3", "node-schedule": "^2.1.0", "pino": "^7.0.0-rc.9", "pino-pretty": "^7.6.1", "quick.db": "^7.1.3", "ts-node": "^10.7.0", + "tsconfig-paths": "^3.14.1", "typescript": "^4.6.3", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "winston-daily-rotate-file": "^4.6.1" }, "devDependencies": { "@types/chance": "^1.1.3", diff --git a/src/commands/admin/counters/index.ts b/src/commands/admin/counters/index.ts deleted file mode 100644 index fe635db..0000000 --- a/src/commands/admin/counters/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; - -// Handlers -import logger from "../../../handlers/logger"; - -// Modules -import add from "./modules/add"; -import remove from "./modules/remove"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { options, guild, user, commandName } = interaction; - - // Module - Add - if (options?.getSubcommand() === "add") { - // Execute Module - Add - return add(interaction); - } - - // Module - Remove - else if (options?.getSubcommand() === "remove") { - // Execute Module - Remove - return remove(interaction); - } - - // Log debug message - return logger?.debug( - `Guild: ${guild?.id} User: ${ - user?.id - } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` - ); -}; diff --git a/src/commands/admin/counters/modules/add.ts b/src/commands/admin/counters/modules/add.ts deleted file mode 100644 index 96aa5a4..0000000 --- a/src/commands/admin/counters/modules/add.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Dependencies -import { ColorResolvable, CommandInteraction } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -// Handlers -import logger from "../../../../handlers/logger"; - -// Models -import counterSchema from "../../../../helpers/database/models/counterSchema"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { options, guild, user } = interaction; - - // Channel option - const optionChannel = options?.getChannel("channel"); - - // Word option - const optionWord = options?.getString("word"); - - // Start option - const optionStart = options?.getNumber("start"); - - if (optionChannel?.type !== "GUILD_TEXT") { - // Return interaction reply - return interaction?.editReply({ - embeds: [ - { - title: ":toolbox: Admin - Counters [Add]" as string, - description: - "That channel is not supported, it needs to be a text channel." as string, - timestamp: new Date(), - color: config?.colors?.error as ColorResolvable, - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }, - ], - }); - } - - const counterExist = await counterSchema?.findOne({ - guildId: guild?.id, - channelId: optionChannel?.id, - optionWord, - }); - - if (!counterExist) { - await counterSchema?.create({ - guildId: guild?.id, - channelId: optionChannel?.id, - optionWord, - counter: optionStart || 0, - }); - - // Log debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} added ${optionChannel?.id} as a counter using word "${optionWord}" for counting.` - ); - - // Return interaction reply - return interaction?.editReply({ - embeds: [ - { - title: ":toolbox: Admin - Counters [Add]" as string, - description: `${optionChannel} is now counting when hearing word ${optionWord} and it starts at number ${ - optionStart || 0 - }.`, - timestamp: new Date(), - color: config?.colors?.success as ColorResolvable, - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }, - ], - }); - } - - // Return interaction reply - return interaction?.editReply({ - embeds: [ - { - title: ":toolbox: Admin - Counters [Add]" as string, - description: `${optionChannel} is already a counting channel.`, - timestamp: new Date(), - color: config?.colors?.error as ColorResolvable, - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }, - ], - }); -}; diff --git a/src/commands/admin/counters/modules/remove.ts b/src/commands/admin/counters/modules/remove.ts deleted file mode 100644 index d229b6e..0000000 --- a/src/commands/admin/counters/modules/remove.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Dependencies -import { ColorResolvable, CommandInteraction } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -// Handlers -import logger from "../../../../handlers/logger"; - -// Models -import counterSchema from "../../../../helpers/database/models/counterSchema"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { options, guild, user } = interaction; - - // Get options - const optionChannel = options?.getChannel("channel"); - - await counterSchema - ?.deleteOne({ - guildId: guild?.id, - channelId: optionChannel?.id, - }) - ?.then(async () => { - // Embed object - const embed = { - title: ":toolbox: Admin - Counters [Remove]" as string, - description: `${optionChannel} is no longer an counting channel.`, - timestamp: new Date(), - color: config?.colors?.success as ColorResolvable, - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); - - // Send debug message - return logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} removed ${optionChannel?.id} as a counter.` - ); -}; diff --git a/src/commands/admin/credits/index.ts b/src/commands/admin/credits/index.ts deleted file mode 100644 index 1eddcde..0000000 --- a/src/commands/admin/credits/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; - -// Modules -import give from "./modules/give"; -import take from "./modules/take"; -import set from "./modules/set"; -import transfer from "./modules/transfer"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { options } = interaction; - - // Module - Give - if (options?.getSubcommand() === "give") { - // Execute Module - Give - return give(interaction); - } - - // Module - Take - else if (options?.getSubcommand() === "take") { - // Execute Module - Take - return take(interaction); - } - - // Module - Set - else if (options?.getSubcommand() === "set") { - // Execute Module - Set - return set(interaction); - } - - // Module - Transfer - else if (options?.getSubcommand() === "transfer") { - // Execute Module - Transfer - return transfer(interaction); - } -}; diff --git a/src/commands/admin/credits/modules/give.ts b/src/commands/admin/credits/modules/give.ts deleted file mode 100644 index e220ced..0000000 --- a/src/commands/admin/credits/modules/give.ts +++ /dev/null @@ -1,132 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -// Handlers -import logger from "../../../../handlers/logger"; - -// Helpers -import creditNoun from "../../../../helpers/creditNoun"; - -// Models -import fetchUser from "../../../../helpers/fetchUser"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { guild, user, options } = interaction; - - // User option - const optionUser = options?.getUser("user"); - - // Amount option - const optionAmount = options?.getInteger("amount"); - - // If amount option is null - if (optionAmount === null) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Give]" as string, - description: "We could not read your requested amount." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If amount is zero or below - if (optionAmount <= 0) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Give]" as string, - description: "You can not give zero credits or below." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - if (optionUser === null) return; - if (guild === null) return; - - // toUser Information - const toUser = await fetchUser(optionUser, guild); - - // If toUser does not exist - if (!toUser) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Give]" as string, - description: `We could not find ${optionUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If toUser.credits does not exist - if (toUser?.credits === null) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Give]" as string, - description: `We could not find credits for ${optionUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // Deposit amount to toUser - toUser.credits += optionAmount; - - // Save toUser - await toUser?.save()?.then(async () => { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Give]" as string, - description: `We have given ${optionUser}, ${creditNoun(optionAmount)}.`, - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Log debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} gave ${ - optionUser?.id - } ${creditNoun(optionAmount)}.` - ); - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); -}; diff --git a/src/commands/admin/credits/modules/set.ts b/src/commands/admin/credits/modules/set.ts deleted file mode 100644 index c39b4f9..0000000 --- a/src/commands/admin/credits/modules/set.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -// Handlers -import logger from "../../../../handlers/logger"; - -// Helpers -import creditNoun from "../../../../helpers/creditNoun"; - -// Models -import fetchUser from "../../../../helpers/fetchUser"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { options, user, guild } = interaction; - - // User Option - const optionUser = options.getUser("user"); - - // Amount Option - const optionAmount = options.getInteger("amount"); - - // If amount is null - if (optionAmount === null) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Set]" as string, - description: "We could not read your requested amount." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - if (optionUser === null) return; - if (guild === null) return; - - // toUser Information - const toUser = await fetchUser(optionUser, guild); - - // If toUser does not exist - if (!toUser) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Set]" as string, - description: `We could not find ${optionUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If toUser.credits does not exist - if (toUser?.credits === null) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Set]" as string, - description: `We could not find credits for ${optionUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // Set toUser with amount - toUser.credits = optionAmount; - - // Save toUser - await toUser?.save()?.then(async () => { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Set]" as string, - description: `We have set ${optionUser} to ${creditNoun(optionAmount)}`, - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Log debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} set ${ - optionUser?.id - } to ${creditNoun(optionAmount)}.` - ); - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); -}; diff --git a/src/commands/admin/credits/modules/take.ts b/src/commands/admin/credits/modules/take.ts deleted file mode 100644 index fb41159..0000000 --- a/src/commands/admin/credits/modules/take.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -// Handlers -import logger from "../../../../handlers/logger"; - -// Helpers -import creditNoun from "../../../../helpers/creditNoun"; - -// Models -import fetchUser from "../../../../helpers/fetchUser"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { guild, user, options } = interaction; - - // User option - const optionUser = options?.getUser("user"); - - // Amount option - const optionAmount = options?.getInteger("amount"); - - // If amount is null - if (optionAmount === null) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Take]" as string, - description: "We could not read your requested amount." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If amount is zero or below - if (optionAmount <= 0) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Take]" as string, - description: "You can not take zero credits or below." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - if (optionUser === null) return; - if (guild === null) return; - - // toUser Information - const toUser = await fetchUser(optionUser, guild); - - // If toUser does not exist - if (!toUser) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Take]" as string, - description: `We could not find ${optionUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If toUser.credits does not exist - if (toUser?.credits === null) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Take]" as string, - description: `We could not find credits for ${optionUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // Withdraw amount from toUser - toUser.credits -= optionAmount; - - // Save toUser - await toUser?.save()?.then(async () => { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Set]" as string, - description: `We have taken ${creditNoun( - optionAmount - )} from ${optionUser}`, - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Log debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} set ${ - optionUser?.id - } to ${creditNoun(optionAmount)}.` - ); - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); -}; diff --git a/src/commands/admin/credits/modules/transfer.ts b/src/commands/admin/credits/modules/transfer.ts deleted file mode 100644 index 9e6ba15..0000000 --- a/src/commands/admin/credits/modules/transfer.ts +++ /dev/null @@ -1,171 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -// Handlers -import logger from "../../../../handlers/logger"; - -// Helpers -import creditNoun from "../../../../helpers/creditNoun"; -import saveUser from "../../../../helpers/saveUser"; - -// Models -import fetchUser from "../../../../helpers/fetchUser"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { guild, options, user } = interaction; - - // Get options - const optionFromUser = options?.getUser("from"); - const optionToUser = options?.getUser("to"); - const optionAmount = options?.getInteger("amount"); - - // If amount is null - if (optionAmount === null) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Transfer]" as string, - description: "We could not read your requested amount." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - if (guild === null) return; - if (optionFromUser === null) return; - if (optionToUser === null) return; - - // Get fromUser object - const fromUser = await fetchUser(optionFromUser, guild); - - // Get toUser object - const toUser = await fetchUser(optionToUser, guild); - - // If toUser does not exist - if (!fromUser) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Transfer]" as string, - description: `We could not find ${optionFromUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If toUser.credits does not exist - if (!fromUser?.credits) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Transfer]" as string, - description: `We could not find credits for ${optionFromUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If toUser does not exist - if (!toUser) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Transfer]" as string, - description: `We could not find ${optionToUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If toUser.credits does not exist - if (toUser?.credits === null) { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Transfer]" as string, - description: `We could not find credits for ${optionToUser} in our database.`, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // Withdraw amount from fromUser - fromUser.credits -= optionAmount; - - // Deposit amount to toUser - toUser.credits += optionAmount; - - // Save users - await saveUser(fromUser, toUser)?.then(async () => { - // Embed object - const embed = { - title: ":toolbox: Admin - Credits [Transfer]" as string, - description: `You sent ${creditNoun( - optionAmount - )} from ${optionFromUser} to ${optionToUser}.`, - color: config?.colors?.success as ColorResolvable, - fields: [ - { - name: `${optionFromUser?.username} Balance`, - value: `${fromUser?.credits}`, - inline: true, - }, - { - name: `${optionToUser?.username} Balance`, - value: `${toUser?.credits}`, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Log debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} transferred ${creditNoun( - optionAmount - )} from ${optionFromUser?.id} to ${optionToUser?.id}.` - ); - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); -}; diff --git a/src/commands/admin/index.ts b/src/commands/admin/index.ts deleted file mode 100644 index 8dd14fc..0000000 --- a/src/commands/admin/index.ts +++ /dev/null @@ -1,177 +0,0 @@ -//Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction, ColorResolvable, Permissions } from "discord.js"; - -// Configurations -import config from "../../../config.json"; - -// Handlers -import logger from "../../handlers/logger"; - -// Groups -import credits from "./credits"; -import counters from "./counters"; - -// Function -export default { - data: new SlashCommandBuilder() - .setName("admin") - .setDescription("Admin actions.") - .addSubcommandGroup((group) => - group - .setName("credits") - .setDescription("Manage credits.") - .addSubcommand((command) => - command - .setName("give") - .setDescription("Give credits to a user") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user you want to pay.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("The amount you will pay.") - .setRequired(true) - ) - ) - .addSubcommand((command) => - command - .setName("set") - .setDescription("Set credits to a user") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user you want to set credits on.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("The amount you will set.") - .setRequired(true) - ) - ) - .addSubcommand((command) => - command - .setName("take") - .setDescription("Take credits from a user") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user you want to take credits from.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("The amount you will take.") - .setRequired(true) - ) - ) - .addSubcommand((command) => - command - .setName("transfer") - .setDescription("Transfer credits from a user to another user.") - .addUserOption((option) => - option - .setName("from") - .setDescription("The user you want to take credits from.") - .setRequired(true) - ) - .addUserOption((option) => - option - .setName("to") - .setDescription("The user you want to give credits to.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("The amount you will transfer.") - .setRequired(true) - ) - ) - ) - .addSubcommandGroup((group) => - group - .setName("counters") - .setDescription("Manage counters.") - .addSubcommand((command) => - command - .setName("add") - .setDescription("Add a counter") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The counter channel.") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("word") - .setDescription("The counter word.") - .setRequired(true) - ) - .addNumberOption((option) => - option.setName("start").setDescription("Start at number X.") - ) - ) - .addSubcommand((command) => - command - .setName("remove") - .setDescription("Remove a counter") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The counter channel.") - .setRequired(true) - ) - ) - ), - async execute(interaction: CommandInteraction) { - // Destructure - const { memberPermissions, options, user, commandName, guild } = - interaction; - - // Check permission - if (!memberPermissions?.has(Permissions?.FLAGS?.MANAGE_GUILD)) { - // Embed object - const embed = { - title: ":toolbox: Admin" as string, - color: config?.colors?.error as ColorResolvable, - description: "You do not have permission to manage this!" as string, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // Group - Credits - if (options?.getSubcommandGroup() === "credits") { - // Execute Group - Credits - return credits(interaction); - } - - // Group - Counters - else if (options?.getSubcommandGroup() === "counters") { - // Execute Group - Counters - return counters(interaction); - } - - // Send debug message - return logger?.debug( - `Guild: ${guild?.id} User: ${ - user?.id - } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` - ); - }, -}; diff --git a/src/commands/counters/index.ts b/src/commands/counters/index.ts deleted file mode 100644 index 3d3542e..0000000 --- a/src/commands/counters/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; -import { SlashCommandBuilder } from "@discordjs/builders"; - -// Modules -import view from "./modules/view"; - -// Handlers -import logger from "../../handlers/logger"; - -// Function -export default { - data: new SlashCommandBuilder() - .setName("counters") - .setDescription("Manage counters.") - .addSubcommand((subcommand) => - subcommand - .setName("view") - .setDescription("View a counter.") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The counter channel you want to view") - .setRequired(true) - ) - ), - async execute(interaction: CommandInteraction) { - const { options, guild, user, commandName } = interaction; - - // Module - View - if (options?.getSubcommand() === "view") { - // Execute Module - View - return view(interaction); - } - - // Send debug message - return logger?.debug( - `Guild: ${guild?.id} User: ${ - user?.id - } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` - ); - }, -}; diff --git a/src/commands/counters/modules/view.ts b/src/commands/counters/modules/view.ts deleted file mode 100644 index 75fa4c5..0000000 --- a/src/commands/counters/modules/view.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../config.json"; - -// Models -import counterSchema from "../../../helpers/database/models/counterSchema"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { options, guild } = interaction; - - // Get options - const optionChannel = options?.getChannel("channel"); - - const counter = await counterSchema?.findOne({ - guildId: guild?.id, - channelId: optionChannel?.id, - }); - - if (!counter) { - // Create embed object - const embed = { - title: ":1234: Counters [View]" as string, - description: `${optionChannel} is not a counting channel.` as string, - timestamp: new Date(), - color: config?.colors?.error as ColorResolvable, - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // Embed object - const embed = { - title: ":1234: Counters [View]" as string, - color: config.colors.success as ColorResolvable, - description: `${optionChannel} is currently at number ${counter?.counter}.`, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - return interaction?.editReply({ embeds: [embed] }); -}; diff --git a/src/commands/credits/index.ts b/src/commands/credits/index.ts deleted file mode 100644 index dca9c4d..0000000 --- a/src/commands/credits/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Dependencies -import { SlashCommandBuilder } from "@discordjs/builders"; -import { CommandInteraction } from "discord.js"; - -// Handlers -import logger from "../../handlers/logger"; - -// Modules -import balance from "./modules/balance"; -import gift from "./modules/gift"; -import top from "./modules/top"; -import work from "./modules/work"; - -// Function -export default { - data: new SlashCommandBuilder() - .setName("credits") - .setDescription("Manage your credits.") - .addSubcommand((subcommand) => - subcommand - .setName("balance") - .setDescription("Check a user's balance.") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user whose balance you want to check.") - .setRequired(false) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName("gift") - .setDescription("Gift someone credits from your credits.") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user you want to pay.") - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("The amount you will pay.") - .setRequired(true) - ) - .addStringOption((option) => - option.setName("reason").setDescription("Your reason.") - ) - ) - .addSubcommand((subcommand) => - subcommand.setName("top").setDescription("Check the top balance.") - ) - .addSubcommand((subcommand) => - subcommand.setName("work").setDescription("Work for credits.") - ), - async execute(interaction: CommandInteraction) { - const { options, user, guild, commandName } = interaction; - - // Module - Balance - if (options?.getSubcommand() === "balance") { - // Execute Module - Balance - return balance(interaction); - } - - // Module - Gift - else if (options?.getSubcommand() === "gift") { - // Execute Module - Gift - return gift(interaction); - } - - // Module - Top - else if (options?.getSubcommand() === "top") { - // Execute Module - Top - return top(interaction); - } - - // Module - Work - else if (options?.getSubcommand() === "work") { - // Execute Module - Work - return work(interaction); - } - - // Send debug message - return logger?.debug( - `Guild: ${guild?.id} User: ${ - user?.id - } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` - ); - }, -}; diff --git a/src/commands/credits/modules/balance.ts b/src/commands/credits/modules/balance.ts deleted file mode 100644 index ec1fc03..0000000 --- a/src/commands/credits/modules/balance.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../config.json"; - -// Helpers -import creditNoun from "../../../helpers/creditNoun"; - -// Models -import fetchUser from "../../../helpers/fetchUser"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { options, user, guild } = interaction; - - // User option - const optionUser = options?.getUser("user"); - - if (guild === null) return; - - // Get credit object - const userDB = await fetchUser(optionUser || user, guild); - - // If userDB does not exist - if (userDB === null) { - // Embed object - const embed = { - title: ":dollar: Credits [Balance]" as string, - description: `We can not find ${ - optionUser || "you" - } in our database.` as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If userDB.credits does not exist - if (userDB.credits === null) { - // Embed object - const embed = { - title: ":dollar: Credits [Balance]" as string, - description: `We can not find credits for ${ - optionUser || "you" - } in our database.` as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } else { - // Embed object - const embed = { - title: ":dollar: Credits [Balance]" as string, - description: `${ - optionUser ? `${optionUser} has` : "You have" - } ${creditNoun(userDB.credits)}.` as string, - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } -}; diff --git a/src/commands/credits/modules/gift.ts b/src/commands/credits/modules/gift.ts deleted file mode 100644 index eea9201..0000000 --- a/src/commands/credits/modules/gift.ts +++ /dev/null @@ -1,187 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../config.json"; - -// Handlers -import logger from "../../../handlers/logger"; - -// Helpers -import saveUser from "../../../helpers/saveUser"; -import creditNoun from "../../../helpers/creditNoun"; - -// Models -import fetchUser from "../../../helpers/fetchUser"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure - const { options, user, guild, client } = interaction; - - // User option - const optionUser = options?.getUser("user"); - - // Amount option - const optionAmount = options?.getInteger("amount"); - - // Reason option - const optionReason = options?.getString("reason"); - - if (guild === null) return; - if (optionUser === null) return; - - // Get fromUserDB object - const fromUserDB = await fetchUser(user, guild); - - // Get toUserDB object - const toUserDB = await fetchUser(optionUser, guild); - - if (fromUserDB === null) return; - if (toUserDB === null) return; - - // If receiver is same as sender - if (optionUser?.id === user?.id) { - // Create embed object - const embed = { - title: ":dollar: Credits [Gift]" as string, - description: "You can't pay yourself." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If amount is null - if (optionAmount === null) { - // Embed object - const embed = { - title: ":dollar: Credits [Gift]" as string, - description: "We could not read your requested amount." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If amount is zero or below - if (optionAmount <= 0) { - // Embed object - const embed = { - title: ":dollar: Credits [Gift]" as string, - description: "You can't pay zero or below." as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If user has below gifting amount - if (fromUserDB?.credits < optionAmount) { - // Embed object - const embed = { - title: ":dollar: Credits [Gift]" as string, - description: - `You have insufficient credits. Your credits is ${fromUserDB?.credits}` as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // If toUserDB has no credits - if (!toUserDB) { - // Embed object - const embed = { - title: ":dollar: Credits [Gift]" as string, - description: - `That user has no credits, I can not gift credits to ${optionUser}` as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // Withdraw amount from fromUserDB - fromUserDB.credits -= optionAmount; - - // Deposit amount to toUserDB - toUserDB.credits += optionAmount; - - // Save users - await saveUser(fromUserDB, toUserDB)?.then(async () => { - // Interaction embed object - const interactionEmbed = { - title: ":dollar: Credits [Gift]", - description: `You sent ${creditNoun(optionAmount)} to ${optionUser}${ - optionReason ? ` with reason: ${optionReason}` : "" - }. Your new credits is ${creditNoun(fromUserDB?.credits)}.`, - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // DM embed object - const dmEmbed = { - title: ":dollar: Credits [Gift]" as string, - description: `You received ${creditNoun(optionAmount)} from ${user}${ - optionReason ? ` with reason: ${optionReason}` : "" - }. Your new credits is ${creditNoun(toUserDB?.credits)}.` as string, - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Get DM user object - const dmUser = client?.users?.cache?.get(interaction?.user?.id); - - // Send DM to user - await dmUser?.send({ embeds: [dmEmbed] }); - - // Send debug message - logger.debug( - `Guild: ${guild?.id} User: ${user?.id} gift sent from: ${user?.id} to: ${optionUser?.id}` - ); - - // Send interaction reply - return interaction.editReply({ - embeds: [interactionEmbed], - }); - }); -}; diff --git a/src/commands/credits/modules/top.ts b/src/commands/credits/modules/top.ts deleted file mode 100644 index 3198484..0000000 --- a/src/commands/credits/modules/top.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../config.json"; - -// Models -import userSchema from "../../../helpers/database/models/userSchema"; - -// helpers -import creditNoun from "../../../helpers/creditNoun"; - -// Function -export default async (interaction: CommandInteraction) => { - // Get all users in the guild - - const usersDB = await userSchema.find({ guildId: interaction?.guild?.id }); - - const topTen = usersDB - - // Sort them after credits amount (ascending) - .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) => - `**Top ${index + 1}** - <@${x?.userId}> ${creditNoun(x?.credits)}`; - - // Create embed object - const embed = { - title: ":dollar: Credits [Top]" as string, - description: `Below are the top ten.\n${topTen - ?.map((x, index) => entry(x, index)) - ?.join("\n")}` as string, - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); -}; diff --git a/src/commands/credits/modules/work.ts b/src/commands/credits/modules/work.ts deleted file mode 100644 index 064b870..0000000 --- a/src/commands/credits/modules/work.ts +++ /dev/null @@ -1,116 +0,0 @@ -// Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; -import Chance from "chance"; - -// Configurations -import config from "../../../../config.json"; - -// Handlers -import logger from "../../../handlers/logger"; - -// Models -import timeouts from "../../../helpers/database/models/timeoutSchema"; - -// Helpers -import creditNoun from "../../../helpers/creditNoun"; -import fetchUser from "../../../helpers/fetchUser"; -import fetchGuild from "../../../helpers/fetchGuild"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { guild, user } = interaction; - - // Chance module - const chance = new Chance(); - - // Check if user has a timeout - const isTimeout = await timeouts?.findOne({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-03-15-19-16", - }); - - if (guild === null) return; - - const guildDB = await fetchGuild(guild); - - // If user is not on timeout - if (!isTimeout) { - const creditsEarned = chance.integer({ - min: 0, - max: guildDB?.credits?.workRate, - }); - - const userDB = await fetchUser(user, guild); - - if (userDB === null) return; - - userDB.credits += creditsEarned; - - await userDB?.save()?.then(async () => { - // Send debug message - logger?.debug(`Credits added to user: ${user?.id}`); - - // Create embed object - const embed = { - title: ":dollar: Credits [Work]" as string, - description: `You have earned ${creditNoun(creditsEarned)}` as string, - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); - - // Create a timeout for the user - await timeouts?.create({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-03-15-19-16", - }); - - setTimeout(async () => { - // Send debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} has not worked within the last ${ - guildDB?.credits?.workTimeout / 1000 - } seconds, work can be done` - ); - - // When timeout is out, remove it from the database - await timeouts?.deleteOne({ - guildId: guild?.id, - userId: user?.id, - timeoutId: "2022-03-15-19-16", - }); - }, guildDB?.credits?.workTimeout); - } else { - // Create embed object - const embed = { - title: ":dollar: Credits [Work]" as string, - description: `You have worked within the last ${ - guildDB?.credits?.workTimeout / 1000 - } seconds, you can not work now!` as string, - timestamp: new Date(), - color: config?.colors?.error as ColorResolvable, - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} has worked within last day, no work can be done` - ); - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } -}; diff --git a/src/commands/settings/guild/addons/credits.ts b/src/commands/settings/guild/addons/credits.ts deleted file mode 100644 index e1aab4f..0000000 --- a/src/commands/settings/guild/addons/credits.ts +++ /dev/null @@ -1,97 +0,0 @@ -// Dependencies -import { ColorResolvable, CommandInteraction } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -//Handlers -import logger from "../../../../handlers/logger"; - -// Models -import guildSchema from "../../../../helpers/database/models/guildSchema"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { guild, user, options } = interaction; - - // Get options - const status = options?.getBoolean("status"); - const rate = options?.getNumber("rate"); - const timeout = options?.getNumber("timeout"); - const minimumLength = options?.getNumber("minimum-length"); - const workRate = options?.getNumber("work-rate"); - const workTimeout = options?.getNumber("work-timeout"); - - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - // Modify values - guildDB.credits.status = status !== null ? status : guildDB?.credits?.status; - guildDB.credits.rate = rate !== null ? rate : guildDB?.credits?.rate; - guildDB.credits.timeout = - timeout !== null ? timeout : guildDB?.credits?.timeout; - guildDB.credits.workRate = - workRate !== null ? workRate : guildDB?.credits?.workRate; - guildDB.credits.workTimeout = - workTimeout !== null ? workTimeout : guildDB?.credits?.workTimeout; - guildDB.credits.minimumLength = - minimumLength !== null ? minimumLength : guildDB?.credits?.minimumLength; - - // Save guild - await guildDB?.save()?.then(async () => { - // Embed object - const embed = { - title: ":tools: Settings - Guild [Credits]" as string, - description: "Following settings is set!" as string, - color: config?.colors?.success as ColorResolvable, - fields: [ - { - name: "🤖 Status" as string, - value: `${guildDB?.credits?.status}` as string, - inline: true, - }, - { - name: "📈 Rate" as string, - value: `${guildDB?.credits?.rate}` as string, - inline: true, - }, - { - name: "📈 Work Rate" as string, - value: `${guildDB?.credits?.workRate}` as string, - inline: true, - }, - { - name: "🔨 Minimum Length" as string, - value: `${guildDB?.credits?.minimumLength}` as string, - inline: true, - }, - { - name: "⏰ Timeout" as string, - value: `${guildDB?.credits?.timeout}` as string, - inline: true, - }, - { - name: "⏰ Work Timeout" as string, - value: `${guildDB?.credits?.workTimeout}` as string, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user.id} has changed credit details.` - ); - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); -}; diff --git a/src/commands/settings/guild/addons/points.ts b/src/commands/settings/guild/addons/points.ts deleted file mode 100644 index 97dbb0f..0000000 --- a/src/commands/settings/guild/addons/points.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Dependencies -import { ColorResolvable, CommandInteraction } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -// Handlers -import logger from "../../../../handlers/logger"; - -// Models -import guildSchema from "../../../../helpers/database/models/guildSchema"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { options, guild, user } = interaction; - - // Get options - const status = options?.getBoolean("status"); - const rate = options?.getNumber("rate"); - const timeout = options?.getNumber("timeout"); - const minimumLength = options?.getNumber("minimum-length"); - - // Get guild object - const guildDB = await guildSchema?.findOne({ - guildId: guild?.id, - }); - - // Modify values - guildDB.points.status = status !== null ? status : guildDB?.points?.status; - guildDB.points.rate = rate !== null ? rate : guildDB?.points?.rate; - guildDB.points.timeout = - timeout !== null ? timeout : guildDB?.points?.timeout; - guildDB.points.minimumLength = - minimumLength !== null ? minimumLength : guildDB?.points?.minimumLength; - - // Save guild - await guildDB?.save()?.then(async () => { - // Create embed object - const embed = { - title: ":hammer: Settings - Guild [Points]" as string, - description: "Following settings is set!" as string, - color: config.colors.success as ColorResolvable, - fields: [ - { - name: "🤖 Status" as string, - value: `${guildDB?.points?.status}` as string, - inline: true, - }, - { - name: "📈 Rate" as string, - value: `${guildDB?.points?.rate}` as string, - inline: true, - }, - { - name: "🔨 Minimum Length" as string, - value: `${guildDB?.points?.minimumLength}` as string, - inline: true, - }, - { - name: "⏰ Timeout" as string, - value: `${guildDB?.points?.timeout}` as string, - inline: true, - }, - ], - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} has changed credit details.` - ); - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); -}; diff --git a/src/commands/settings/guild/addons/pterodactyl.ts b/src/commands/settings/guild/addons/pterodactyl.ts deleted file mode 100644 index 0f84340..0000000 --- a/src/commands/settings/guild/addons/pterodactyl.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Dependencies -import { ColorResolvable, CommandInteraction } from "discord.js"; - -// Configurations -import config from "../../../../../config.json"; - -// Handlers -import logger from "../../../../handlers/logger"; - -// Models -import apiSchema from "../../../../helpers/database/models/apiSchema"; -import encryption from "../../../../handlers/encryption"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { options, guild, user } = interaction; - - // Get options - const url = options?.getString("url"); - const token = encryption.encrypt(options?.getString("token")); - - // Update API credentials - await apiSchema - ?.findOneAndUpdate( - { guildId: guild?.id }, - { url, token }, - { new: true, upsert: true } - ) - .then(async () => { - // Embed object - const embed = { - title: ":hammer: Settings - Guild [Pterodactyl]" as string, - color: config?.colors?.success as ColorResolvable, - description: "Pterodactyl settings is saved!" as string, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send debug message - logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} has changed api credentials.` - ); - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - }); -}; diff --git a/src/commands/settings/guild/index.ts b/src/commands/settings/guild/index.ts deleted file mode 100644 index fcfa173..0000000 --- a/src/commands/settings/guild/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Dependencies -import { Permissions, ColorResolvable, CommandInteraction } from "discord.js"; - -// Configurations -import config from "../../../../config.json"; - -// Handlers -import logger from "../../../handlers/logger"; - -// Modules -import pterodactyl from "./addons/pterodactyl"; -import credits from "./addons/credits"; -import points from "./addons/points"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { memberPermissions, options, commandName, user, guild } = interaction; - - // Check permission - if (!memberPermissions?.has(Permissions?.FLAGS?.MANAGE_GUILD)) { - // Create embed object - const embed = { - title: ":tools: Settings - Guild" as string, - color: config?.colors?.error as ColorResolvable, - description: "You do not have permission to manage this!" as string, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Return interaction reply - return interaction?.editReply({ embeds: [embed] }); - } - - // Module - Pterodactyl - if (options?.getSubcommand() === "pterodactyl") { - // Execute Module - Pterodactyl - return pterodactyl(interaction); - } - - // Module - Credits - else if (options?.getSubcommand() === "credits") { - // Execute Module - Credits - return credits(interaction); - } - - // Module - Points - else if (options?.getSubcommand() === "points") { - // Execute Module - Points - return points(interaction); - } - - // Send debug message - return logger?.debug( - `Guild: ${guild?.id} User: ${ - user?.id - } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` - ); -}; diff --git a/src/commands/settings/index.ts b/src/commands/settings/index.ts deleted file mode 100644 index a0cdb0b..0000000 --- a/src/commands/settings/index.ts +++ /dev/null @@ -1,144 +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 "../../handlers/logger"; - -// Function -export default { - data: new SlashCommandBuilder() - .setName("settings") - .setDescription("Manage settings.") - .addSubcommandGroup((group) => - group - .setName("guild") - .setDescription("Manage guild settings.") - .addSubcommand((command) => - command - .setName("pterodactyl") - .setDescription("Controlpanel.gg") - .addStringOption((option) => - option - .setName("url") - .setDescription("The api url") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("token") - .setDescription("The api token") - .setRequired(true) - ) - ) - .addSubcommand((command) => - command - .setName("credits") - .setDescription("Credits") - .addBooleanOption((option) => - option - .setName("status") - .setDescription("Should credits be enabled?") - ) - .addNumberOption((option) => - option - .setName("rate") - .setDescription("Amount of credits per message.") - ) - .addNumberOption((option) => - option - .setName("minimum-length") - .setDescription("Minimum length of message to earn credits.") - ) - .addNumberOption((option) => - option - .setName("work-rate") - .setDescription("Maximum amount of credits on work.") - ) - .addNumberOption((option) => - option - .setName("work-timeout") - .setDescription( - "Timeout between work schedules (milliseconds)." - ) - ) - .addNumberOption((option) => - option - .setName("timeout") - .setDescription( - "Timeout between earning credits (milliseconds)." - ) - ) - ) - .addSubcommand((command) => - command - .setName("points") - .setDescription("Points") - .addBooleanOption((option) => - option - .setName("status") - .setDescription("Should credits be enabled?") - ) - .addNumberOption((option) => - option - .setName("rate") - .setDescription("Amount of credits per message.") - ) - .addNumberOption((option) => - option - .setName("minimum-length") - .setDescription("Minimum length of message to earn credits.") - ) - .addNumberOption((option) => - option - .setName("timeout") - .setDescription( - "Timeout between earning credits (milliseconds)." - ) - ) - ) - ) - .addSubcommandGroup((group) => - group - .setName("user") - .setDescription("Manage user settings.") - .addSubcommand((command) => - command - .setName("appearance") - .setDescription("Manage your appearance") - .addStringOption((option) => - option - .setName("language") - .setDescription("Configure your language") - .addChoice("English", "en") - .addChoice("Swedish", "sv") - ) - ) - ), - async execute(interaction: CommandInteraction) { - // Destructure - const { options, commandName, user, guild } = interaction; - - // Group - Guild - if (options.getSubcommandGroup() === "guild") { - // Execute Group - Guild - await guildGroup(interaction); - } - // Group - User - else if (options.getSubcommandGroup() === "user") { - // Execute Group - User - await userGroup(interaction); - } - - // Send debug message - return logger?.debug( - `Guild: ${guild?.id} User: ${ - user?.id - } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` - ); - }, -}; diff --git a/src/commands/settings/user/index.ts b/src/commands/settings/user/index.ts deleted file mode 100644 index 7797711..0000000 --- a/src/commands/settings/user/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Dependencies -import { CommandInteraction } from "discord.js"; - -// Handlers -import logger from "../../../handlers/logger"; - -// Modules -import appearance from "./modules/appearance"; - -// Function -export default async (interaction: CommandInteraction) => { - // Destructure member - const { guild, user, options, commandName } = interaction; - - // Module - Appearance - if (options?.getSubcommand() === "appearance") { - // Execute Module - Appearance - await appearance(interaction); - } - - // Send debug message - return logger?.debug( - `Guild: ${guild?.id} User: ${ - user?.id - } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` - ); -}; diff --git a/src/commands/utilities/modules/lookup.ts b/src/commands/utilities/modules/lookup.ts deleted file mode 100644 index b997eb4..0000000 --- a/src/commands/utilities/modules/lookup.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Dependencies -import axios from "axios"; -import { CommandInteraction, ColorResolvable } from "discord.js"; - -// Configurations -import config from "../../../../config.json"; - -// Handlers -import logger from "../../../handlers/logger"; - -// Function -export default 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" as string, - description: `${res?.data?.message}: ${res?.data?.query}` as string, - color: config?.colors?.error as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // 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" as string, - fields: [ - { - name: "AS" as string, - value: `${res?.data?.as || "Not available"}` as string, - }, - { - name: "Country" as string, - value: `${res?.data?.country || "Not available"}` as string, - }, - { - name: "Country Code" as string, - value: `${res?.data?.countryCode || "Not available"}` as string, - }, - { - name: "Region" as string, - value: `${res?.data?.region || "Not available"}` as string, - }, - { - name: "Region Name" as string, - value: `${res?.data?.regionName || "Not available"}` as string, - }, - { - name: "City" as string, - value: `${res?.data?.city || "Not available"}` as string, - }, - { - name: "ZIP Code" as string, - value: `${res?.data?.zip || "Not available"}` as string, - }, - { - name: "Latitude" as string, - value: `${res?.data?.lat || "Not available"}` as string, - }, - { - name: "Longitude" as string, - value: `${res?.data?.lon || "Not available"}` as string, - }, - { - name: "Timezone" as string, - value: `${res?.data?.timezone || "Not available"}` as string, - }, - { - name: "ISP" as string, - value: `${res?.data?.isp || "Not available"}` as string, - }, - { - name: "Organization" as string, - value: `${res?.data?.org || "Not available"}` as string, - }, - ], - color: config?.colors?.success as ColorResolvable, - timestamp: new Date(), - footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, - }, - }; - - // Send interaction reply - await interaction?.editReply({ embeds: [embed] }); - } - }) - .catch(async (e) => { - logger?.error(e); - }); -}; diff --git a/src/config/example.api.ts b/src/config/example.api.ts new file mode 100644 index 0000000..7d5621c --- /dev/null +++ b/src/config/example.api.ts @@ -0,0 +1,2 @@ +// Controlpanel.gg (Pterodactyl) API token +export const cpggToken = ""; diff --git a/src/config/example.database.ts b/src/config/example.database.ts new file mode 100644 index 0000000..35e2e27 --- /dev/null +++ b/src/config/example.database.ts @@ -0,0 +1,3 @@ +// MongoDB connection string +export const url = + "mongodb+srv://username:password@server/database?retryWrites=true&w=majority"; diff --git a/src/config/example.discord.ts b/src/config/example.discord.ts new file mode 100644 index 0000000..682ff99 --- /dev/null +++ b/src/config/example.discord.ts @@ -0,0 +1,5 @@ +// Discord API token +export const token = ""; + +// Discord API id +export const clientId = ""; diff --git a/src/config/example.embed.ts b/src/config/example.embed.ts new file mode 100644 index 0000000..d6eebb1 --- /dev/null +++ b/src/config/example.embed.ts @@ -0,0 +1,17 @@ +// Dependencies +import { ColorResolvable } from "discord.js"; + +// Color for successfully actions +export const successColor: ColorResolvable = "#22bb33"; + +// Color for waiting actions +export const waitColor: ColorResolvable = "#f0ad4e"; + +// Color for error actions +export const errorColor: ColorResolvable = "#bb2124"; + +// Footer text +export const footerText = "https://github.com/ZynerOrg/xyter"; + +// Footer icon +export const footerIcon = "https://github.com/ZynerOrg.png"; diff --git a/src/config/example.encryption.ts b/src/config/example.encryption.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/config/example.other.ts b/src/config/example.other.ts new file mode 100644 index 0000000..3a86a51 --- /dev/null +++ b/src/config/example.other.ts @@ -0,0 +1,11 @@ +// Development features +export const devMode = false; + +// Development guild +export const guildId = ""; + +// Hoster name +export const hosterName = "someone"; + +// Hoster Url +export const hosterUrl = "scheme://domain.tld"; diff --git a/src/config/example.reputation.ts b/src/config/example.reputation.ts new file mode 100644 index 0000000..064edc9 --- /dev/null +++ b/src/config/example.reputation.ts @@ -0,0 +1,2 @@ +// Timeout between repute someone (seconds) +export const timeout = 86400; // One day diff --git a/src/database/index.ts b/src/database/index.ts new file mode 100644 index 0000000..8130eb2 --- /dev/null +++ b/src/database/index.ts @@ -0,0 +1,15 @@ +import { url } from "@config/database"; + +import mongoose from "mongoose"; +import logger from "@logger"; + +export default async () => { + await mongoose + .connect(url) + ?.then(async () => { + logger.info("Successfully connected to MongoDB!"); + }) + ?.catch(async () => { + logger.error("Error whilst connecting to MongoDB!"); + }); +}; diff --git a/src/helpers/database/models/apiSchema.ts b/src/database/schemas/api.ts similarity index 100% rename from src/helpers/database/models/apiSchema.ts rename to src/database/schemas/api.ts diff --git a/src/helpers/database/models/counterSchema.ts b/src/database/schemas/counter.ts similarity index 100% rename from src/helpers/database/models/counterSchema.ts rename to src/database/schemas/counter.ts diff --git a/src/helpers/database/models/guildSchema.ts b/src/database/schemas/guild.ts similarity index 50% rename from src/helpers/database/models/guildSchema.ts rename to src/database/schemas/guild.ts index 393e329..ff3328f 100644 --- a/src/helpers/database/models/guildSchema.ts +++ b/src/database/schemas/guild.ts @@ -1,66 +1,85 @@ -import mongoose from "mongoose"; +import { Schema, model } from "mongoose"; -const guildSchema = new mongoose.Schema( +interface IGuild { + guildId: string; + credits: { + status: boolean; + rate: number; + timeout: number; + workRate: number; + minimumLength: number; + workTimeout: number; + }; + shop: { roles: { status: boolean; pricePerHour: number } }; + points: { + status: boolean; + rate: number; + minimumLength: number; + timeout: number; + }; +} + +const guildSchema = new Schema( { guildId: { - type: mongoose.SchemaTypes.Decimal128, + type: String, required: true, unique: true, index: true, }, credits: { status: { - type: mongoose.SchemaTypes.Boolean, + type: Boolean, default: true, }, rate: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 1, }, minimumLength: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 5, }, timeout: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 5000, }, workRate: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 15, }, workTimeout: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 900000, }, }, shop: { roles: { status: { - type: mongoose.SchemaTypes.Boolean, + type: Boolean, default: true, }, pricePerHour: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 5, }, }, }, points: { status: { - type: mongoose.SchemaTypes.Boolean, + type: Boolean, default: false, }, rate: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 1, }, minimumLength: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 5, }, timeout: { - type: mongoose.SchemaTypes.Number, + type: Number, default: 5000, }, }, @@ -68,4 +87,4 @@ const guildSchema = new mongoose.Schema( { timestamps: true } ); -export default mongoose.model("guild", guildSchema); +export default model("guild", guildSchema); diff --git a/src/database/schemas/index.ts b/src/database/schemas/index.ts new file mode 100644 index 0000000..5ba7679 --- /dev/null +++ b/src/database/schemas/index.ts @@ -0,0 +1,3 @@ +import counter from "./counter"; + +export default { counter }; diff --git a/src/helpers/database/models/shopRolesSchema.ts b/src/database/schemas/shopRole.ts similarity index 100% rename from src/helpers/database/models/shopRolesSchema.ts rename to src/database/schemas/shopRole.ts diff --git a/src/helpers/database/models/timeoutSchema.ts b/src/database/schemas/timeout.ts similarity index 100% rename from src/helpers/database/models/timeoutSchema.ts rename to src/database/schemas/timeout.ts diff --git a/src/helpers/database/models/userSchema.ts b/src/database/schemas/user.ts similarity index 100% rename from src/helpers/database/models/userSchema.ts rename to src/database/schemas/user.ts diff --git a/src/events/guildCreate/index.ts b/src/events/guildCreate/index.ts index dfcc16b..f3264c8 100644 --- a/src/events/guildCreate/index.ts +++ b/src/events/guildCreate/index.ts @@ -5,15 +5,12 @@ import { Guild } from "discord.js"; import updatePresence from "../../helpers/updatePresence"; import fetchGuild from "../../helpers/fetchGuild"; -// Function export default { name: "guildCreate", async execute(guild: Guild) { - // Destructure const { client } = guild; await fetchGuild(guild); - await updatePresence(client); }, }; diff --git a/src/events/guildDelete/index.ts b/src/events/guildDelete/index.ts index 5c8d167..cf96c55 100644 --- a/src/events/guildDelete/index.ts +++ b/src/events/guildDelete/index.ts @@ -5,15 +5,12 @@ import { Guild } from "discord.js"; import updatePresence from "../../helpers/updatePresence"; import dropGuild from "../../helpers/dropGuild"; -// Function export default { name: "guildDelete", async execute(guild: Guild) { - // Destructure client const { client } = guild; await dropGuild(guild); - await updatePresence(client); }, }; diff --git a/src/events/guildMemberAdd/index.ts b/src/events/guildMemberAdd/index.ts index 2a8c0e0..1b5a140 100644 --- a/src/events/guildMemberAdd/index.ts +++ b/src/events/guildMemberAdd/index.ts @@ -1,4 +1,7 @@ +// Dependencies import { GuildMember } from "discord.js"; + +// Helpers import updatePresence from "../../helpers/updatePresence"; import fetchUser from "../../helpers/fetchUser"; @@ -8,7 +11,6 @@ export default { const { client, user, guild } = member; await fetchUser(user, guild); - await updatePresence(client); }, }; diff --git a/src/events/guildMemberRemove/index.ts b/src/events/guildMemberRemove/index.ts index 1759ab9..3db75a9 100644 --- a/src/events/guildMemberRemove/index.ts +++ b/src/events/guildMemberRemove/index.ts @@ -1,4 +1,7 @@ +// Dependencies import { GuildMember } from "discord.js"; + +// Helpers import updatePresence from "../../helpers/updatePresence"; import dropUser from "../../helpers/dropUser"; @@ -8,7 +11,6 @@ export default { const { client, user, guild } = member; await dropUser(user, guild); - await updatePresence(client); }, }; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index fdd40b2..33b006b 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -1,57 +1,11 @@ -import config from "../../../config.json"; -import logger from "../../handlers/logger"; -import guilds from "../../helpers/database/models/guildSchema"; +// Dependencies +import { CommandInteraction } from "discord.js"; -import { Interaction, ColorResolvable } from "discord.js"; +import isCommand from "./isCommand"; export default { name: "interactionCreate", - async execute(interaction: Interaction) { - // Destructure member, client - const { client, guild } = interaction; - - // If interaction is command - if (interaction.isCommand()) { - // Get command from collection - const command = client.commands.get(interaction.commandName); - - // If command do not exist - if (!command) return; - - // Create guild if it does not exist already - await guilds.findOne({ guildId: guild?.id }, { new: true, upsert: true }); - - try { - // Defer reply - await interaction.deferReply({ ephemeral: true }); - - // Execute command - await command.execute(interaction); - - // Send debug message - logger.debug(`Executing command: ${interaction.commandName}`); - } catch (e) { - // Send debug message - logger.error(e); - - // Send interaction reply - await interaction.reply({ - embeds: [ - { - author: { - name: client?.user?.username, - icon_url: client?.user?.displayAvatarURL(), - url: "https://bot.zyner.org/", - }, - title: "Error", - description: "There was an error while executing this command!", - color: config.colors.error as ColorResolvable, - timestamp: new Date(), - }, - ], - ephemeral: true, - }); - } - } + async execute(interaction: CommandInteraction) { + await isCommand(interaction); }, }; diff --git a/src/events/interactionCreate/isCommand.ts b/src/events/interactionCreate/isCommand.ts new file mode 100644 index 0000000..bb28847 --- /dev/null +++ b/src/events/interactionCreate/isCommand.ts @@ -0,0 +1,58 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +import logger from "@logger"; + +import * as embed from "@config/embed"; + +import guildSchema from "@schemas/guild"; + +export default async (interaction: CommandInteraction) => { + if (!interaction.isCommand()) return; + + const { client, guild, commandName, user } = interaction; + + const currentCommand = client.commands.get(commandName); + if (!currentCommand) return; + + // If command do not exist + + // Create guild if it does not exist already + await guildSchema.findOne( + { guildId: guild?.id }, + { new: true, upsert: true } + ); + + // Defer reply + await interaction.deferReply({ ephemeral: true }); + + await currentCommand + .execute(interaction) + .then(async () => { + return logger.debug( + `Guild: ${guild?.id} (${guild?.name}) User: ${user?.tag} executed ${commandName}` + ); + }) + .catch(async (error: any) => { + console.log(error); + + logger.error( + `Guild: ${guild?.id} (${guild?.name}) User: ${user?.tag} There was an error executing the command: ${commandName}` + ); + + logger.error(error); + + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("Error") + .setDescription( + `There was an error executing the command: **${currentCommand.data.name}**.` + ) + .setColor(embed.errorColor) + .setTimestamp(new Date()) + .setFooter({ text: embed.footerText, iconURL: embed.footerIcon }), + ], + }); + }); +}; diff --git a/src/events/messageCreate/index.ts b/src/events/messageCreate/index.ts index 691178d..fce8271 100644 --- a/src/events/messageCreate/index.ts +++ b/src/events/messageCreate/index.ts @@ -14,7 +14,6 @@ export default { async execute(message: Message) { const { author, guild } = message; - // If message author is bot if (author?.bot) return; if (guild === null) return; @@ -32,6 +31,6 @@ export default { await points(guildObj, userObj, message); // Execute Module - Counters - await counters(guildObj, userObj, message); + await counters(message); }, }; diff --git a/src/events/messageCreate/modules/counters.ts b/src/events/messageCreate/modules/counters.ts index 9cbe3b3..27883f3 100644 --- a/src/events/messageCreate/modules/counters.ts +++ b/src/events/messageCreate/modules/counters.ts @@ -1,8 +1,8 @@ -import counters from "../../../helpers/database/models/counterSchema"; +import counters from "../../../database/schemas/counter"; import { Message } from "discord.js"; -export default async (guildDB: any, userDB: any, message: Message) => { +export default async (message: Message) => { const { guild, channel, content } = message; // Get counter object diff --git a/src/events/messageCreate/modules/credits.ts b/src/events/messageCreate/modules/credits.ts index a426369..3c33ad6 100644 --- a/src/events/messageCreate/modules/credits.ts +++ b/src/events/messageCreate/modules/credits.ts @@ -1,5 +1,5 @@ -import logger from "../../../handlers/logger"; -import timeouts from "../../../helpers/database/models/timeoutSchema"; +import logger from "../../../logger"; +import timeouts from "../../../database/schemas/timeout"; import { Message } from "discord.js"; export default async (guildDB: any, userDB: any, message: Message) => { const { guild, author, channel, content } = message; diff --git a/src/events/messageCreate/modules/points.ts b/src/events/messageCreate/modules/points.ts index ba0a96d..3530ed8 100644 --- a/src/events/messageCreate/modules/points.ts +++ b/src/events/messageCreate/modules/points.ts @@ -1,5 +1,5 @@ -import logger from "../../../handlers/logger"; -import timeouts from "../../../helpers/database/models/timeoutSchema"; +import logger from "../../../logger"; +import timeouts from "../../../database/schemas/timeout"; import { Message } from "discord.js"; export default async (guildDB: any, userDB: any, message: Message) => { diff --git a/src/events/messageUpdate/index.ts b/src/events/messageUpdate/index.ts index bedd151..2a38a73 100644 --- a/src/events/messageUpdate/index.ts +++ b/src/events/messageUpdate/index.ts @@ -1,27 +1,19 @@ -import counters from "../../helpers/database/models/counterSchema"; +// Dependencies import { Message } from "discord.js"; +import logger from "../../logger"; + +// Modules +import counter from "./modules/counter"; + export default { name: "messageUpdate", async execute(oldMessage: Message, newMessage: Message) { - // If message author is bot - if (newMessage.author.bot) return; + const { author } = newMessage; - // Get counter object - const counter = await counters.findOne({ - guildId: newMessage.guild?.id, - channelId: newMessage.channel.id, - }); + logger.debug({ oldMessage, newMessage }); - // If counter for the message channel - if (counter) { - // If message content is not strictly the same as counter word - if (newMessage.content !== counter.word) { - // Delete the message - await newMessage.delete(); - await newMessage.channel.send( - `${newMessage.author} said **${counter.word}**.` - ); - } - } + if (author?.bot) return; + + await counter(newMessage); }, }; diff --git a/src/events/messageUpdate/modules/counter.ts b/src/events/messageUpdate/modules/counter.ts new file mode 100644 index 0000000..d330b4e --- /dev/null +++ b/src/events/messageUpdate/modules/counter.ts @@ -0,0 +1,28 @@ +// Dependencies +import { Message } from "discord.js"; + +// Models +import counterSchema from "../../../database/schemas/counter"; +import logger from "../../../logger"; + +export default async (message: Message) => { + const { guild, channel, author, content } = message; + + const counter = await counterSchema?.findOne({ + guildId: guild?.id, + channelId: channel?.id, + }); + + if (counter === null) return; + const { word } = counter; + if (content === word) return; + + await message + ?.delete() + ?.then(async () => { + await channel?.send(`${author} said **${word}**.`); + }) + ?.catch(async (error) => { + logger.error(new Error(error)); + }); +}; diff --git a/src/events/ready/index.ts b/src/events/ready/index.ts index 3b67e00..6de258b 100644 --- a/src/events/ready/index.ts +++ b/src/events/ready/index.ts @@ -1,71 +1,24 @@ -import logger from "../../handlers/logger"; -import config from "../../../config.json"; -import deployCommands from "../../helpers/deployCommands"; -import dbGuildFix from "../../helpers/dbGuildFix"; -import dbMemberFix from "../../helpers/dbMemberFix"; - -import userSchema from "../../helpers/database/models/userSchema"; - +// Dependencies import { Client } from "discord.js"; +import logger from "../../logger"; + +// Helpers +import deployCommands from "../../handlers/deployCommands"; import updatePresence from "../../helpers/updatePresence"; +import devMode from "../../helpers/devMode"; + export default { name: "ready", once: true, async execute(client: Client) { - // Send info message - await logger.info(`Ready! Logged in as ${client?.user?.tag}`); - + logger.info(`Successfully logged into discord user: ${client?.user?.tag}!`); await updatePresence(client); - - if (config.importToDB) { - const guilds = client.guilds.cache; - await guilds.map(async (guild) => { - await guild?.members.fetch().then(async (members) => { - await members.forEach(async (member) => { - const { user } = member; - dbMemberFix(user, guild); - }); - }); - await dbGuildFix(guild); - }); - } - - if (client === null) return; - if (client.application === null) return; - - if (!config?.devMode) { - client?.application?.commands - ?.set([], config.bot.guildId) - .then(async () => { - logger.info( - `Removed all guild based commands from ${config.bot.guildId}` - ); - }); - } - - if (config.clearUnused) { - await userSchema.find().then( - async (result) => - await result.map(async (user) => { - if ( - user.credits !== 0 || - user.reputation !== 0 || - user.points !== 0 - ) { - logger.info(`Not removing user: ${user}`); - } else { - logger.warn(`Removing user: ${user}`); - console.log({ userId: user.userId, guildId: user.guildId }); - await userSchema - .deleteOne({ _id: user._id }) - .then(async (result) => { - logger.error(`Removed user: ${user} ${result}`); - }); - } - }) - ); - } - + await devMode(client); await deployCommands(); + + const guilds = client.guilds.cache; + guilds.map(async (guild) => { + logger.debug({ name: guild.name, members: guild.memberCount }); + }); }, }; diff --git a/src/handlers/commands.ts b/src/handlers/commands.ts index b98bf4b..0e657b8 100644 --- a/src/handlers/commands.ts +++ b/src/handlers/commands.ts @@ -1,12 +1,23 @@ import fs from "fs"; // fs import { Collection } from "discord.js"; // discord.js import { Client } from "../types/common/discord"; +import logger from "../logger"; + export default async (client: Client) => { client.commands = new Collection(); - const commandFiles = fs.readdirSync("./src/commands"); - for (const file of commandFiles) { - const command = require(`../commands/${file}`); - client.commands.set(command.default.data.name, command.default); - } + fs.readdir("./src/plugins", async (error: any, plugins: any) => { + if (error) { + return logger?.error(new Error(error)); + } + + await plugins?.map(async (pluginName: any) => { + const plugin = await import(`../plugins/${pluginName}`); + + await client?.commands?.set(plugin?.default?.data?.name, plugin?.default); + logger?.debug( + `Successfully loaded plugin: ${plugin?.default?.data?.name} from ${plugin.default?.metadata?.author}` + ); + }); + }); }; diff --git a/src/handlers/deployCommands.ts b/src/handlers/deployCommands.ts new file mode 100644 index 0000000..2d134e5 --- /dev/null +++ b/src/handlers/deployCommands.ts @@ -0,0 +1,54 @@ +// Dependencies +import { token, clientId } from "@config/discord"; +import { devMode, guildId } from "@config/other"; + +import logger from "../logger"; +import fs from "fs"; +import { REST } from "@discordjs/rest"; +import { Routes } from "discord-api-types/v9"; + +export default async () => { + fs.readdir("./src/plugins", async (error: any, plugins: any) => { + if (error) { + return logger?.error(new Error(error)); + } + + const pluginList = [] as any; + + await plugins?.map(async (pluginName: any) => { + const plugin = await import(`../plugins/${pluginName}`); + + pluginList.push(plugin.default.data.toJSON()); + + logger?.debug( + `Successfully deployed plugin: ${plugin?.default?.data?.name} from ${plugin.default?.metadata?.author}` + ); + }); + + const rest = new REST({ version: "9" }).setToken(token); + + await rest + .put(Routes.applicationCommands(clientId), { + body: pluginList, + }) + .then(async () => + logger.info("Successfully registered application commands.") + ) + .catch(async (err: any) => { + logger.error(err); + }); + + if (devMode) { + await rest + .put(Routes.applicationGuildCommands(clientId, guildId), { + body: pluginList, + }) + .then(async () => + logger.info("Successfully registered guild application commands.") + ) + .catch(async (err: any) => { + logger.error(err); + }); + } + }); +}; diff --git a/src/handlers/logger.ts b/src/handlers/logger.ts deleted file mode 100644 index 799cc58..0000000 --- a/src/handlers/logger.ts +++ /dev/null @@ -1,4 +0,0 @@ -import pino from "pino"; -import * as config from "../../config.json"; - -export default pino({ level: config.debug ? "debug" : "info" }); diff --git a/src/handlers/schedules.ts b/src/handlers/schedules.ts deleted file mode 100644 index 143e056..0000000 --- a/src/handlers/schedules.ts +++ /dev/null @@ -1,65 +0,0 @@ -import schedule from "node-schedule"; -import users from "../helpers/database/models/userSchema"; -import shopRolesSchema from "../helpers/database/models/shopRolesSchema"; -import guilds from "../helpers/database/models/guildSchema"; -import logger from "./logger"; -import { Client } from "discord.js"; - -export default async (client: Client) => { - schedule.scheduleJob("*/5 * * * *", async () => { - shopRolesSchema.find().then(async (shopRoles: any) => { - shopRoles.map(async (shopRole: any) => { - const payed = new Date(shopRole.lastPayed); - - const oneHourAfterPayed = payed.setHours(payed.getHours() + 1); - - if (new Date() > new Date(oneHourAfterPayed)) { - logger.debug( - `Role: ${shopRole.roleId} Expires: ${ - new Date() < new Date(oneHourAfterPayed) - } Last Payed: ${shopRole.lastPayed}` - ); - - // Get guild object - const guild = await guilds.findOne({ - guildId: shopRole.guildId, - }); - - const userDB = await users.findOne({ - userId: shopRole.userId, - guildId: shopRole.guildId, - }); - const { pricePerHour } = guild.shop.roles; - - if (userDB === null) return; - - if (userDB.credits < pricePerHour) { - const rGuild = client?.guilds?.cache?.get(`${shopRole.guildId}`); - const rMember = await rGuild?.members?.fetch(`${shopRole.userId}`); - - shopRolesSchema - .deleteOne({ _id: shopRole._id }) - .then(async () => - logger.debug(`Removed ${shopRole._id} from shopRoles`) - ); - - return await rMember?.roles - .remove(`${shopRole.roleId}`) - .then(async (test) => console.log("4", test)) - .catch(async (test) => console.log("5", test)); // Removes all roles - } - - shopRole.lastPayed = new Date(); - shopRole.save(); - userDB.credits -= pricePerHour; - userDB.save(); - await logger.debug( - `${shopRole.roleId} was payed one hour later. BEFORE: ${payed} AFTER: ${oneHourAfterPayed} UPDATED: ${shopRole.updatedAt} CREATED: ${shopRole.createdAt}` - ); - } - }); - }); - - await logger.debug("Checking schedules! (Every 5 minutes)"); - }); -}; diff --git a/src/helpers/creditNoun.ts b/src/helpers/creditNoun.ts deleted file mode 100644 index 19ef8d9..0000000 --- a/src/helpers/creditNoun.ts +++ /dev/null @@ -1,2 +0,0 @@ -export default (amount: number) => - `${amount <= 1 ? `${amount} credit` : `${amount} credits`}`; diff --git a/src/helpers/database/index.ts b/src/helpers/database/index.ts deleted file mode 100644 index 7439ea8..0000000 --- a/src/helpers/database/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import mongoose from "mongoose"; - -import * as config from "../../../config.json"; -import logger from "../../handlers/logger"; - -export default async () => { - await mongoose.connect(config.mongodb.url); - logger.info("Connected to the database"); -}; diff --git a/src/helpers/dbGuildFix.ts b/src/helpers/dbGuildFix.ts index cc3fcf1..e51b89e 100644 --- a/src/helpers/dbGuildFix.ts +++ b/src/helpers/dbGuildFix.ts @@ -1,9 +1,9 @@ // TODO This file will make sure that all guilds always has at least one entry in all collections with "guildId" -import apis from "./database/models/apiSchema"; -import guilds from "./database/models/guildSchema"; +import apis from "../database/schemas/api"; +import guilds from "../database/schemas/guild"; -import logger from "../handlers/logger"; +import logger from "../logger"; import { Guild } from "discord.js"; export default async (guild: Guild) => { diff --git a/src/helpers/dbMemberFix.ts b/src/helpers/dbMemberFix.ts index 5839dbe..e432226 100644 --- a/src/helpers/dbMemberFix.ts +++ b/src/helpers/dbMemberFix.ts @@ -1,5 +1,5 @@ -import users from "./database/models/userSchema"; -import logger from "../handlers/logger"; +import users from "../database/schemas/user"; +import logger from "../logger"; import { Guild, User } from "discord.js"; export default async (user: User, guild: Guild) => { diff --git a/src/helpers/deployCommands.ts b/src/helpers/deployCommands.ts deleted file mode 100644 index b741a9a..0000000 --- a/src/helpers/deployCommands.ts +++ /dev/null @@ -1,41 +0,0 @@ -import config from "../../config.json"; -import logger from "../handlers/logger"; -import fs from "fs"; -import { REST } from "@discordjs/rest"; -import { Routes } from "discord-api-types/v9"; - -export default async () => { - const commands = []; - const commandFiles = fs.readdirSync("./src/commands"); - - for (const file of commandFiles) { - // eslint-disable-next-line global-require - const command = require(`../commands/${file}`); - commands.push(command.default.data.toJSON()); - } - - const rest = new REST({ version: "9" }).setToken(config.bot.token); - - await rest.put(Routes.applicationCommands(config.bot.clientId), { - body: commands, - }); - - if (config?.devMode) { - await rest - .put( - Routes.applicationGuildCommands( - config.bot.clientId, - config.bot.guildId - ), - { - body: commands, - } - ) - .then(async () => - logger.info("Successfully registered application commands.") - ) - .catch(async (err) => { - await logger.error(err); - }); - } -}; diff --git a/src/helpers/devMode.ts b/src/helpers/devMode.ts new file mode 100644 index 0000000..708481b --- /dev/null +++ b/src/helpers/devMode.ts @@ -0,0 +1,15 @@ +// Dependencies +import { Client } from "discord.js"; + +import logger from "@logger"; + +// Configuration +import { devMode, guildId } from "@config/other"; + +export default async (client: Client) => { + if (!devMode) { + client?.application?.commands?.set([], guildId).then(async () => { + logger.verbose(`Removed all guild based commands from ${guildId}`); + }); + } +}; diff --git a/src/helpers/dropGuild.ts b/src/helpers/dropGuild.ts index 4e5985a..70e099b 100644 --- a/src/helpers/dropGuild.ts +++ b/src/helpers/dropGuild.ts @@ -1,11 +1,11 @@ -import guilds from "../helpers/database/models/guildSchema"; -import users from "../helpers/database/models/userSchema"; -import apis from "../helpers/database/models/apiSchema"; -import counters from "../helpers/database/models/counterSchema"; -import shopRoles from "../helpers/database/models/shopRolesSchema"; -import timeouts from "../helpers/database/models/timeoutSchema"; +import guilds from "../database/schemas/guild"; +import users from "../database/schemas/user"; +import apis from "../database/schemas/api"; +import counters from "../database/schemas/counter"; +import shopRoles from "../database/schemas/shopRole"; +import timeouts from "../database/schemas/timeout"; -import logger from "../handlers/logger"; +import logger from "../logger"; import { Guild } from "discord.js"; diff --git a/src/helpers/dropUser.ts b/src/helpers/dropUser.ts index 6cca352..09b6e95 100644 --- a/src/helpers/dropUser.ts +++ b/src/helpers/dropUser.ts @@ -1,6 +1,6 @@ -import users from "../helpers/database/models/userSchema"; +import users from "../database/schemas/user"; -import logger from "../handlers/logger"; +import logger from "../logger"; import { Guild, User } from "discord.js"; diff --git a/src/helpers/fetchGuild.ts b/src/helpers/fetchGuild.ts index 2677699..9424e93 100644 --- a/src/helpers/fetchGuild.ts +++ b/src/helpers/fetchGuild.ts @@ -2,18 +2,18 @@ import { Guild } from "discord.js"; // Models -import guildSchema from "./database/models/guildSchema"; +import guildSchema from "../database/schemas/guild"; // Handlers -import logger from "../handlers/logger"; +import logger from "../logger"; // Function export default async (guild: Guild) => { const guildObj = await guildSchema?.findOne({ guildId: guild.id }); if (guildObj === null) { - const guildObj = new guildSchema({ guildId: guild.id }); + const newGuildObj = new guildSchema({ guildId: guild.id }); - await guildObj + await newGuildObj .save() .then(async () => { logger.debug( @@ -24,7 +24,7 @@ export default async (guild: Guild) => { logger.error(err); }); - return guildObj; + return newGuildObj; } else { return guildObj; } diff --git a/src/helpers/fetchUser.ts b/src/helpers/fetchUser.ts index f67cd47..b9d21b9 100644 --- a/src/helpers/fetchUser.ts +++ b/src/helpers/fetchUser.ts @@ -2,10 +2,10 @@ import { Guild, User } from "discord.js"; // Models -import userSchema from "./database/models/userSchema"; +import userSchema from "../database/schemas/user"; // Handlers -import logger from "../handlers/logger"; +import logger from "../logger"; // Function export default async (user: User, guild: Guild) => { @@ -14,12 +14,12 @@ export default async (user: User, guild: Guild) => { guildId: guild.id, }); if (userObj === null) { - const userObj = new userSchema({ + const newUserObj = new userSchema({ userId: user.id, guildId: guild.id, }); - await userObj + await newUserObj .save() .then(async () => { logger.debug( @@ -30,7 +30,7 @@ export default async (user: User, guild: Guild) => { logger.error(err); }); - return userObj; + return newUserObj; } else { return userObj; } diff --git a/src/helpers/index.ts b/src/helpers/index.ts new file mode 100644 index 0000000..93be57e --- /dev/null +++ b/src/helpers/index.ts @@ -0,0 +1,3 @@ +import pluralize from "./pluralize"; + +export default { pluralize }; diff --git a/src/helpers/pluralize.ts b/src/helpers/pluralize.ts new file mode 100644 index 0000000..0bed6ba --- /dev/null +++ b/src/helpers/pluralize.ts @@ -0,0 +1,2 @@ +export default (count: number, noun: string, suffix?: string) => + `${count} ${noun}${count !== 1 ? suffix || "s" : ""}`; diff --git a/src/helpers/saveUser.ts b/src/helpers/saveUser.ts index 7488bd6..01c7684 100644 --- a/src/helpers/saveUser.ts +++ b/src/helpers/saveUser.ts @@ -1,5 +1,5 @@ import sleep from "./sleep"; -import logger from "../handlers/logger"; +import logger from "../logger"; import Chance from "chance"; export default async function saveUser(data: any, data2: any) { diff --git a/src/index.ts b/src/index.ts index 3a27750..5b9c81b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,28 +1,30 @@ // Dependencies +import "tsconfig-paths/register"; // Allows using tsconfig.json paths during runtime import { Client, Intents } from "discord.js"; // discord.js -import database from "./helpers/database"; -import events from "./handlers/events"; -import commands from "./handlers/commands"; -import locale from "./handlers/locale"; -import schedules from "./handlers/schedules"; +import locale from "@locale"; +import database from "@database"; +import schedules from "@schedules"; -import config from "../config.json"; // config.json +import events from "@handlers/events"; +import commands from "@handlers/commands"; -(async () => { - // Initialize discord.js client - const client = new Client({ - intents: [ - Intents?.FLAGS?.GUILDS, - Intents?.FLAGS?.GUILD_MESSAGES, - Intents?.FLAGS?.GUILD_MEMBERS, - ], - }); +// Configurations +import { token } from "@config/discord"; - await database(); - await locale(); - await events(client); - await commands(client); - await schedules(client); - await client?.login(config?.bot?.token); -})(); +const client = new Client({ + intents: [ + Intents?.FLAGS?.GUILDS, + Intents?.FLAGS?.GUILD_MESSAGES, + Intents?.FLAGS?.GUILD_MEMBERS, + ], +}); + +locale(); +database(); +schedules(client); + +commands(client); +events(client); + +client?.login(token); diff --git a/src/handlers/locale.ts b/src/locale/index.ts similarity index 100% rename from src/handlers/locale.ts rename to src/locale/index.ts diff --git a/src/logger/index.ts b/src/logger/index.ts new file mode 100644 index 0000000..ae96924 --- /dev/null +++ b/src/logger/index.ts @@ -0,0 +1,26 @@ +import winston from "winston"; +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", + }), + align(), + printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`) + ), + }), + ], +}); diff --git a/src/plugins/counters/index.ts b/src/plugins/counters/index.ts new file mode 100644 index 0000000..7a5a5df --- /dev/null +++ b/src/plugins/counters/index.ts @@ -0,0 +1,20 @@ +// Dependencies +import { CommandInteraction } from "discord.js"; +import { SlashCommandBuilder } from "@discordjs/builders"; + +import modules from "@root/plugins/counters/modules"; + +export default { + metadata: { author: "Zyner" }, + data: new SlashCommandBuilder() + .setName("counters") + .setDescription("Manage counters.") + .addSubcommand(modules.view.data), + async execute(interaction: CommandInteraction) { + const { options } = interaction; + + if (options?.getSubcommand() === "view") { + return modules.view.execute(interaction); + } + }, +}; diff --git a/src/plugins/counters/modules/index.ts b/src/plugins/counters/modules/index.ts new file mode 100644 index 0000000..dc539f8 --- /dev/null +++ b/src/plugins/counters/modules/index.ts @@ -0,0 +1,3 @@ +import view from "./view"; + +export default { view }; diff --git a/src/plugins/counters/modules/view/index.ts b/src/plugins/counters/modules/view/index.ts new file mode 100644 index 0000000..3e3cf8f --- /dev/null +++ b/src/plugins/counters/modules/view/index.ts @@ -0,0 +1,71 @@ +// Dependencies +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) => { + return command + .setName("view") + .setDescription("View a counter's count.") + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The counter channel you want to view.") + .setRequired(true) + .addChannelType(ChannelType.GuildText as number) + ); + }, + execute: async (interaction: CommandInteraction) => { + const { options, guild } = interaction; + + const discordChannel = options?.getChannel("channel"); + + const counter = await counterSchema?.findOne({ + guildId: guild?.id, + channelId: discordChannel?.id, + }); + + if (counter === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:1234:] Counters (View)") + .setDescription(`${discordChannel} is not a counting channel!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }), + ], + }); + } + + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:1234:] Counters (View)") + .setDescription( + `${discordChannel} is currently at number ${counter?.counter}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ + text: footerText, + iconURL: footerIcon, + }), + ], + }); + }, +}; diff --git a/src/plugins/credits/index.ts b/src/plugins/credits/index.ts new file mode 100644 index 0000000..05e0c9a --- /dev/null +++ b/src/plugins/credits/index.ts @@ -0,0 +1,36 @@ +// Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +// Modules +import modules from "@root/plugins/credits/modules"; + +export default { + metadata: { author: "Zyner" }, + data: new SlashCommandBuilder() + .setName("credits") + .setDescription("Manage your credits.") + .addSubcommand(modules.balance.data) + .addSubcommand(modules.gift.data) + .addSubcommand(modules.top.data) + .addSubcommand(modules.work.data), + async execute(interaction: CommandInteraction) { + const { options } = interaction; + + if (options?.getSubcommand() === "balance") { + return modules.balance.execute(interaction); + } + + if (options?.getSubcommand() === "gift") { + return modules.gift.execute(interaction); + } + + if (options?.getSubcommand() === "top") { + return modules.top.execute(interaction); + } + + if (options?.getSubcommand() === "work") { + return modules.work.execute(interaction); + } + }, +}; diff --git a/src/plugins/credits/modules/balance/index.ts b/src/plugins/credits/modules/balance/index.ts new file mode 100644 index 0000000..ee45681 --- /dev/null +++ b/src/plugins/credits/modules/balance/index.ts @@ -0,0 +1,100 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// 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("Check a user's balance.") + + // User + .addUserOption((option) => + option + .setName("user") + .setDescription("The user whose balance you want to check.") + ) + ); + }, + execute: async (interaction: CommandInteraction) => { + const { options, user, guild } = interaction; + + const discordUser = options?.getUser("user"); + + if (guild === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Balance)") + .setDescription(`We can not find your guild!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + const userObj = await fetchUser(discordUser || user, guild); + + if (userObj === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Balance)") + .setDescription( + `We can not find ${discordUser || "you"} in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + if (userObj.credits === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Balance)") + .setDescription( + `We can not find credits for ${ + discordUser || "you" + } in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Balance)") + .setDescription( + `${discordUser || "You"} have ${pluralize( + userObj.credits, + "credit" + )}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }, +}; diff --git a/src/plugins/credits/modules/gift/index.ts b/src/plugins/credits/modules/gift/index.ts new file mode 100644 index 0000000..a083313 --- /dev/null +++ b/src/plugins/credits/modules/gift/index.ts @@ -0,0 +1,249 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +// Configurations +import { + errorColor, + successColor, + footerText, + footerIcon, +} from "@config/embed"; + +// Handlers +import logger from "../../../../logger"; + +// Helpers +import saveUser from "../../../../helpers/saveUser"; +import pluralize from "../../../../helpers/pluralize"; + +// Models +import fetchUser from "../../../../helpers/fetchUser"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("gift") + .setDescription("Gift someone credits from your credits.") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user you want to pay.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("The amount you will pay.") + .setRequired(true) + ) + .addStringOption((option) => + option.setName("reason").setDescription("Your reason.") + ); + }, + execute: async (interaction: CommandInteraction) => { + const { options, user, guild, client } = interaction; + + const optionUser = options?.getUser("user"); + const optionAmount = options?.getInteger("amount"); + const optionReason = options?.getString("reason"); + + if (guild === 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 }), + ], + }); + } + + if (optionUser === null) { + 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 }), + ], + }); + } + + // Get fromUserDB object + const fromUserDB = await fetchUser(user, guild); + + // Get toUserDB object + const toUserDB = await fetchUser(optionUser, guild); + + if (fromUserDB === null) { + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Gift)") + .setDescription( + `We can not find your requested from user in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + if (toUserDB === null) { + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Gift)") + .setDescription( + `We can not find your requested to user in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If receiver is same as sender + if (optionUser?.id === user?.id) { + 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 }), + ], + }); + } + + // If amount is null + if (optionAmount === 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 }), + ], + }); + } + + // If amount is zero or below + if (optionAmount <= 0) { + 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 }), + ], + }); + } + + // If user has below gifting amount + if (fromUserDB?.credits < optionAmount) { + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Gift)") + .setDescription( + `You have insufficient credits. Your balance is ${fromUserDB?.credits}!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If toUserDB has no credits + if (toUserDB === null) { + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Gift)") + .setDescription( + `We can not find your requested to user in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // Withdraw amount from fromUserDB + fromUserDB.credits -= optionAmount; + + // Deposit amount to toUserDB + toUserDB.credits += optionAmount; + + // Save users + await saveUser(fromUserDB, toUserDB)?.then(async () => { + // Get DM user object + const dmUser = client?.users?.cache?.get(optionUser?.id); + + // Send DM to user + await dmUser + ?.send({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Gift)") + .setDescription( + `You received ${pluralize( + optionAmount, + "credit" + )} from ${user}${ + optionReason ? ` with reason: ${optionReason}` : "" + }. Your new credits is ${pluralize( + toUserDB?.credits, + "credit" + )}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }) + .catch(async () => + logger.debug(`Can not send DM to user ${optionUser?.id}`) + ); + + // Send debug message + logger.debug( + `Guild: ${guild?.id} User: ${user?.id} gift sent from: ${user?.id} to: ${optionUser?.id}` + ); + + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Gift)") + .setDescription( + `You sent ${pluralize(optionAmount, "credit")} to ${optionUser}${ + optionReason ? ` with reason: ${optionReason}` : "" + }. Your new credits is ${pluralize( + fromUserDB?.credits, + "credit" + )}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + }, +}; diff --git a/src/plugins/credits/modules/index.ts b/src/plugins/credits/modules/index.ts new file mode 100644 index 0000000..20edcaf --- /dev/null +++ b/src/plugins/credits/modules/index.ts @@ -0,0 +1,6 @@ +import balance from "./balance"; +import gift from "./gift"; +import top from "./top"; +import work from "./work"; + +export default { balance, gift, top, work }; diff --git a/src/plugins/credits/modules/top/index.ts b/src/plugins/credits/modules/top/index.ts new file mode 100644 index 0000000..2cd1ced --- /dev/null +++ b/src/plugins/credits/modules/top/index.ts @@ -0,0 +1,52 @@ +// Dependencies +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { CommandInteraction, MessageEmbed } from "discord.js"; + +import userSchema from "@schemas/user"; + +// Configurations +import { successColor, footerText, footerIcon } from "@config/embed"; + +// Helpers +import pluralize from "@helpers/pluralize"; + +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command.setName("top").setDescription("Check the top balance."); + }, + execute: async (interaction: CommandInteraction) => { + // Get all users in the guild + + const usersDB = await userSchema.find({ guildId: interaction?.guild?.id }); + + const topTen = usersDB + + // Sort them after credits amount (ascending) + .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) => + `**Top ${index + 1}** - <@${x?.userId}> ${pluralize( + x?.credits, + "credit" + )}`; + + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Top)") + .setDescription( + `Below are the top ten. + + ${topTen?.map((x, index) => entry(x, index))?.join("\n")}` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }, +}; diff --git a/src/plugins/credits/modules/work/index.ts b/src/plugins/credits/modules/work/index.ts new file mode 100644 index 0000000..7b9a7eb --- /dev/null +++ b/src/plugins/credits/modules/work/index.ts @@ -0,0 +1,117 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import Chance from "chance"; + +// Configurations +import { successColor, footerText, footerIcon } from "@config/embed"; + +// Handlers +import logger from "@logger"; + +// Models +import timeoutSchema from "@schemas/timeout"; + +// Helpers +import pluralize from "@helpers/pluralize"; +import fetchUser from "@helpers/fetchUser"; +import fetchGuild from "@helpers/fetchGuild"; + +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command.setName("work").setDescription("Work for credits."); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure member + const { guild, user } = interaction; + + // Chance module + const chance = new Chance(); + + // Check if user has a timeout + const isTimeout = await timeoutSchema?.findOne({ + guildId: guild?.id, + userId: user?.id, + timeoutId: "2022-03-15-19-16", + }); + + if (guild === null) return; + + const guildDB = await fetchGuild(guild); + + // If user is not on timeout + if (!isTimeout) { + const creditsEarned = chance.integer({ + min: 0, + max: guildDB?.credits?.workRate, + }); + + const userDB = await fetchUser(user, guild); + + if (userDB === null) return; + + userDB.credits += creditsEarned; + + await userDB?.save()?.then(async () => { + logger?.debug(`Credits added to user: ${user?.id}`); + + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Work)") + .setDescription( + `You have earned ${pluralize(creditsEarned, "credit")}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + + // Create a timeout for the user + await timeoutSchema?.create({ + guildId: guild?.id, + userId: user?.id, + timeoutId: "2022-03-15-19-16", + }); + + setTimeout(async () => { + logger?.debug( + `Guild: ${guild?.id} User: ${ + user?.id + } has not worked within the last ${ + guildDB?.credits?.workTimeout / 1000 + } seconds, work can be done` + ); + + // When timeout is out, remove it from the database + await timeoutSchema?.deleteOne({ + guildId: guild?.id, + userId: user?.id, + timeoutId: "2022-03-15-19-16", + }); + }, guildDB?.credits?.workTimeout); + } else { + // Send debug message + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} has worked within last day, no work can be done` + ); + + return interaction.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:dollar:] Credits (Work)") + .setDescription( + `You have worked within the last ${ + guildDB?.credits?.workTimeout / 1000 + } seconds, you can not work now!` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + }, +}; diff --git a/src/plugins/manage/groups/counters/index.ts b/src/plugins/manage/groups/counters/index.ts new file mode 100644 index 0000000..f60e671 --- /dev/null +++ b/src/plugins/manage/groups/counters/index.ts @@ -0,0 +1,29 @@ +// Dependencies +import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; +import { CommandInteraction } from "discord.js"; + +// Modules +import moduleCreate from "./modules/create"; +import moduleDelete from "./modules/delete"; + +// Function +export default { + data: (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("counters") + .setDescription("Manage your guild's counters.") + .addSubcommand(moduleCreate.data) + .addSubcommand(moduleDelete.data); + }, + execute: async (interaction: CommandInteraction) => { + const { options } = interaction; + + if (options?.getSubcommand() === "create") { + return moduleCreate.execute(interaction); + } + + if (options?.getSubcommand() === "delete") { + return moduleDelete.execute(interaction); + } + }, +}; diff --git a/src/plugins/manage/groups/counters/modules/create/index.ts b/src/plugins/manage/groups/counters/modules/create/index.ts new file mode 100644 index 0000000..43c55a3 --- /dev/null +++ b/src/plugins/manage/groups/counters/modules/create/index.ts @@ -0,0 +1,100 @@ +// Dependencies +import { MessageEmbed, CommandInteraction } from "discord.js"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { ChannelType } from "discord-api-types/v10"; + +// Configurations +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; + +// Handlers +import logger from "@logger"; + +// Models +import counterSchema from "../../../../../../database/schemas/counter"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("create") + .setDescription("Add a counter to your guild.") + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The channel you want to add a counter to.") + .setRequired(true) + .addChannelType(ChannelType.GuildText as number) + ) + .addStringOption((option) => + option + .setName("word") + .setDescription("The word you want to count.") + .setRequired(true) + ) + .addNumberOption((option) => + option + .setName("start") + .setDescription("The count that the counter will start at.") + ); + }, + execute: async (interaction: CommandInteraction) => { + const { options, guild, user } = interaction; + + const discordChannel = options?.getChannel("channel"); + const countingWord = options?.getString("word"); + const startValue = options?.getNumber("start"); + + const counter = await counterSchema?.findOne({ + guildId: guild?.id, + channelId: discordChannel?.id, + }); + + if (counter) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Counters (Create)") + .setDescription( + `${discordChannel} is already a counting channel, currently it's counting ${counter.word}!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + await counterSchema + ?.create({ + guildId: guild?.id, + channelId: discordChannel?.id, + word: countingWord, + counter: startValue || 0, + }) + .then(async () => { + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} added ${discordChannel?.id} as a counter using word "${countingWord}" for counting.` + ); + + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Counters (Create)") + .setDescription( + `${discordChannel} is now counting when hearing word ${countingWord} and it starts at number ${ + startValue || 0 + }.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + }, +}; diff --git a/src/plugins/manage/groups/counters/modules/delete/index.ts b/src/plugins/manage/groups/counters/modules/delete/index.ts new file mode 100644 index 0000000..d9d76bf --- /dev/null +++ b/src/plugins/manage/groups/counters/modules/delete/index.ts @@ -0,0 +1,81 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +// Configurations +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; + +// Handlers +import logger from "@logger"; + +// Models +import counterSchema from "../../../../../../database/schemas/counter"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { ChannelType } from "discord-api-types/v10"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("delete") + .setDescription("Delete a counter from your guild.") + .addChannelOption((option) => + option + .setName("channel") + .setDescription("The channel that you want to delete a counter from.") + .setRequired(true) + .addChannelType(ChannelType.GuildText as number) + ); + }, + execute: async (interaction: CommandInteraction) => { + const { options, guild, user } = interaction; + + const discordChannel = options?.getChannel("channel"); + + const counter = await counterSchema?.findOne({ + guildId: guild?.id, + channelId: discordChannel?.id, + }); + + if (counter === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Counters (Delete)") + .setDescription(`${discordChannel} is not a counting channel!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + await counterSchema + ?.deleteOne({ + guildId: guild?.id, + channelId: discordChannel?.id, + }) + ?.then(async () => { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Counters (Delete)") + .setDescription( + `${discordChannel} is no longer an counting channel.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} removed ${discordChannel?.id} as a counter.` + ); + }, +}; diff --git a/src/plugins/manage/groups/credits/index.ts b/src/plugins/manage/groups/credits/index.ts new file mode 100644 index 0000000..3072c56 --- /dev/null +++ b/src/plugins/manage/groups/credits/index.ts @@ -0,0 +1,41 @@ +// Dependencies +import { CommandInteraction } from "discord.js"; +import { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; + +// 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 guild member's credits.") + .addSubcommand(moduleGive.data) + .addSubcommand(moduleSet.data) + .addSubcommand(moduleTake.data) + .addSubcommand(moduleTransfer.data); + }, + execute: async (interaction: CommandInteraction) => { + const { options } = interaction; + + if (options?.getSubcommand() === "give") { + return moduleGive.execute(interaction); + } + + if (options?.getSubcommand() === "set") { + return moduleSet.execute(interaction); + } + + if (options?.getSubcommand() === "take") { + return moduleTake.execute(interaction); + } + + if (options?.getSubcommand() === "transfer") { + return moduleTransfer.execute(interaction); + } + }, +}; diff --git a/src/plugins/manage/groups/credits/modules/give/index.ts b/src/plugins/manage/groups/credits/modules/give/index.ts new file mode 100644 index 0000000..02dca8c --- /dev/null +++ b/src/plugins/manage/groups/credits/modules/give/index.ts @@ -0,0 +1,161 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Configurations +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; + +// Handlers +import logger from "@logger"; + +// Helpers +import pluralize from "@helpers/pluralize"; + +// Models +import fetchUser from "@helpers/fetchUser"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("give") + .setDescription("Give credits to a user") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user you want to pay.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("The amount you will pay.") + .setRequired(true) + ); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure + const { guild, user, options } = interaction; + + const discordReceiver = options?.getUser("user"); + const creditAmount = options?.getInteger("amount"); + + // If amount option is null + if (creditAmount === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Give)") + .setDescription(`We could not read your requested amount!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If amount is zero or below + if (creditAmount <= 0) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Give)") + .setDescription(`You can not give zero credits or below!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + if (discordReceiver === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Give)") + .setDescription(`We could not read receiver user!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + if (guild === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Give)") + .setDescription(`We could not read your guild!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + const toUser = await fetchUser(discordReceiver, guild); + + if (toUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Give)") + .setDescription( + `We could not read your receiver user from our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + if (toUser?.credits === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Give)") + .setDescription( + `We could not find credits for ${discordReceiver} in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // Deposit amount to toUser + toUser.credits += creditAmount; + + // Save toUser + await toUser?.save()?.then(async () => { + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} gave ${ + discordReceiver?.id + } ${pluralize(creditAmount, "credit")}.` + ); + + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Give)") + .setDescription( + `We have given ${discordReceiver}, ${pluralize( + creditAmount, + "credit" + )}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + }, +}; diff --git a/src/plugins/manage/groups/credits/modules/set/index.ts b/src/plugins/manage/groups/credits/modules/set/index.ts new file mode 100644 index 0000000..0b3daa6 --- /dev/null +++ b/src/plugins/manage/groups/credits/modules/set/index.ts @@ -0,0 +1,149 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +// Configurations +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; + +// Handlers +import logger from "@logger"; + +// Helpers +import pluralize from "@helpers/pluralize"; + +// Models +import fetchUser from "@helpers/fetchUser"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("set") + .setDescription("Set credits to a user") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user you want to set credits on.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("The amount you will set.") + .setRequired(true) + ); + }, + execute: async (interaction: CommandInteraction) => { + const { options, user, guild } = interaction; + + const discordUser = options.getUser("user"); + const creditAmount = options.getInteger("amount"); + + // If amount is null + if (creditAmount === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Set)") + .setDescription(`We could not read your requested amount!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + if (discordUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Set)") + .setDescription(`We could not read your requested user!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + if (guild === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Set)") + .setDescription(`We could not read your guild!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // toUser Information + const toUser = await fetchUser(discordUser, guild); + + // If toUser does not exist + if (toUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Set)") + .setDescription( + `We could not read your requested user from our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If toUser.credits does not exist + if (toUser?.credits === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Set)") + .setDescription( + `We could not find credits for ${discordUser} in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // Set toUser with amount + toUser.credits = creditAmount; + + // Save toUser + await toUser?.save()?.then(async () => { + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} set ${ + discordUser?.id + } to ${pluralize(creditAmount, "credit")}.` + ); + + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Set)") + .setDescription( + `We have set ${discordUser} to ${pluralize( + creditAmount, + "credit" + )}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + }, +}; diff --git a/src/plugins/manage/groups/credits/modules/take/index.ts b/src/plugins/manage/groups/credits/modules/take/index.ts new file mode 100644 index 0000000..34688f8 --- /dev/null +++ b/src/plugins/manage/groups/credits/modules/take/index.ts @@ -0,0 +1,167 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +// Configurations +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; + +// Handlers +import logger from "@logger"; + +// Helpers +import pluralize from "@helpers/pluralize"; + +// Models +import fetchUser from "@helpers/fetchUser"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("take") + .setDescription("Take credits from a user") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user you want to take credits from.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("The amount you will take.") + .setRequired(true) + ); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure + const { guild, user, options } = interaction; + + // User option + const optionUser = options?.getUser("user"); + + // Amount option + const optionAmount = options?.getInteger("amount"); + + // If amount is null + if (optionAmount === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Take)") + .setDescription(`We could not read your requested amount!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If amount is zero or below + if (optionAmount <= 0) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Take)") + .setDescription(`We could not take zero credits or below!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + if (optionUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Take)") + .setDescription(`We could not read your requested user!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + if (guild === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Take)") + .setDescription(`We could not read your guild!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // toUser Information + const toUser = await fetchUser(optionUser, guild); + + // If toUser does not exist + if (toUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Take)") + .setDescription( + `We could not read your requested user from our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If toUser.credits does not exist + if (toUser?.credits === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Take)") + .setDescription( + `We could not find credits for ${optionUser} in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // Withdraw amount from toUser + toUser.credits -= optionAmount; + + // Save toUser + await toUser?.save()?.then(async () => { + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} set ${ + optionUser?.id + } to ${pluralize(optionAmount, "credit")}.` + ); + + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Take)") + .setDescription( + `We have taken ${pluralize( + optionAmount, + "credit" + )} from ${optionUser}.` + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + }, +}; diff --git a/src/plugins/manage/groups/credits/modules/transfer/index.ts b/src/plugins/manage/groups/credits/modules/transfer/index.ts new file mode 100644 index 0000000..f5a0d02 --- /dev/null +++ b/src/plugins/manage/groups/credits/modules/transfer/index.ts @@ -0,0 +1,222 @@ +// Dependencies +import { CommandInteraction, MessageEmbed } from "discord.js"; + +// Configurations +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; + +// Handlers +import logger from "@logger"; + +// Helpers +import pluralize from "@helpers/pluralize"; +import saveUser from "@helpers/saveUser"; + +// Models +import fetchUser from "@helpers/fetchUser"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("transfer") + .setDescription("Transfer credits from a user to another user.") + .addUserOption((option) => + option + .setName("from") + .setDescription("The user you want to take credits from.") + .setRequired(true) + ) + .addUserOption((option) => + option + .setName("to") + .setDescription("The user you want to give credits to.") + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("The amount you will transfer.") + .setRequired(true) + ); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure member + const { guild, options, user } = interaction; + + // Get options + const optionFromUser = options?.getUser("from"); + const optionToUser = options?.getUser("to"); + const optionAmount = options?.getInteger("amount"); + + // If amount is null + if (optionAmount === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription(`We could not read your requested amount!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + if (guild === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription(`We could not read your guild!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + if (optionFromUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription(`We could not read your requested from user!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + if (optionToUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription(`We could not read your requested to user!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // Get fromUser object + const fromUser = await fetchUser(optionFromUser, guild); + + // Get toUser object + const toUser = await fetchUser(optionToUser, guild); + + // If toUser does not exist + if (fromUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription( + `We could not read your requested from user from our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If toUser.credits does not exist + if (!fromUser?.credits) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription( + `We could not find credits for ${optionFromUser} in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If toUser does not exist + if (toUser === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription( + `We could not read your requested to user from our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // If toUser.credits does not exist + if (toUser?.credits === null) { + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription( + `We could not find credits for ${optionToUser} in our database!` + ) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + // Withdraw amount from fromUser + fromUser.credits -= optionAmount; + + // Deposit amount to toUser + toUser.credits += optionAmount; + + // Save users + await saveUser(fromUser, toUser)?.then(async () => { + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} transferred ${pluralize( + optionAmount, + "credit" + )} from ${optionFromUser?.id} to ${optionToUser?.id}.` + ); + + return interaction?.editReply({ + embeds: [ + new MessageEmbed() + .setTitle("[:toolbox:] Manage - Credits (Transfer)") + .setDescription( + `We have sent ${pluralize( + optionAmount, + "credit" + )} from ${optionFromUser} to ${optionToUser}.` + ) + .addFields( + { + name: `${optionFromUser?.username} Balance`, + value: `${fromUser?.credits}`, + inline: true, + }, + { + name: `${optionToUser?.username} Balance`, + value: `${toUser?.credits}`, + inline: true, + } + ) + .setTimestamp(new Date()) + .setColor(successColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + }); + }, +}; diff --git a/src/plugins/manage/index.ts b/src/plugins/manage/index.ts new file mode 100644 index 0000000..37b195e --- /dev/null +++ b/src/plugins/manage/index.ts @@ -0,0 +1,47 @@ +//Dependencies +import { SlashCommandBuilder } from "@discordjs/builders"; +import { CommandInteraction, Permissions, MessageEmbed } from "discord.js"; + +// Configurations +import { errorColor, footerText, footerIcon } from "@config/embed"; + +// Groups +import credits from "./groups/credits"; +import counters from "./groups/counters"; + +// Function +export default { + metadata: { author: "Zyner" }, + data: new SlashCommandBuilder() + .setName("manage") + .setDescription("Manage your guild.") + .addSubcommandGroup(counters.data) + .addSubcommandGroup(credits.data), + + 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 permission to manage this!`) + .setTimestamp(new Date()) + .setColor(errorColor) + .setFooter({ text: footerText, iconURL: footerIcon }), + ], + }); + } + + if (options?.getSubcommandGroup() === "credits") { + return credits.execute(interaction); + } + + if (options?.getSubcommandGroup() === "counters") { + return counters.execute(interaction); + } + }, +}; diff --git a/src/commands/profile/index.ts b/src/plugins/profile/index.ts similarity index 94% rename from src/commands/profile/index.ts rename to src/plugins/profile/index.ts index 9747c66..647a4f4 100644 --- a/src/commands/profile/index.ts +++ b/src/plugins/profile/index.ts @@ -6,10 +6,11 @@ import { CommandInteraction } from "discord.js"; import view from "./modules/view"; // Handlers -import logger from "../../handlers/logger"; +import logger from "../../logger"; // Function export default { + metadata: { author: "Zyner" }, data: new SlashCommandBuilder() .setName("profile") .setDescription("Check a profile.") diff --git a/src/commands/profile/modules/view.ts b/src/plugins/profile/modules/view.ts similarity index 50% rename from src/commands/profile/modules/view.ts rename to src/plugins/profile/modules/view.ts index 16a3151..d5d05bf 100644 --- a/src/commands/profile/modules/view.ts +++ b/src/plugins/profile/modules/view.ts @@ -1,11 +1,11 @@ // Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; +import { CommandInteraction } from "discord.js"; // Configurations -import config from "../../../../config.json"; +import { successColor, footerText, footerIcon } from "@config/embed"; // Models -import fetchUser from "../../../helpers/fetchUser"; +import fetchUser from "@helpers/fetchUser"; // Function export default async (interaction: CommandInteraction) => { @@ -28,41 +28,41 @@ export default async (interaction: CommandInteraction) => { // Embed object const embed = { author: { - name: `${discordUser?.username}#${discordUser?.discriminator}` as string, - icon_url: discordUser?.displayAvatarURL() as string, + name: `${discordUser?.username}#${discordUser?.discriminator}`, + icon_url: discordUser?.displayAvatarURL(), }, - color: config?.colors?.success as ColorResolvable, + color: successColor, fields: [ { - name: `:dollar: Credits` as string, - value: `${userObj?.credits || "Not found"}` as string, + name: `:dollar: Credits`, + value: `${userObj?.credits || "Not found"}`, inline: true, }, { - name: `:squeeze_bottle: Level` as string, - value: `${userObj?.level || "Not found"}` as string, + name: `:squeeze_bottle: Level`, + value: `${userObj?.level || "Not found"}`, inline: true, }, { - name: `:squeeze_bottle: Points` as string, - value: `${userObj?.points || "Not found"}` as string, + name: `:squeeze_bottle: Points`, + value: `${userObj?.points || "Not found"}`, inline: true, }, { - name: `:loudspeaker: Reputation` as string, - value: `${userObj?.reputation || "Not found"}` as string, + name: `:loudspeaker: Reputation`, + value: `${userObj?.reputation || "Not found"}`, inline: true, }, { - name: `:rainbow_flag: Language` as string, - value: `${userObj?.language || "Not found"}` as string, + name: `:rainbow_flag: Language`, + value: `${userObj?.language || "Not found"}`, inline: true, }, ], timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; diff --git a/src/commands/reputation/index.ts b/src/plugins/reputation/index.ts similarity index 95% rename from src/commands/reputation/index.ts rename to src/plugins/reputation/index.ts index 93ff54c..61517c6 100644 --- a/src/commands/reputation/index.ts +++ b/src/plugins/reputation/index.ts @@ -6,10 +6,11 @@ import { CommandInteraction } from "discord.js"; import give from "./modules/give"; // Handlers -import logger from "../../handlers/logger"; +import logger from "../../logger"; // Function export default { + metadata: { author: "Zyner" }, data: new SlashCommandBuilder() .setName("reputation") .setDescription("Give reputation.") diff --git a/src/commands/reputation/modules/give.ts b/src/plugins/reputation/modules/give.ts similarity index 64% rename from src/commands/reputation/modules/give.ts rename to src/plugins/reputation/modules/give.ts index 21b816a..56b3d1f 100644 --- a/src/commands/reputation/modules/give.ts +++ b/src/plugins/reputation/modules/give.ts @@ -1,15 +1,22 @@ // Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; +import { CommandInteraction } from "discord.js"; // Configurations -import config from "../../../../config.json"; +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; + +import { timeout } from "@config/reputation"; // Handlers -import logger from "../../../handlers/logger"; +import logger from "@logger"; // Models -import timeoutSchema from "../../../helpers/database/models/timeoutSchema"; -import fetchUser from "../../../helpers/fetchUser"; +import timeoutSchema from "@schemas/timeout"; +import fetchUser from "@helpers/fetchUser"; // Function export default async (interaction: CommandInteraction) => { @@ -42,13 +49,13 @@ export default async (interaction: CommandInteraction) => { if (optionTarget?.id === user?.id) { // Embed object const embed = { - title: ":loudspeaker: Reputation [Give]" as string, - description: "You can not repute yourself." as string, + title: ":loudspeaker: Reputation [Give]", + description: "You can not repute yourself.", timestamp: new Date(), - color: config?.colors?.error as ColorResolvable, + color: errorColor, footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; @@ -70,14 +77,13 @@ export default async (interaction: CommandInteraction) => { await userObj?.save()?.then(async () => { // Embed object const embed = { - title: ":loudspeaker: Reputation [Give]" as string, - description: - `You have given ${optionTarget} a ${optionType} reputation!` as string, + title: ":loudspeaker: Reputation [Give]", + description: `You have given ${optionTarget} a ${optionType} reputation!`, timestamp: new Date(), - color: config?.colors?.success as ColorResolvable, + color: successColor, footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; @@ -99,9 +105,7 @@ export default async (interaction: CommandInteraction) => { setTimeout(async () => { // send debug message logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} has not repute within last ${ - config?.reputation?.timeout / 1000 - } seconds, reputation can be given` + `Guild: ${guild?.id} User: ${user?.id} has not repute within last ${timeout} seconds, reputation can be given` ); // When timeout is out, remove it from the database @@ -110,27 +114,23 @@ export default async (interaction: CommandInteraction) => { userId: user?.id, timeoutId: "2022-04-10-16-42", }); - }, config?.reputation?.timeout); + }, timeout); } else { // Create embed object const embed = { - title: ":loudspeaker: Reputation [Give]" as string, - description: `You have given reputation within the last ${ - config?.reputation?.timeout / 1000 - } seconds, you can not repute now!` as string, + title: ":loudspeaker: Reputation [Give]", + description: `You have given reputation within the last ${timeout} seconds, you can not repute now!`, timestamp: new Date(), - color: config.colors.error as ColorResolvable, + color: errorColor, footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; // Log debug message logger?.debug( - `Guild: ${guild?.id} User: ${user?.id} has repute within last ${ - config?.reputation?.timeout / 1000 - } seconds, no reputation can be given` + `Guild: ${guild?.id} User: ${user?.id} has repute within last ${timeout} seconds, no reputation can be given` ); // Return interaction reply diff --git a/src/plugins/settings/guild/index.ts b/src/plugins/settings/guild/index.ts new file mode 100644 index 0000000..8beecfd --- /dev/null +++ b/src/plugins/settings/guild/index.ts @@ -0,0 +1,74 @@ +// 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 { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; + +// Function +export default { + data: (group: SlashCommandSubcommandGroupBuilder) => { + return group + .setName("guild") + .setDescription("Manage guild settings.") + .addSubcommand(pterodactyl.data) + .addSubcommand(credits.data) + .addSubcommand(points.data); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure member + const { memberPermissions, options, commandName, user, guild } = + interaction; + + // Check permission + if (!memberPermissions?.has(Permissions?.FLAGS?.MANAGE_GUILD)) { + // Create embed object + const embed = { + title: ":tools: Settings - Guild", + color: errorColor, + description: "You do not have permission to manage this!", + timestamp: new Date(), + footer: { + iconURL: footerIcon as string, + text: footerText as string, + }, + }; + + // Return interaction reply + return interaction?.editReply({ embeds: [embed] }); + } + + // Module - Pterodactyl + if (options?.getSubcommand() === "pterodactyl") { + // Execute Module - Pterodactyl + return pterodactyl.execute(interaction); + } + + // Module - Credits + else if (options?.getSubcommand() === "credits") { + // Execute Module - Credits + return credits.execute(interaction); + } + + // Module - Points + else if (options?.getSubcommand() === "points") { + // Execute Module - Points + return points.execute(interaction); + } + + // Send debug message + return logger?.debug( + `Guild: ${guild?.id} User: ${ + user?.id + } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` + ); + }, +}; diff --git a/src/plugins/settings/guild/modules/credits.ts b/src/plugins/settings/guild/modules/credits.ts new file mode 100644 index 0000000..dd38971 --- /dev/null +++ b/src/plugins/settings/guild/modules/credits.ts @@ -0,0 +1,134 @@ +// Dependencies +import { CommandInteraction } from "discord.js"; + +// Configurations +import { successColor, footerText, footerIcon } from "@config/embed"; + +//Handlers +import logger from "@logger"; + +// Models +import guildSchema from "@schemas/guild"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("credits") + .setDescription("Credits") + .addBooleanOption((option) => + option.setName("status").setDescription("Should credits be enabled?") + ) + .addNumberOption((option) => + option.setName("rate").setDescription("Amount of credits per message.") + ) + .addNumberOption((option) => + option + .setName("minimum-length") + .setDescription("Minimum length of message to earn credits.") + ) + .addNumberOption((option) => + option + .setName("work-rate") + .setDescription("Maximum amount of credits on work.") + ) + .addNumberOption((option) => + option + .setName("work-timeout") + .setDescription("Timeout between work schedules (milliseconds).") + ) + .addNumberOption((option) => + option + .setName("timeout") + .setDescription("Timeout between earning credits (milliseconds).") + ); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure member + const { guild, user, options } = interaction; + + // Get options + const status = options?.getBoolean("status"); + const rate = options?.getNumber("rate"); + const timeout = options?.getNumber("timeout"); + const minimumLength = options?.getNumber("minimum-length"); + const workRate = options?.getNumber("work-rate"); + const workTimeout = options?.getNumber("work-timeout"); + + // Get guild object + const guildDB = await guildSchema?.findOne({ + guildId: guild?.id, + }); + + if (guildDB === null) return; + + // Modify values + guildDB.credits.status = + status !== null ? status : guildDB?.credits?.status; + guildDB.credits.rate = rate !== null ? rate : guildDB?.credits?.rate; + guildDB.credits.timeout = + timeout !== null ? timeout : guildDB?.credits?.timeout; + guildDB.credits.workRate = + workRate !== null ? workRate : guildDB?.credits?.workRate; + guildDB.credits.workTimeout = + workTimeout !== null ? workTimeout : guildDB?.credits?.workTimeout; + guildDB.credits.minimumLength = + minimumLength !== null ? minimumLength : guildDB?.credits?.minimumLength; + + // Save guild + await guildDB?.save()?.then(async () => { + // Embed object + const embed = { + title: ":tools: Settings - Guild [Credits]", + description: "Following settings is set!", + color: successColor, + fields: [ + { + name: "🤖 Status", + value: `${guildDB?.credits?.status}`, + inline: true, + }, + { + name: "📈 Rate", + value: `${guildDB?.credits?.rate}`, + inline: true, + }, + { + name: "📈 Work Rate", + value: `${guildDB?.credits?.workRate}`, + inline: true, + }, + { + name: "🔨 Minimum Length", + value: `${guildDB?.credits?.minimumLength}`, + inline: true, + }, + { + name: "⏰ Timeout", + value: `${guildDB?.credits?.timeout}`, + inline: true, + }, + { + name: "⏰ Work Timeout", + value: `${guildDB?.credits?.workTimeout}`, + inline: true, + }, + ], + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, + }; + + // Send debug message + logger?.debug( + `Guild: ${guild?.id} User: ${user.id} has changed credit details.` + ); + + // Return interaction reply + return interaction?.editReply({ embeds: [embed] }); + }); + }, +}; diff --git a/src/plugins/settings/guild/modules/points.ts b/src/plugins/settings/guild/modules/points.ts new file mode 100644 index 0000000..b9ef0b1 --- /dev/null +++ b/src/plugins/settings/guild/modules/points.ts @@ -0,0 +1,107 @@ +// Dependencies +import { CommandInteraction } from "discord.js"; + +// Configurations +import { successColor, footerText, footerIcon } from "@config/embed"; + +// Handlers +import logger from "../../../../logger"; + +// Models +import guildSchema from "../../../../database/schemas/guild"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("points") + .setDescription("Points") + .addBooleanOption((option) => + option.setName("status").setDescription("Should credits be enabled?") + ) + .addNumberOption((option) => + option.setName("rate").setDescription("Amount of credits per message.") + ) + .addNumberOption((option) => + option + .setName("minimum-length") + .setDescription("Minimum length of message to earn credits.") + ) + .addNumberOption((option) => + option + .setName("timeout") + .setDescription("Timeout between earning credits (milliseconds).") + ); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure member + const { options, guild, user } = interaction; + + // Get options + const status = options?.getBoolean("status"); + const rate = options?.getNumber("rate"); + const timeout = options?.getNumber("timeout"); + const minimumLength = options?.getNumber("minimum-length"); + + // Get guild object + const guildDB = await guildSchema?.findOne({ + guildId: guild?.id, + }); + + if (guildDB === null) return; + + // Modify values + guildDB.points.status = status !== null ? status : guildDB?.points?.status; + guildDB.points.rate = rate !== null ? rate : guildDB?.points?.rate; + guildDB.points.timeout = + timeout !== null ? timeout : guildDB?.points?.timeout; + guildDB.points.minimumLength = + minimumLength !== null ? minimumLength : guildDB?.points?.minimumLength; + + // Save guild + await guildDB?.save()?.then(async () => { + // Create embed object + const embed = { + title: ":hammer: Settings - Guild [Points]", + description: "Following settings is set!", + color: successColor, + fields: [ + { + name: "🤖 Status", + value: `${guildDB?.points?.status}`, + inline: true, + }, + { + name: "📈 Rate", + value: `${guildDB?.points?.rate}`, + inline: true, + }, + { + name: "🔨 Minimum Length", + value: `${guildDB?.points?.minimumLength}`, + inline: true, + }, + { + name: "⏰ Timeout", + value: `${guildDB?.points?.timeout}`, + inline: true, + }, + ], + timestamp: new Date(), + footer: { + iconURL: footerIcon, + text: footerText, + }, + }; + + // Send debug message + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} has changed credit details.` + ); + + // Return interaction reply + return interaction?.editReply({ embeds: [embed] }); + }); + }, +}; diff --git a/src/plugins/settings/guild/modules/pterodactyl.ts b/src/plugins/settings/guild/modules/pterodactyl.ts new file mode 100644 index 0000000..d25bfe3 --- /dev/null +++ b/src/plugins/settings/guild/modules/pterodactyl.ts @@ -0,0 +1,68 @@ +// Dependencies +import { CommandInteraction } from "discord.js"; + +// Configurations +import { successColor, footerText, footerIcon } from "@config/embed"; + +// Handlers +import logger from "../../../../logger"; + +// Models +import apiSchema from "../../../../database/schemas/api"; +import encryption from "../../../../handlers/encryption"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; + +// Function +export default { + data: (command: SlashCommandSubcommandBuilder) => { + return command + .setName("pterodactyl") + .setDescription("Controlpanel.gg") + .addStringOption((option) => + option.setName("url").setDescription("The api url").setRequired(true) + ) + .addStringOption((option) => + option + .setName("token") + .setDescription("The api token") + .setRequired(true) + ); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure member + const { options, guild, user } = interaction; + + // Get options + const url = options?.getString("url"); + const token = encryption.encrypt(options?.getString("token")); + + // Update API credentials + await apiSchema + ?.findOneAndUpdate( + { guildId: guild?.id }, + { url, token }, + { new: true, upsert: true } + ) + .then(async () => { + // Embed object + const embed = { + title: ":hammer: Settings - Guild [Pterodactyl]", + color: successColor, + description: "Pterodactyl settings is saved!", + timestamp: new Date(), + footer: { + iconURL: footerIcon as string, + text: footerText as string, + }, + }; + + // Send debug message + logger?.debug( + `Guild: ${guild?.id} User: ${user?.id} has changed api credentials.` + ); + + // Return interaction reply + return interaction?.editReply({ embeds: [embed] }); + }); + }, +}; diff --git a/src/plugins/settings/index.ts b/src/plugins/settings/index.ts new file mode 100644 index 0000000..0ccb54d --- /dev/null +++ b/src/plugins/settings/index.ts @@ -0,0 +1,43 @@ +// 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) { + // Destructure + const { options, commandName, user, guild } = interaction; + + // Group - Guild + if (options.getSubcommandGroup() === "guild") { + // Execute Group - Guild + await guildGroup.execute(interaction); + } + // Group - User + else if (options.getSubcommandGroup() === "user") { + // Execute Group - User + await userGroup.execute(interaction); + } + + // Send debug message + return logger?.debug( + `Guild: ${guild?.id} User: ${ + user?.id + } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` + ); + }, +}; diff --git a/src/plugins/settings/user/index.ts b/src/plugins/settings/user/index.ts new file mode 100644 index 0000000..e325780 --- /dev/null +++ b/src/plugins/settings/user/index.ts @@ -0,0 +1,47 @@ +// 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("Manage user settings.") + .addSubcommand((command) => + command + .setName("appearance") + .setDescription("Manage your appearance") + .addStringOption((option) => + option + .setName("language") + .setDescription("Configure your language") + .addChoice("English", "en") + .addChoice("Swedish", "sv") + ) + ); + }, + execute: async (interaction: CommandInteraction) => { + // Destructure member + const { guild, user, options, commandName } = interaction; + + // Module - Appearance + if (options?.getSubcommand() === "appearance") { + // Execute Module - Appearance + await appearance(interaction); + } + + // Send debug message + return logger?.debug( + `Guild: ${guild?.id} User: ${ + user?.id + } executed /${commandName} ${options?.getSubcommandGroup()} ${options?.getSubcommand()}` + ); + }, +}; diff --git a/src/commands/settings/user/modules/appearance.ts b/src/plugins/settings/user/modules/appearance.ts similarity index 65% rename from src/commands/settings/user/modules/appearance.ts rename to src/plugins/settings/user/modules/appearance.ts index 17ecb8a..ee02a2d 100644 --- a/src/commands/settings/user/modules/appearance.ts +++ b/src/plugins/settings/user/modules/appearance.ts @@ -1,11 +1,11 @@ // Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; +import { CommandInteraction } from "discord.js"; // Configurations -import config from "../../../../../config.json"; +import { successColor, footerText, footerIcon } from "@config/embed"; // Handlers -import logger from "../../../../handlers/logger"; +import logger from "../../../../logger"; // Models import fetchUser from "../../../../helpers/fetchUser"; @@ -32,20 +32,20 @@ export default async (interaction: CommandInteraction) => { await userDB?.save()?.then(async () => { // Embed object const embed = { - title: ":hammer: Settings - User [Appearance]" as string, - description: "Following settings is set!" as string, - color: config?.colors?.success as ColorResolvable, + title: ":hammer: Settings - User [Appearance]", + description: "Following settings is set!", + color: successColor, fields: [ { - name: "🏳️‍🌈 Language" as string, - value: `${userDB?.language}` as string, + name: "🏳️‍🌈 Language", + value: `${userDB?.language}`, inline: true, }, ], timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; diff --git a/src/commands/shop/index.ts b/src/plugins/shop/index.ts similarity index 94% rename from src/commands/shop/index.ts rename to src/plugins/shop/index.ts index 8dc0e22..c601eac 100644 --- a/src/commands/shop/index.ts +++ b/src/plugins/shop/index.ts @@ -9,13 +9,14 @@ import pterodactyl from "./modules/pterodactyl"; import roles from "./roles"; // Handlers -import logger from "../../handlers/logger"; +import logger from "../../logger"; // Function export default { + metadata: { author: "Zyner" }, data: new SlashCommandBuilder() .setName("shop") - .setDescription("Open our shop.") + .setDescription("Purchase some items using your credits.") .addSubcommand((subcommand) => subcommand .setName("pterodactyl") diff --git a/src/commands/shop/modules/pterodactyl.ts b/src/plugins/shop/modules/pterodactyl.ts similarity index 54% rename from src/commands/shop/modules/pterodactyl.ts rename to src/plugins/shop/modules/pterodactyl.ts index 8453862..c523a8f 100644 --- a/src/commands/shop/modules/pterodactyl.ts +++ b/src/plugins/shop/modules/pterodactyl.ts @@ -1,21 +1,26 @@ // Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; +import { CommandInteraction } from "discord.js"; import { v4 as uuidv4 } from "uuid"; import axios from "axios"; // Configurations -import config from "../../../../config.json"; +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; // Handlers -import logger from "../../../handlers/logger"; -import encryption from "../../../handlers/encryption"; +import logger from "@logger"; +import encryption from "@handlers/encryption"; // Helpers -import creditNoun from "../../../helpers/creditNoun"; +import pluralize from "@helpers/pluralize"; // Models -import apiSchema from "../../../helpers/database/models/apiSchema"; -import fetchUser from "../../../helpers/fetchUser"; +import apiSchema from "@schemas/api"; +import fetchUser from "@helpers/fetchUser"; // Function export default async (interaction: CommandInteraction) => { @@ -28,13 +33,13 @@ export default async (interaction: CommandInteraction) => { if (optionAmount === null) { // Embed object const embed = { - title: ":dollar: Credits [Gift]" as string, - description: "We could not read your requested amount." as string, - color: config?.colors?.error as ColorResolvable, + title: ":dollar: Credits [Gift]", + description: "We could not read your requested amount.", + color: errorColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; @@ -55,20 +60,19 @@ export default async (interaction: CommandInteraction) => { // Stop if amount or user credits is below 100 if ((optionAmount || userDB?.credits) < 100) { const embed = { - title: ":shopping_cart: Shop [Pterodactyl]" as string, - description: - `You **can't** withdraw for __Pterodactyl__ below **100**.` as string, - color: config?.colors?.error as ColorResolvable, + title: ":shopping_cart: Shop [Pterodactyl]", + description: `You **can't** withdraw for __Pterodactyl__ below **100**.`, + color: errorColor, fields: [ { - name: "Your balance" as string, - value: `${creditNoun(userDB?.credits)}` as string, + name: "Your balance", + value: `${pluralize(userDB?.credits, "credit")}`, }, ], timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; return interaction?.editReply({ embeds: [embed] }); @@ -77,20 +81,19 @@ export default async (interaction: CommandInteraction) => { // Stop if amount or user credits is above 1.000.000 if ((optionAmount || userDB?.credits) > 1000000) { const embed = { - title: ":shopping_cart: Shop [Pterodactyl]" as string, - description: - `You **can't** withdraw for __Pterodactyl__ above **1.000.000**.` as string, - color: config?.colors?.error as ColorResolvable, + title: ":shopping_cart: Shop [Pterodactyl]", + description: `You **can't** withdraw for __Pterodactyl__ above **1.000.000**.`, + color: errorColor, fields: [ { - name: "Your balance" as string, - value: `${creditNoun(userDB?.credits)}` as string, + name: "Your balance", + value: `${pluralize(userDB?.credits, "credit")}`, }, ], timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; return interaction?.editReply({ embeds: [embed] }); @@ -99,19 +102,19 @@ export default async (interaction: CommandInteraction) => { // Stop if user credits is below amount if (userDB?.credits < optionAmount) { const embed = { - title: ":shopping_cart: Shop [Pterodactyl]" as string, - description: `You have **insufficient** credits.` as string, - color: config.colors.error as ColorResolvable, + title: ":shopping_cart: Shop [Pterodactyl]", + description: `You have **insufficient** credits.`, + color: errorColor, fields: [ { - name: "Your balance" as string, - value: `${creditNoun(userDB?.credits)}` as string, + name: "Your balance", + value: `${pluralize(userDB?.credits, "credit")}`, }, ], timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; return interaction?.editReply({ embeds: [embed] }); @@ -151,33 +154,33 @@ export default async (interaction: CommandInteraction) => { ?.then(async () => { // Create DM embed object const dmEmbed = { - title: ":shopping_cart: Shop [Pterodactyl]" as string, - description: `Redeem this voucher [here](${shopUrl})!` as string, + title: ":shopping_cart: Shop [Pterodactyl]", + description: `Redeem this voucher [here](${shopUrl})!`, fields: [ - { name: "Code" as string, value: `${code}` as string, inline: true }, + { name: "Code", value: `${code}`, inline: true }, { - name: "Credits" as string, - value: `${optionAmount || userDB?.credits}` as string, + name: "Credits", + value: `${optionAmount || userDB?.credits}`, inline: true, }, ], - color: config?.colors?.success as ColorResolvable, + color: successColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; // Create interaction embed object const interactionEmbed = { - title: ":shopping_cart: Shop [Pterodactyl]" as string, - description: "I have sent you the code in DM!" as string, - color: config?.colors?.success as ColorResolvable, + title: ":shopping_cart: Shop [Pterodactyl]", + description: "I have sent you the code in DM!", + color: successColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; @@ -191,7 +194,10 @@ export default async (interaction: CommandInteraction) => { ?.then(async () => { // Send debug message logger?.debug( - `User: ${user?.username} redeemed: ${creditNoun(optionAmount)}` + `User: ${user?.username} redeemed: ${pluralize( + optionAmount, + "credit" + )}` ); // Send DM message @@ -207,14 +213,14 @@ export default async (interaction: CommandInteraction) => { .catch(async (e: any) => { logger?.error(e); const embed = { - title: ":shopping_cart: Shop [Pterodactyl]" as string, + title: ":shopping_cart: Shop [Pterodactyl]", description: - "Something went wrong, please try again later." as string, - color: config?.colors?.error as ColorResolvable, + "Something went wrong while saving your credits, please try again later.", + color: errorColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; return interaction?.editReply({ embeds: [embed] }); @@ -225,13 +231,13 @@ export default async (interaction: CommandInteraction) => { .catch(async (e) => { logger?.error(e); const embed = { - title: ":shopping_cart: Shop [Pterodactyl]" as string, - description: "Something went wrong, please try again later." as string, - color: config?.colors?.error as ColorResolvable, + title: ":shopping_cart: Shop [Pterodactyl]", + description: "Something went wrong, please try again later.", + color: errorColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; return interaction?.editReply({ embeds: [embed] }); diff --git a/src/commands/shop/roles/index.ts b/src/plugins/shop/roles/index.ts similarity index 94% rename from src/commands/shop/roles/index.ts rename to src/plugins/shop/roles/index.ts index 5103fd6..42b5b45 100644 --- a/src/commands/shop/roles/index.ts +++ b/src/plugins/shop/roles/index.ts @@ -2,7 +2,7 @@ import { CommandInteraction } from "discord.js"; // Handlers -import logger from "../../../handlers/logger"; +import logger from "../../../logger"; // Modules import buy from "./modules/buy"; diff --git a/src/commands/shop/roles/modules/buy.ts b/src/plugins/shop/roles/modules/buy.ts similarity index 60% rename from src/commands/shop/roles/modules/buy.ts rename to src/plugins/shop/roles/modules/buy.ts index 8e99cf3..238f4e6 100644 --- a/src/commands/shop/roles/modules/buy.ts +++ b/src/plugins/shop/roles/modules/buy.ts @@ -6,15 +6,19 @@ import { } from "discord.js"; // Configurations -import config from "../../../../../config.json"; - +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; // Models -import shopRolesSchema from "../../../../helpers/database/models/shopRolesSchema"; -import guildSchema from "../../../../helpers/database/models/guildSchema"; +import shopRolesSchema from "@schemas/shopRole"; +import guildSchema from "@schemas/guild"; // Helpers -import creditNoun from "../../../../helpers/creditNoun"; -import fetchUser from "../../../../helpers/fetchUser"; +import pluralize from "@helpers/pluralize"; +import fetchUser from "@helpers/fetchUser"; // Function export default async (interaction: CommandInteraction) => { @@ -27,13 +31,13 @@ export default async (interaction: CommandInteraction) => { if (optionName === null) { // Embed object const embed = { - title: ":dollar: Shop - Roles [Buy]" as string, - description: "We could not read your requested name." as string, - color: config?.colors?.error as ColorResolvable, + title: ":dollar: Shop - Roles [Buy]", + description: "We could not read your requested name.", + color: errorColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; @@ -74,23 +78,21 @@ export default async (interaction: CommandInteraction) => { }); await (member?.roles as GuildMemberRoleManager)?.add(role?.id); - await shopRolesSchema?.find()?.then((role: any) => console.log(role)); const embed = { - title: ":shopping_cart: Shop - Roles [Buy]" as string, - description: - `You have bought ${role?.name} for ${guildDB?.shop?.roles?.pricePerHour} per hour.` as string, - color: config?.colors?.success as ColorResolvable, + title: ":shopping_cart: Shop - Roles [Buy]", + description: `You have bought ${role?.name} for ${guildDB?.shop?.roles?.pricePerHour} per hour.`, + color: successColor, fields: [ { - name: "Your balance" as string, - value: `${creditNoun(userDB?.credits)}` as string, + name: "Your balance", + value: `${pluralize(userDB?.credits, "credit")}`, }, ], timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; return interaction?.editReply({ diff --git a/src/commands/shop/roles/modules/cancel.ts b/src/plugins/shop/roles/modules/cancel.ts similarity index 56% rename from src/commands/shop/roles/modules/cancel.ts rename to src/plugins/shop/roles/modules/cancel.ts index 9a41a01..3e8479c 100644 --- a/src/commands/shop/roles/modules/cancel.ts +++ b/src/plugins/shop/roles/modules/cancel.ts @@ -1,19 +1,19 @@ // Dependencies -import { - CommandInteraction, - ColorResolvable, - GuildMemberRoleManager, -} from "discord.js"; +import { CommandInteraction, GuildMemberRoleManager } from "discord.js"; // Configurations -import config from "../../../../../config.json"; - +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; // Models -import shopRolesSchema from "../../../../helpers/database/models/shopRolesSchema"; +import shopRolesSchema from "@schemas/shopRole"; // Helpers -import creditNoun from "../../../../helpers/creditNoun"; -import fetchUser from "../../../../helpers/fetchUser"; +import pluralize from "@helpers/pluralize"; +import fetchUser from "@helpers/fetchUser"; // Function export default async (interaction: CommandInteraction) => { @@ -25,13 +25,13 @@ export default async (interaction: CommandInteraction) => { if (optionRole === null) { // Embed object const embed = { - title: ":dollar: Shop - Roles [Cancel]" as string, - description: "We could not read your requested role." as string, - color: config?.colors?.error as ColorResolvable, + title: ":dollar: Shop - Roles [Cancel]", + description: "We could not read your requested role.", + color: errorColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; @@ -62,19 +62,19 @@ export default async (interaction: CommandInteraction) => { }); const embed = { - title: ":shopping_cart: Shop - Roles [Cancel]" as string, - description: `You have canceled ${optionRole.name}.` as string, - color: config?.colors?.success as ColorResolvable, + title: ":shopping_cart: Shop - Roles [Cancel]", + description: `You have canceled ${optionRole.name}.`, + color: successColor, fields: [ { - name: "Your balance" as string, - value: `${creditNoun(userDB?.credits)}` as string, + name: "Your balance", + value: `${pluralize(userDB?.credits, "credit")}`, }, ], timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; return interaction?.editReply({ diff --git a/src/commands/utilities/index.ts b/src/plugins/utilities/index.ts similarity index 96% rename from src/commands/utilities/index.ts rename to src/plugins/utilities/index.ts index 9e009a3..65fccdb 100644 --- a/src/commands/utilities/index.ts +++ b/src/plugins/utilities/index.ts @@ -8,10 +8,11 @@ import about from "./modules/about"; import stats from "./modules/stats"; // Handlers -import logger from "../../handlers/logger"; +import logger from "../../logger"; // Function export default { + metadata: { author: "Zyner" }, data: new SlashCommandBuilder() .setName("utilities") .setDescription("Common utilities.") diff --git a/src/commands/utilities/modules/about.ts b/src/plugins/utilities/modules/about.ts similarity index 52% rename from src/commands/utilities/modules/about.ts rename to src/plugins/utilities/modules/about.ts index e76779e..afce531 100644 --- a/src/commands/utilities/modules/about.ts +++ b/src/plugins/utilities/modules/about.ts @@ -1,25 +1,25 @@ // Dependencies -import { CommandInteraction, ColorResolvable } from "discord.js"; +import { CommandInteraction } from "discord.js"; // Configurations -import config from "../../../../config.json"; +import { successColor, footerText, footerIcon } from "@config/embed"; + +import { hosterName, hosterUrl } from "@config/other"; // Function export default async (interaction: CommandInteraction) => { const interactionEmbed = { - title: ":hammer: Utilities [About]" as string, + title: ":hammer: Utilities [About]", description: `This bot is hosted by ${ - config?.hoster?.url - ? `[${config?.hoster?.name}](${config?.hoster?.url})` - : `${config?.hoster?.name}` + hosterUrl ? `[${hosterName}](${hosterUrl})` : `${hosterName}` }, the bot is developed by [Zyner](https://github.com/ZynerOrg)! - If you are interested in contributing, then just [fork it](https://github.com/ZynerOrg/xyter) yourself, we :heart: Open Source.` as string, - color: config?.colors?.success as ColorResolvable, + If you are interested in contributing, then just [fork it](https://github.com/ZynerOrg/xyter) yourself, we :heart: Open Source.`, + color: successColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; interaction?.editReply({ embeds: [interactionEmbed] }); diff --git a/src/plugins/utilities/modules/lookup.ts b/src/plugins/utilities/modules/lookup.ts new file mode 100644 index 0000000..23d3661 --- /dev/null +++ b/src/plugins/utilities/modules/lookup.ts @@ -0,0 +1,117 @@ +// Dependencies +import axios from "axios"; +import { CommandInteraction } from "discord.js"; + +// Configurations +import { + successColor, + errorColor, + footerText, + footerIcon, +} from "@config/embed"; + +// Handlers +import logger from "../../../logger"; + +// Function +export default 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/commands/utilities/modules/stats.ts b/src/plugins/utilities/modules/stats.ts similarity index 56% rename from src/commands/utilities/modules/stats.ts rename to src/plugins/utilities/modules/stats.ts index 9463d71..ce95724 100644 --- a/src/commands/utilities/modules/stats.ts +++ b/src/plugins/utilities/modules/stats.ts @@ -1,5 +1,5 @@ -import config from "../../../../config.json"; -import { CommandInteraction, ColorResolvable } from "discord.js"; +import { successColor, footerText, footerIcon } from "@config/embed"; +import { CommandInteraction } from "discord.js"; export default async (interaction: CommandInteraction) => { const { client } = interaction; if (client?.uptime === null) return; @@ -14,44 +14,43 @@ export default async (interaction: CommandInteraction) => { const uptime = `${days} days, ${hours} hours, ${minutes} minutes and ${seconds} seconds`; const interactionEmbed = { - title: ":hammer: Utilities - Stats" as string, - description: - "Below you can see a list of statistics about the bot." as string, + title: ":hammer: Utilities - Stats", + description: "Below you can see a list of statistics about the bot.", fields: [ { - name: "⏰ Latency" as string, - value: `${Date?.now() - interaction?.createdTimestamp} ms` as string, + name: "⏰ Latency", + value: `${Date?.now() - interaction?.createdTimestamp} ms`, inline: true, }, { - name: "⏰ API Latency" as string, - value: `${Math?.round(client?.ws?.ping)} ms` as string, + name: "⏰ API Latency", + value: `${Math?.round(client?.ws?.ping)} ms`, inline: true, }, { - name: "⏰ Uptime" as string, - value: `${uptime}` as string, + name: "⏰ Uptime", + value: `${uptime}`, inline: false, }, { - name: "📈 Guilds" as string, - value: `${client?.guilds?.cache?.size}` as string, + name: "📈 Guilds", + value: `${client?.guilds?.cache?.size}`, inline: true, }, { - name: "📈 Users (non-unique)" as string, + name: "📈 Users (non-unique)", value: `${client?.guilds?.cache?.reduce( (acc, guild) => acc + guild?.memberCount, 0 - )}` as string, + )}`, inline: true, }, ], - color: config?.colors?.success as ColorResolvable, + color: successColor, timestamp: new Date(), footer: { - iconURL: config?.footer?.icon as string, - text: config?.footer?.text as string, + iconURL: footerIcon, + text: footerText, }, }; interaction?.editReply({ embeds: [interactionEmbed] }); diff --git a/src/schedules/index.ts b/src/schedules/index.ts new file mode 100644 index 0000000..8d5590a --- /dev/null +++ b/src/schedules/index.ts @@ -0,0 +1,18 @@ +// Dependencies +import { Client } from "discord.js"; +import schedule from "node-schedule"; + +import logger from "@logger"; + +// Jobs +import shopRoles from "@root/schedules/jobs/shopRoles"; + +export default async (client: Client) => { + const expression = "*/5 * * * *"; + + schedule.scheduleJob(expression, async () => { + logger.verbose(`Checking schedules! (${expression})`); + await shopRoles(client); + }); + logger.info("Successfully started schedule engine!"); +}; diff --git a/src/schedules/jobs/shopRoles.ts b/src/schedules/jobs/shopRoles.ts new file mode 100644 index 0000000..37ced37 --- /dev/null +++ b/src/schedules/jobs/shopRoles.ts @@ -0,0 +1,60 @@ +// Dependencies +import { Client } from "discord.js"; + +import logger from "@logger"; + +// Schemas +import userSchema from "@schemas/user"; +import shopRoleSchema from "@schemas/shopRole"; +import guildSchema from "@schemas/guild"; + +export default async (client: Client) => { + await shopRoleSchema?.find()?.then(async (shopRoles: any) => { + shopRoles?.map(async (shopRole: any) => { + const payed = new Date(shopRole?.lastPayed); + + const oneHourAfterPayed = payed?.setHours(payed?.getHours() + 1); + + if (new Date() > new Date(oneHourAfterPayed)) { + logger.debug( + `Role: ${shopRole?.roleId} Expires: ${ + new Date() < new Date(oneHourAfterPayed) + } Last Payed: ${shopRole?.lastPayed}` + ); + + // Get guild object + const guild = await guildSchema?.findOne({ + guildId: shopRole?.guildId, + }); + + if (guild === null) return; + const userDB = await userSchema?.findOne({ + userId: shopRole?.userId, + guildId: shopRole?.guildId, + }); + const { pricePerHour } = guild.shop.roles; + + if (userDB === null) return; + + if (userDB?.credits < pricePerHour) { + const rGuild = client?.guilds?.cache?.get(`${shopRole?.guildId}`); + const rMember = await rGuild?.members?.fetch(`${shopRole?.userId}`); + + shopRoleSchema + ?.deleteOne({ _id: shopRole?._id }) + ?.then(async () => + logger?.debug(`Removed ${shopRole?._id} from collection.`) + ); + + return rMember?.roles?.remove(`${shopRole?.roleId}`); + } + + shopRole.lastPayed = new Date(); + shopRole?.save()?.then(async () => { + userDB.credits -= pricePerHour; + userDB?.save(); + }); + } + }); + }); +}; diff --git a/src/types/common/discord.d.ts b/src/types/common/discord.d.ts index 30bae32..1e4926f 100644 --- a/src/types/common/discord.d.ts +++ b/src/types/common/discord.d.ts @@ -1,5 +1,5 @@ -import { Collection, Client as DJSClient } from 'discord.js'; -declare module 'discord.js' { +import { Collection, Client as DJSClient } from "discord.js"; +declare module "discord.js" { export interface Client extends DJSClient { commands: Collection; } diff --git a/tsconfig.json b/tsconfig.json index 565e6a7..6cb9403 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,13 +12,22 @@ "module": "commonjs", "resolveJsonModule": true, "isolatedModules": true, - // "removeComments": true, "outDir": "./build", + "baseUrl": "./src", + "typeRoots": ["/types/common", "./node_modules/@types"], "paths": { - "@interface/*": ["./src/Interfaces/*"] - }, - "typeRoots": ["./src/types/common", "./node_modules/@types"] + "@interface/*": ["Interfaces/*"], + "@root/*": ["*"], + "@config/*": ["config/*"], + "@logger": ["logger"], + "@database": ["database"], + "@schedules": ["schedules"], + "@handlers/*": ["handlers/*"], + "@helpers/*": ["helpers/*"], + "@locale": ["locale"], + "@schemas/*": ["database/schemas/*"] + } }, - "exclude": ["./node_modules", "./test"], - "include": ["./src"] + "include": ["./src"], + "exclude": ["./node_modules", "./test"] }