diff --git a/.gitignore b/.gitignore index bc29e41..ec6bc87 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,6 @@ dist src/config/config.json my/ .vscode/ - +.idea src/config/config.js package-lock.json \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 69a3d84..f019834 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,14 +1,14 @@ { - "printWidth": 80, - "tabWidth": 3, - "useTabs": false, - "semi": true, - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": false, - "arrowParens": "always", - "proseWrap": "preserve", - "endOfLine": "lf", - "htmlWhitespaceSensitivity": "ignore" + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "always", + "proseWrap": "preserve", + "endOfLine": "lf", + "htmlWhitespaceSensitivity": "ignore" } diff --git a/README.md b/README.md index febd3e2..024ab8f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ package-lock.json package.json SECURITY.md ``` + ## Authors - [@GrishMahat](https://github.com/GrishMahat) @@ -46,11 +47,13 @@ SECURITY.md ## Contributing #### 1. Fork the repository. + #### 2. Create a New Branch ```bash git checkout -b feature/your-feature-name ``` + #### 3. Commit Your Changes ## Usage @@ -58,15 +61,15 @@ git checkout -b feature/your-feature-name ```bash git commit -m "Add some feature" ``` + #### 4. Push to the Branch + ```bash git push origin feature/your-feature-name ``` + #### 5. Open a Pull Request. ## License This project is licensed under the MIT License. ![MIT License](https://img.shields.io/badge/License-MIT-green.svg) - - - diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..4fbdfb0 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,44 @@ +import { ESLint } from 'eslint'; +import globals from 'globals'; +import pluginJs from '@eslint/js'; +import eslintPluginImport from 'eslint-plugin-import'; + +const eslint = new ESLint({ + overrideConfig: { + env: { + node: true, // Specify that the environment is Node.js + es2021: true, // Use ES2021 features + }, + + parserOptions: { + ecmaVersion: 2021, // Use modern ECMAScript features + sourceType: 'module', // Allow using imports + }, + rules: { + 'no-unused-vars': 'warn', + 'import/no-unresolved': 'error', + 'prefer-const': 'error', + eqeqeq: ['error', 'always'], + curly: ['error', 'all'], + 'arrow-body-style': ['error', 'as-needed'], + 'consistent-return': 'error', + indent: ['error', 2], // Enforce consistent indentation (2 spaces) + 'linebreak-style': ['error', 'unix'], // Enforce consistent linebreak style + 'no-console': ['warn', { allow: ['warn', 'error'] }], // Allow console.warn and console.error + quotes: ['error', 'single'], // Enforce the use of single quotes + 'object-curly-spacing': ['error', 'always'], // Enforce consistent spacing inside braces + semi: ['error', 'always'], // Enforce semi-colons + 'comma-dangle': ['error', 'es5'], // Enforce trailing commas where valid in ES5 + 'space-before-function-paren': ['error', 'always'], // Enforce consistent spacing before function parentheses + 'keyword-spacing': ['error', { before: true, after: true }], // Enforce consistent spacing around keywords + 'no-shadow': 'error', // Disallow variable declarations from shadowing outer scope declarations + 'prefer-template': 'error', // Prefer template literals over string concatenation + 'array-callback-return': 'error', // Ensure return statements in callbacks of array methods + 'callback-return': ['error', ['callback', 'cb']], // Require return statements after callbacks + 'consistent-this': ['error', 'self'], // Enforce a consistent naming for `this` + 'handle-callback-err': 'error', // Require error handling in callbacks + }, + }, +}); + +export default eslint; diff --git a/package.json b/package.json index d7afea4..dc98aa6 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,62 @@ { - "name": "clienterrverse_economy", - "version": "2.0.0", - "main": "src/index.js", - "type": "module", - "scripts": { - "start": "node --no-warnings src/index.js", - "dev": "nodemon src/index.js", - "pre": "npx prettier --write ." - }, - "repository": { - "type": "git", - "url": "git+https://github.com/clienterrverse/clienterrverse_economy.git" - }, - "author": ".clienterr", - "license": "ISC", - "bugs": { - "url": "https://github.com/clienterrverse/clienterrverse_economy/issues" - }, - "homepage": "https://github.com/clienterrverse/clienterrverse_economy#readme", - "description": "", - "dependencies": { - "@napi-rs/canvas": "^0.1.53", - "axios": "^1.7.2", - "bottleneck": "^2.19.5", - "colors": "^1.4.0", - "date-fns": "^3.6.0", - "discord-arts": "^0.6.1", - "discord-html-transcripts": "^3.2.0", - "discord-image-generation": "^1.4.25", - "discord.js": "^14.16.1", - "mathjs": "^13.0.3", - "mongoose": "^8.5.2", - "ms": "^2.1.3", - "os": "^0.1.2" - }, - "devDependencies": { - "dotenv": "^16.4.5", - "nodemon": "^3.1.1", - "prettier": "3.3.3" - } + "name": "clienterrverse_economy", + "version": "2.0.0", + "main": "src/index.js", + "type": "module", + "scripts": { + "start": "node --no-warnings src/index.js", + "dev": "nodemon src/index.js", + "pre": "npx prettier --write .", + "lint": "npx eslint . --fix", + "precommit": "lint-staged", + "prettify": "npx prettier --write .", + "check-deps": "npm-check-updates", + "update-deps": "npm-check-updates -u && npm install" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/clienterrverse/clienterrverse_economy.git" + }, + "author": ".clienterr", + "license": "ISC", + "bugs": { + "url": "https://github.com/clienterrverse/clienterrverse_economy/issues" + }, + "homepage": "https://github.com/clienterrverse/clienterrverse_economy#readme", + "description": "", + "dependencies": { + "@eslint/eslintrc": "^3.1.0", + "@napi-rs/canvas": "^0.1.56", + "anime-wallpaper": "^3.2.1", + "axios": "^1.7.7", + "bottleneck": "^2.19.5", + "canvas": "^2.11.2", + "colors": "^1.4.0", + "crypto": "^1.0.1", + "date-fns": "^4.1.0", + "discord-arts": "^0.6.1", + "discord-html-transcripts": "^3.2.0", + "discord-image-generation": "^1.4.25", + "discord.js": "^14.16.2", + "dotenv": "^16.4.5", + "file-type": "^19.5.0", + "mathjs": "^13.1.1", + "mongoose": "^8.6.3", + "ms": "^2.1.3", + "os": "^0.1.2", + "perf_hooks": "^0.0.1", + "random-anime": "^1.0.6", + "undici": ">=6.19.8", + "uuid": "^10.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.10.0", + "eslint": "^8.50.0", + "eslint-plugin-import": "^2.30.0", + "globals": "^15.9.0", + "lint-staged": "^15.2.10", + "nodemon": "^3.1.7", + "npm-check-updates": "^17.1.3", + "prettier": "^3.3.3" + } } diff --git a/src/assets/graphql/Recommendation.graphql b/src/assets/graphql/Recommendation.graphql index 6a5a388..28001d1 100644 --- a/src/assets/graphql/Recommendation.graphql +++ b/src/assets/graphql/Recommendation.graphql @@ -1,45 +1,45 @@ query ($page: Int = 0, $amount: Int = 50, $watched: [Int!]!, $nextDay: Int!) { - Page(page: $page, perPage: $amount) { - pageInfo { - currentPage - hasNextPage - } - Recommendation( - notYetAired: false - mediaId_in: $watched - sort: TIME - airingAt_lesser: $nextDay - ) { - media { - id - siteUrl - format - duration - episodes - title { - romaji - } - coverImage { - large - color - } - externalLinks { - site - url - } - studios(isMain: true) { - edges { - isMain - node { - name - } - } + Page(page: $page, perPage: $amount) { + pageInfo { + currentPage + hasNextPage + } + Recommendation( + notYetAired: false + mediaId_in: $watched + sort: TIME + airingAt_lesser: $nextDay + ) { + media { + id + siteUrl + format + duration + episodes + title { + romaji + } + coverImage { + large + color + } + externalLinks { + site + url + } + studios(isMain: true) { + edges { + isMain + node { + name } - } - episode - airingAt - timeUntilAiring - id + } + } } - } + episode + airingAt + timeUntilAiring + id + } + } } diff --git a/src/assets/graphql/Schedule.graphql b/src/assets/graphql/Schedule.graphql index 5098c6f..183d391 100644 --- a/src/assets/graphql/Schedule.graphql +++ b/src/assets/graphql/Schedule.graphql @@ -1,45 +1,45 @@ query ($page: Int = 0, $amount: Int = 50, $watched: [Int!]!, $nextDay: Int!) { - Page(page: $page, perPage: $amount) { - pageInfo { - currentPage - hasNextPage - } - airingSchedules( - notYetAired: true - mediaId_in: $watched - sort: TIME - airingAt_lesser: $nextDay - ) { - media { - id - siteUrl - format - duration - episodes - title { - romaji - } - coverImage { - large - color - } - externalLinks { - site - url - } - studios(isMain: true) { - edges { - isMain - node { - name - } - } + Page(page: $page, perPage: $amount) { + pageInfo { + currentPage + hasNextPage + } + airingSchedules( + notYetAired: true + mediaId_in: $watched + sort: TIME + airingAt_lesser: $nextDay + ) { + media { + id + siteUrl + format + duration + episodes + title { + romaji + } + coverImage { + large + color + } + externalLinks { + site + url + } + studios(isMain: true) { + edges { + isMain + node { + name } - } - episode - airingAt - timeUntilAiring - id + } + } } - } + episode + airingAt + timeUntilAiring + id + } + } } diff --git a/src/assets/graphql/Seiyuu.graphql b/src/assets/graphql/Seiyuu.graphql index 92f4a36..79f1829 100644 --- a/src/assets/graphql/Seiyuu.graphql +++ b/src/assets/graphql/Seiyuu.graphql @@ -1,34 +1,34 @@ query ($search: String) { - Staff(search: $search, sort: SEARCH_MATCH) { - id - name { - full - native + Staff(search: $search, sort: SEARCH_MATCH) { + id + name { + full + native + } + language + image { + large + } + description + siteUrl + characters(page: 1, perPage: 20, sort: ROLE) { + nodes { + id + name { + full + native + } + siteUrl } - language - image { - large + } + staffMedia(page: 1, perPage: 20, sort: POPULARITY) { + nodes { + title { + romaji + english + } + siteUrl } - description - siteUrl - characters(page: 1, perPage: 20, sort: ROLE) { - nodes { - id - name { - full - native - } - siteUrl - } - } - staffMedia(page: 1, perPage: 20, sort: POPULARITY) { - nodes { - title { - romaji - english - } - siteUrl - } - } - } + } + } } diff --git a/src/assets/graphql/Watching.graphql b/src/assets/graphql/Watching.graphql index 10175b1..d043f52 100644 --- a/src/assets/graphql/Watching.graphql +++ b/src/assets/graphql/Watching.graphql @@ -1,16 +1,16 @@ query ($watched: [Int!] = [0], $page: Int!) { - Page(page: $page) { - pageInfo { - currentPage - hasNextPage + Page(page: $page) { + pageInfo { + currentPage + hasNextPage + } + media(id_in: $watched, sort: TITLE_ROMAJI) { + status + siteUrl + id + title { + romaji } - media(id_in: $watched, sort: TITLE_ROMAJI) { - status - siteUrl - id - title { - romaji - } - } - } + } + } } diff --git a/src/buttons/approve-guild-join.js b/src/buttons/approve-guild-join.js index 061cdca..d1fb3be 100644 --- a/src/buttons/approve-guild-join.js +++ b/src/buttons/approve-guild-join.js @@ -1,64 +1,92 @@ +/** + * Handles the approval of a guild join request. + * + * This function is triggered by the 'approve-guild-join' custom button. It retrieves the original message, + * extracts the user ID from the embed, finds the user in the pending members list, adds them to the guild members, + * removes them from the pending list, updates the original message, and notifies the user. + * + * @param {Discord.Client} client - The Discord client instance. + * @param {Discord.Interaction} interaction - The interaction object. + */ import { PermissionFlagsBits, EmbedBuilder } from 'discord.js'; import Guild from '../schemas/guildSchema.js'; // Adjust the import path as needed export default { - customId: 'approve-guild-join', - userPermissions: [PermissionFlagsBits.ManageGuild], - botPermissions: [PermissionFlagsBits.ManageRoles], - run: async (client, interaction) => { - try { - const { guild, member } = interaction; + /** + * The custom ID of the button. + * @type {string} + */ + customId: 'approve-guild-join', + /** + * The required permissions for the user to execute this action. + * @type {Array} + */ + userPermissions: [PermissionFlagsBits.ManageGuild], + /** + * The required permissions for the bot to execute this action. + * @type {Array} + */ + botPermissions: [PermissionFlagsBits.ManageRoles], + /** + * Executes the guild join request approval process. + * + * @param {Discord.Client} client - The Discord client instance. + * @param {Discord.Interaction} interaction - The interaction object. + */ + run: async (client, interaction) => { + try { + const { guild, member } = interaction; - // Get the original message - const message = interaction.message; - const embed = message.embeds[0]; + // Get the original message + const message = interaction.message; + const embed = message.embeds[0]; - // Extract user ID from the embed - const userField = embed.fields.find((field) => field.name === 'User'); - const userId = userField.value.match(/\d+/)[0]; + // Extract user ID from the embed + const userField = embed.fields.find((field) => field.name === 'User'); + const userId = userField.value.match(/\d+/)[0]; - // Find the user in pending members - const pendingMember = guildData.pendingMembers.find( - (pm) => pm.userId === userId - ); - if (!pendingMember) { - return interaction.reply({ - content: 'This user is no longer in the pending list.', - ephemeral: true, - }); - } + // Find the user in pending members + const pendingMember = guildData.pendingMembers.find( + (pm) => pm.userId === userId + ); + if (!pendingMember) { + return interaction.reply({ + content: 'This user is no longer in the pending list.', + ephemeral: true, + }); + } - // Add user to guild members and remove from pending - guildData.members.push(userId); - guildData.pendingMembers = guildData.pendingMembers.filter( - (pm) => pm.userId !== userId - ); - await guildData.save(); + // Add user to guild members and remove from pending + guildData.members.push(userId); + guildData.pendingMembers = guildData.pendingMembers.filter( + (pm) => pm.userId !== userId + ); + await guildData.save(); - // Update the original message - const updatedEmbed = EmbedBuilder.from(embed) - .setColor('Green') - .setTitle('Guild Join Request Approved') - .addFields({ name: 'Approved by', value: member.user.tag }); + // Update the original message + const updatedEmbed = EmbedBuilder.from(embed) + .setColor('Green') + .setTitle('Guild Join Request Approved') + .addFields({ name: 'Approved by', value: member.user.tag }); - await message.edit({ embeds: [updatedEmbed], components: [] }); + await message.edit({ embeds: [updatedEmbed], components: [] }); - // Notify the user - const user = await client.users.fetch(userId); - await user - .send('Your request to join the guild has been approved!') - .catch(() => {}); + // Notify the user + const user = await client.users.fetch(userId); + await user + .send('Your request to join the guild has been approved!') + .catch(() => {}); - return interaction.reply({ - content: 'Guild join request approved successfully.', - ephemeral: true, - }); - } catch (error) { - console.error(error); - return interaction.reply({ - content: 'An error occurred while processing the approval.', - ephemeral: true, - }); - } - }, + return interaction.reply({ + content: 'Guild join request approved successfully.', + ephemeral: true, + }); + } catch (error) { + console.error(error); + return interaction.reply({ + content: 'An error occurred while processing the approval.', + ephemeral: true, + }); + } + }, }; diff --git a/src/buttons/claimTicketBtn.js b/src/buttons/claimTicketBtn.js index e1ef5ba..248b278 100644 --- a/src/buttons/claimTicketBtn.js +++ b/src/buttons/claimTicketBtn.js @@ -2,128 +2,146 @@ import { EmbedBuilder, PermissionFlagsBits } from 'discord.js'; import ticketSchema from '../schemas/ticketSchema.js'; import ticketSetupSchema from '../schemas/ticketSetupSchema.js'; -export default { - customId: 'claimTicketBtn', - userPermissions: [], - botPermissions: [PermissionFlagsBits.ManageChannels], - - run: async (client, interaction) => { - try { - await interaction.deferReply({ ephemeral: true }); - - const { member, channel, guild } = interaction; - - const ticketSetup = await ticketSetupSchema - .findOne({ guildID: guild.id }) - .catch(() => null); - if (!ticketSetup) { - return await interaction.editReply( - 'Ticket setup not found. Please configure the ticket system.' - ); - } +/** + * @typedef {Object} ClaimTicketButton + * @property {string} customId - The custom ID of the button. + * @property {Array} userPermissions - The permissions required for the user to interact with the button. + * @property {Array} botPermissions - The permissions required for the bot to interact with the button. + * @property {Function} run - The function that runs when the button is interacted with. + */ - const staffRoleId = ticketSetup.staffRoleID; - if (!staffRoleId || !member.roles.cache.has(staffRoleId)) { - return await interaction.editReply( - 'You do not have the necessary permissions to claim this ticket.' - ); - } - - const ticket = await ticketSchema - .findOne({ ticketChannelID: channel.id }) - .catch(() => null); - if (!ticket) { - return await interaction.editReply( - 'This channel is not associated with a valid ticket.' +/** + * @type {ClaimTicketButton} + */ +const claimTicketButton = { + customId: 'claimTicketBtn', + userPermissions: [], + botPermissions: [PermissionFlagsBits.ManageChannels], + + /** + * The function that runs when the button is interacted with. + * @param {Client} client - The Discord client. + * @param {Interaction} interaction - The interaction that triggered the button. + */ + run: async (client, interaction) => { + try { + await interaction.deferReply({ ephemeral: true }); + + const { member, channel, guild } = interaction; + + const ticketSetup = await ticketSetupSchema + .findOne({ guildID: guild.id }) + .catch(() => null); + if (!ticketSetup) { + return await interaction.editReply( + 'Ticket setup not found. Please configure the ticket system.' + ); + } + + const staffRoleId = ticketSetup.staffRoleID; + if (!staffRoleId || !member.roles.cache.has(staffRoleId)) { + return await interaction.editReply( + 'You do not have the necessary permissions to claim this ticket.' + ); + } + + const ticket = await ticketSchema + .findOne({ ticketChannelID: channel.id }) + .catch(() => null); + if (!ticket) { + return await interaction.editReply( + 'This channel is not associated with a valid ticket.' + ); + } + + if (ticket.claimedBy) { + const claimedMember = await guild.members + .fetch(ticket.claimedBy) + .catch(() => null); + const claimedByText = claimedMember + ? claimedMember.toString() + : 'a staff member'; + return await interaction.editReply( + `This ticket has already been claimed by ${claimedByText}.` + ); + } + + const permissionUpdates = [ + { + id: member.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.ManageChannels, + ], + }, + { + id: guild.roles.everyone.id, + deny: [PermissionFlagsBits.ViewChannel], + }, + { + id: ticket.ticketMemberID, + allow: [ + PermissionFlagBits.ViewChannel, + PermissionFlagsBits.SendMessages, + ], + }, + { + id: staffRoleId, + deny: [PermissionFlagBits.ViewChannel], + }, + ]; + + await Promise.all( + permissionUpdates.map(async (update) => { + try { + await channel.permissionOverwrites.edit( + update.id, + update.allow || update.deny ); - } - - if (ticket.claimedBy) { - const claimedMember = await guild.members - .fetch(ticket.claimedBy) - .catch(() => null); - const claimedByText = claimedMember - ? claimedMember.toString() - : 'a staff member'; - return await interaction.editReply( - `This ticket has already been claimed by ${claimedByText}.` + } catch (error) { + console.error( + `Failed to update permissions for ${update.id}:`, + error ); - } - - const permissionUpdates = [ - { - id: member.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.ManageChannels, - ], - }, - { - id: guild.roles.everyone.id, - deny: [PermissionFlagsBits.ViewChannel], - }, - { - id: ticket.ticketMemberID, - allow: [ - PermissionFlagBits.ViewChannel, - PermissionFlagsBits.SendMessages, - ], - }, - { - id: staffRoleId, - deny: [PermissionFlagBits.ViewChannel], - }, - ]; - - await Promise.all( - permissionUpdates.map(async (update) => { - try { - await channel.permissionOverwrites.edit( - update.id, - update.allow || update.deny - ); - } catch (error) { - console.error( - `Failed to update permissions for ${update.id}:`, - error - ); - } - }) - ); - - ticket.claimedBy = member.id; - await ticket.save().catch((error) => { - console.error('Failed to save ticket:', error); - throw new Error('Failed to update ticket information.'); - }); - - const claimEmbed = new EmbedBuilder() - .setColor('#00FF00') - .setDescription(`${member} has claimed this ticket.`) - .setTimestamp(); - - await interaction.deleteReply(); - await channel.send({ embeds: [claimEmbed] }).catch((error) => { - console.error('Failed to send claim message:', error); - throw new Error('Failed to send claim notification.'); - }); - } catch (error) { - console.error('Error claiming ticket:', error); - - if (error.code === 10062) { - return await interaction - .followUp({ - content: 'This interaction has expired. Please try again.', - ephemeral: true, - }) - .catch(console.error); - } - - const errorMessage = - error.message || - 'There was an error claiming the ticket. Please try again later.'; - return await interaction.editReply(errorMessage).catch(console.error); + } + }) + ); + + ticket.claimedBy = member.id; + await ticket.save().catch((error) => { + console.error('Failed to save ticket:', error); + throw new Error('Failed to update ticket information.'); + }); + + const claimEmbed = new EmbedBuilder() + .setColor('#00FF00') + .setDescription(`${member} has claimed this ticket.`) + .setTimestamp(); + + await interaction.deleteReply(); + await channel.send({ embeds: [claimEmbed] }).catch((error) => { + console.error('Failed to send claim message:', error); + throw new Error('Failed to send claim notification.'); + }); + } catch (error) { + console.error('Error claiming ticket:', error); + + if (error.code === 10062) { + return await interaction + .followUp({ + content: 'This interaction has expired. Please try again.', + ephemeral: true, + }) + .catch(console.error); } - }, + + const errorMessage = + error.message || + 'There was an error claiming the ticket. Please try again later.'; + return await interaction.editReply(errorMessage).catch(console.error); + } + }, }; + +export default claimTicketButton; diff --git a/src/buttons/closeTicketBtn.js b/src/buttons/closeTicketBtn.js index a7efe87..bec69c6 100644 --- a/src/buttons/closeTicketBtn.js +++ b/src/buttons/closeTicketBtn.js @@ -1,93 +1,119 @@ +/** + * Represents the close ticket button functionality. + * + * This module exports a single object that defines the properties and behavior of the close ticket button. + * + * @module closeTicketBtn + */ + import { - ModalBuilder, - TextInputBuilder, - TextInputStyle, - ActionRowBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ActionRowBuilder, } from 'discord.js'; import ticketSetupSchema from '../schemas/ticketSetupSchema.js'; +/** + * The closeTicketBtn object. + * + * This object contains the properties and methods necessary to handle the close ticket button interaction. + * + * @property {string} customId - The custom ID of the button. + * @property {Array} userPermissions - An array of user permissions required to use this button. + * @property {Array} botPermissions - An array of bot permissions required to use this button. + * @property {Function} run - The function that runs when the button is clicked. + */ export default { - customId: 'closeTicketBtn', - userPermissions: [], - botPermissions: [], + customId: 'closeTicketBtn', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - const { guild, member } = interaction; + /** + * Handles the close ticket button interaction. + * + * This function is called when the close ticket button is clicked. It checks if the ticket system is configured, + * if the staff role exists, and if the member has the staff role. If all conditions are met, it shows a modal to + * confirm the closing of the ticket. + * + * @param {Client} client - The Discord client. + * @param {Interaction} interaction - The interaction object. + */ + run: async (client, interaction) => { + try { + const { guild, member } = interaction; - // Defer the reply to buy more time for processing - await interaction.deferReply({ ephemeral: true }); + // Defer the reply to buy more time for processing + await interaction.deferReply({ ephemeral: true }); - // Get the ticket setup configuration - const ticketSetup = await ticketSetupSchema - .findOne({ guildID: guild.id }) - .catch(() => null); - if (!ticketSetup) { - return await interaction.editReply({ - content: - 'Ticket system is not configured properly. Please contact an administrator.', - }); - } + // Get the ticket setup configuration + const ticketSetup = await ticketSetupSchema + .findOne({ guildID: guild.id }) + .catch(() => null); + if (!ticketSetup) { + return await interaction.editReply({ + content: + 'Ticket system is not configured properly. Please contact an administrator.', + }); + } - // Check if the staff role exists - const staffRole = await guild.roles - .fetch(ticketSetup.staffRoleID) - .catch(() => null); - if (!staffRole) { - return await interaction.editReply({ - content: - 'The configured staff role no longer exists. Please contact an administrator.', - }); - } + // Check if the staff role exists + const staffRole = await guild.roles + .fetch(ticketSetup.staffRoleID) + .catch(() => null); + if (!staffRole) { + return await interaction.editReply({ + content: + 'The configured staff role no longer exists. Please contact an administrator.', + }); + } - // Check if the member has the staff role - if (!member.roles.cache.has(staffRole.id)) { - return await interaction.editReply({ - content: 'You do not have permission to close tickets.', - }); - } + // Check if the member has the staff role + if (!member.roles.cache.has(staffRole.id)) { + return await interaction.editReply({ + content: 'You do not have permission to close tickets.', + }); + } - // Create the modal - const closeTicketModal = new ModalBuilder() - .setCustomId('closeTicketModal') - .setTitle('Close Ticket Confirmation') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId('closeTicketReason') - .setLabel('Reason for closing (optional)') - .setStyle(TextInputStyle.Paragraph) - .setRequired(false) - .setMaxLength(1000) - .setPlaceholder( - 'Enter the reason for closing this ticket...' - ) - ) - ); + // Create the modal + const closeTicketModal = new ModalBuilder() + .setCustomId('closeTicketModal') + .setTitle('Close Ticket Confirmation') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId('closeTicketReason') + .setLabel('Reason for closing (optional)') + .setStyle(TextInputStyle.Paragraph) + .setRequired(false) + .setMaxLength(1000) + .setPlaceholder('Enter the reason for closing this ticket...') + ) + ); - // Show the modal - await interaction.deleteReply(); - await interaction.showModal(closeTicketModal); - } catch (error) { - console.error('Error in closeTicketBtn:', error); + // Show the modal + await interaction.deleteReply(); + await interaction.showModal(closeTicketModal); + } catch (error) { + console.error('Error in closeTicketBtn:', error); - // Check if the interaction has already been replied to - if (interaction.deferred || interaction.replied) { - await interaction - .editReply({ - content: - 'An error occurred while processing your request. Please try again later.', - }) - .catch(console.error); - } else { - await interaction - .reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }) - .catch(console.error); - } + // Check if the interaction has already been replied to + if (interaction.deferred || interaction.replied) { + await interaction + .editReply({ + content: + 'An error occurred while processing your request. Please try again later.', + }) + .catch(console.error); + } else { + await interaction + .reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }) + .catch(console.error); } - }, + } + }, }; diff --git a/src/buttons/deny-guild-join.js b/src/buttons/deny-guild-join.js index 70d46fd..034ba1e 100644 --- a/src/buttons/deny-guild-join.js +++ b/src/buttons/deny-guild-join.js @@ -2,71 +2,71 @@ import { PermissionFlagsBits, EmbedBuilder } from 'discord.js'; import Guild from '../schemas/guildSchema.js'; // Adjust the import path as needed export default { - customId: 'deny-guild-join', - userPermissions: [PermissionFlagsBits.ManageGuild], - botPermissions: [PermissionFlagsBits.ManageRoles], - run: async (client, interaction) => { - try { - const { guild, member } = interaction; + customId: 'deny-guild-join', + userPermissions: [PermissionFlagsBits.ManageGuild], + botPermissions: [PermissionFlagsBits.ManageRoles], + run: async (client, interaction) => { + try { + const { guild, member } = interaction; - // Find the guild document - const guildData = await Guild.findOne({ guildId: guild.id }); - if (!guildData) { - return interaction.reply({ - content: 'Guild data not found.', - ephemeral: true, - }); - } + // Find the guild document + const guildData = await Guild.findOne({ guildId: guild.id }); + if (!guildData) { + return interaction.reply({ + content: 'Guild data not found.', + ephemeral: true, + }); + } - // Get the original message - const message = interaction.message; - const embed = message.embeds[0]; + // Get the original message + const message = interaction.message; + const embed = message.embeds[0]; - // Extract user ID from the embed - const userField = embed.fields.find((field) => field.name === 'User'); - const userId = userField.value.match(/\d+/)[0]; + // Extract user ID from the embed + const userField = embed.fields.find((field) => field.name === 'User'); + const userId = userField.value.match(/\d+/)[0]; - // Find the user in pending members - const pendingMember = guildData.pendingMembers.find( - (pm) => pm.userId === userId - ); - if (!pendingMember) { - return interaction.reply({ - content: 'This user is no longer in the pending list.', - ephemeral: true, - }); - } + // Find the user in pending members + const pendingMember = guildData.pendingMembers.find( + (pm) => pm.userId === userId + ); + if (!pendingMember) { + return interaction.reply({ + content: 'This user is no longer in the pending list.', + ephemeral: true, + }); + } - // Remove from pending members - guildData.pendingMembers = guildData.pendingMembers.filter( - (pm) => pm.userId !== userId - ); - await guildData.save(); + // Remove from pending members + guildData.pendingMembers = guildData.pendingMembers.filter( + (pm) => pm.userId !== userId + ); + await guildData.save(); - // Update the original message - const updatedEmbed = EmbedBuilder.from(embed) - .setColor('Red') - .setTitle('Guild Join Request Denied') - .addFields({ name: 'Denied by', value: member.user.tag }); + // Update the original message + const updatedEmbed = EmbedBuilder.from(embed) + .setColor('Red') + .setTitle('Guild Join Request Denied') + .addFields({ name: 'Denied by', value: member.user.tag }); - await message.edit({ embeds: [updatedEmbed], components: [] }); + await message.edit({ embeds: [updatedEmbed], components: [] }); - // Notify the user - const user = await client.users.fetch(userId); - await user - .send('Your request to join the guild has been denied.') - .catch(() => {}); + // Notify the user + const user = await client.users.fetch(userId); + await user + .send('Your request to join the guild has been denied.') + .catch(() => {}); - return interaction.reply({ - content: 'Guild join request denied successfully.', - ephemeral: true, - }); - } catch (error) { - console.error(error); - return interaction.reply({ - content: 'An error occurred while processing the denial.', - ephemeral: true, - }); - } - }, + return interaction.reply({ + content: 'Guild join request denied successfully.', + ephemeral: true, + }); + } catch (error) { + console.error(error); + return interaction.reply({ + content: 'An error occurred while processing the denial.', + ephemeral: true, + }); + } + }, }; diff --git a/src/buttons/kick_user.js b/src/buttons/kick_user.js index c17537e..3e81fc9 100644 --- a/src/buttons/kick_user.js +++ b/src/buttons/kick_user.js @@ -3,63 +3,63 @@ import { ActionRowBuilder, UserSelectMenuBuilder } from 'discord.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; export default { - customId: 'kick_user', - userPermissions: [], - botPermissions: [], + customId: 'kick_user', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - // Perform comprehensive checks - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + run: async (client, interaction) => { + try { + // Perform comprehensive checks + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: - checkResult.message || - 'You do not have permission to kick users from this channel.', - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: + checkResult.message || + 'You do not have permission to kick users from this channel.', + ephemeral: true, + }); + } - const voiceChannel = interaction.member.voice.channel; + const voiceChannel = interaction.member.voice.channel; - if (!voiceChannel) { - return interaction.reply({ - content: 'You are not in a voice channel.', - ephemeral: true, - }); - } + if (!voiceChannel) { + return interaction.reply({ + content: 'You are not in a voice channel.', + ephemeral: true, + }); + } - // Create the user select menu - const selectMenu = new UserSelectMenuBuilder() - .setCustomId('kick_user_select') - .setPlaceholder('Select a user to kick') - .setMinValues(1) - .setMaxValues(1); + // Create the user select menu + const selectMenu = new UserSelectMenuBuilder() + .setCustomId('kick_user_select') + .setPlaceholder('Select a user to kick') + .setMinValues(1) + .setMaxValues(1); - // Create an action row and add the select menu to it - const actionRow = new ActionRowBuilder().addComponents(selectMenu); + // Create an action row and add the select menu to it + const actionRow = new ActionRowBuilder().addComponents(selectMenu); - // Reply to the interaction with the select menu - await interaction.reply({ - content: 'Select a user to kick:', - components: [actionRow], - ephemeral: true, - }); - } catch (error) { - console.error('Error in kick_user interaction:', error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); - } - }, + // Reply to the interaction with the select menu + await interaction.reply({ + content: 'Select a user to kick:', + components: [actionRow], + ephemeral: true, + }); + } catch (error) { + console.error('Error in kick_user interaction:', error); + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/buttons/lockTicketBtn.js b/src/buttons/lockTicketBtn.js index 8e0c228..4c4dcd3 100644 --- a/src/buttons/lockTicketBtn.js +++ b/src/buttons/lockTicketBtn.js @@ -3,106 +3,103 @@ import ticketSchema from '../schemas/ticketSchema.js'; import ticketSetupSchema from '../schemas/ticketSetupSchema.js'; export default { - customId: 'lockTicketBtn', - userPermissions: [], - botPermissions: [PermissionFlagsBits.ManageChannels], - - run: async (client, interaction) => { - await interaction.deferReply({ ephemeral: true }); - - try { - const { channel, guild, member } = interaction; - - const [ticket, ticketSetup] = await Promise.all([ - ticketSchema.findOne({ ticketChannelID: channel.id }), - ticketSetupSchema.findOne({ guildID: guild.id }), - ]); - - if (!ticket || !ticketSetup) { - return await interaction.editReply({ - content: !ticket - ? 'Ticket not found.' - : 'Ticket system is not configured properly.', - }); - } - - const staffRole = await guild.roles - .fetch(ticketSetup.staffRoleID) - .catch(() => null); - if (!staffRole) { - return await interaction.editReply({ - content: - 'Staff role not found. Please contact an administrator.', - }); - } - - if (!member.roles.cache.has(staffRole.id)) { - return await interaction.editReply({ - content: 'You do not have permission to lock or unlock tickets.', - }); - } - - const isClaimer = member.id === ticket.claimedBy; - const isAdmin = member.permissions.has( - PermissionFlagsBits.Administrator - ); - - if (!isClaimer && !isAdmin) { - return await interaction.editReply({ - content: - 'Only the staff member who claimed this ticket or an administrator can lock/unlock it.', - }); - } - - const ticketMember = await guild.members - .fetch(ticket.ticketMemberID) - .catch(() => null); - if (!ticketMember) { - return await interaction.editReply({ - content: 'Ticket member not found in the guild.', - }); - } - - const currentPermissions = channel.permissionsFor(ticketMember); - if (!currentPermissions) { - return await interaction.editReply({ - content: - 'Could not determine the current permissions for the ticket member.', - }); - } - - const isLocked = !currentPermissions.has( - PermissionFlagsBits.SendMessages - ); - const newPermissions = { - [PermissionFlagsBits.SendMessages]: isLocked, - [PermissionFlagsBits.ViewChannel]: true, - }; - - await channel.permissionOverwrites.edit(ticketMember, newPermissions); - - const embedData = { - color: isLocked ? 'Green' : 'Orange', - title: isLocked ? 'Ticket Unlocked' : 'Ticket Locked', - description: `This ticket has been ${isLocked ? 'unlocked' : 'locked'} by ${member.user.tag}.`, - }; - - const statusEmbed = new EmbedBuilder(embedData); - - await Promise.all([ - interaction.editReply({ - content: `This ticket has been ${isLocked ? 'unlocked' : 'locked'}.`, - }), - channel.send({ embeds: [statusEmbed] }), - ]); - } catch (error) { - console.error('Error toggling ticket lock:', error); - await interaction - .editReply({ - content: - 'There was an error toggling the ticket lock. Please try again later.', - }) - .catch(console.error); + customId: 'lockTicketBtn', + userPermissions: [], + botPermissions: [PermissionFlagsBits.ManageChannels], + + run: async (client, interaction) => { + await interaction.deferReply({ ephemeral: true }); + + try { + const { channel, guild, member } = interaction; + + const [ticket, ticketSetup] = await Promise.all([ + ticketSchema.findOne({ ticketChannelID: channel.id }), + ticketSetupSchema.findOne({ guildID: guild.id }), + ]); + + if (!ticket || !ticketSetup) { + return await interaction.editReply({ + content: !ticket + ? 'Ticket not found.' + : 'Ticket system is not configured properly.', + }); } - }, + + const staffRole = await guild.roles + .fetch(ticketSetup.staffRoleID) + .catch(() => null); + if (!staffRole) { + return await interaction.editReply({ + content: 'Staff role not found. Please contact an administrator.', + }); + } + + if (!member.roles.cache.has(staffRole.id)) { + return await interaction.editReply({ + content: 'You do not have permission to lock or unlock tickets.', + }); + } + + const isClaimer = member.id === ticket.claimedBy; + const isAdmin = member.permissions.has(PermissionFlagsBits.Administrator); + + if (!isClaimer && !isAdmin) { + return await interaction.editReply({ + content: + 'Only the staff member who claimed this ticket or an administrator can lock/unlock it.', + }); + } + + const ticketMember = await guild.members + .fetch(ticket.ticketMemberID) + .catch(() => null); + if (!ticketMember) { + return await interaction.editReply({ + content: 'Ticket member not found in the guild.', + }); + } + + const currentPermissions = channel.permissionsFor(ticketMember); + if (!currentPermissions) { + return await interaction.editReply({ + content: + 'Could not determine the current permissions for the ticket member.', + }); + } + + const isLocked = !currentPermissions.has( + PermissionFlagsBits.SendMessages + ); + const newPermissions = { + [PermissionFlagsBits.SendMessages]: isLocked, + [PermissionFlagsBits.ViewChannel]: true, + }; + + await channel.permissionOverwrites.edit(ticketMember, newPermissions); + + const embedData = { + color: isLocked ? 'Green' : 'Orange', + title: isLocked ? 'Ticket Unlocked' : 'Ticket Locked', + description: `This ticket has been ${isLocked ? 'unlocked' : 'locked'} by ${member.user.tag}.`, + }; + + const statusEmbed = new EmbedBuilder(embedData); + + await Promise.all([ + interaction.editReply({ + content: `This ticket has been ${isLocked ? 'unlocked' : 'locked'}.`, + }), + channel.send({ embeds: [statusEmbed] }), + ]); + } catch (error) { + console.error('Error toggling ticket lock:', error); + await interaction + .editReply({ + content: + 'There was an error toggling the ticket lock. Please try again later.', + }) + .catch(console.error); + } + }, }; diff --git a/src/buttons/manage_members.js b/src/buttons/manage_members.js index 64fe43c..7451ce5 100644 --- a/src/buttons/manage_members.js +++ b/src/buttons/manage_members.js @@ -1,69 +1,69 @@ import { - EmbedBuilder, - ActionRowBuilder, - UserSelectMenuBuilder, + EmbedBuilder, + ActionRowBuilder, + UserSelectMenuBuilder, } from 'discord.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; export default { - customId: 'manage_members', - userPermissions: [], - botPermissions: [], + customId: 'manage_members', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - await interaction.deferReply({ ephemeral: true }); + run: async (client, interaction) => { + try { + await interaction.deferReply({ ephemeral: true }); - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return await interaction.editReply({ - content: checkResult.message, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return await interaction.editReply({ + content: checkResult.message, + }); + } - const selectMenu = new UserSelectMenuBuilder() - .setCustomId('members_manage') - .setPlaceholder('Select a user to manage access') - .setMinValues(1) - .setMaxValues(1); + const selectMenu = new UserSelectMenuBuilder() + .setCustomId('members_manage') + .setPlaceholder('Select a user to manage access') + .setMinValues(1) + .setMaxValues(1); - const actionRow = new ActionRowBuilder().addComponents(selectMenu); + const actionRow = new ActionRowBuilder().addComponents(selectMenu); - const embed = new EmbedBuilder() - .setTitle('Manage Channel Members') - .setDescription( - 'Select a user to grant or remove access to the channel.' - ) - .setColor('Blue') - .setTimestamp(); + const embed = new EmbedBuilder() + .setTitle('Manage Channel Members') + .setDescription( + 'Select a user to grant or remove access to the channel.' + ) + .setColor('Blue') + .setTimestamp(); - await interaction.editReply({ - embeds: [embed], - components: [actionRow], - }); - } catch (error) { - console.error('Error in manage_members interaction:', error); + await interaction.editReply({ + embeds: [embed], + components: [actionRow], + }); + } catch (error) { + console.error('Error in manage_members interaction:', error); - const errorMessage = - 'An error occurred while processing your request. Please try again later.'; + const errorMessage = + 'An error occurred while processing your request. Please try again later.'; - if (interaction.deferred) { - await interaction - .editReply({ content: errorMessage }) - .catch(console.error); - } else { - await interaction - .reply({ content: errorMessage, ephemeral: true }) - .catch(console.error); - } + if (interaction.deferred) { + await interaction + .editReply({ content: errorMessage }) + .catch(console.error); + } else { + await interaction + .reply({ content: errorMessage, ephemeral: true }) + .catch(console.error); } - }, + } + }, }; diff --git a/src/buttons/manage_roles.js b/src/buttons/manage_roles.js index 503a6b9..d108aae 100644 --- a/src/buttons/manage_roles.js +++ b/src/buttons/manage_roles.js @@ -1,86 +1,86 @@ /** @format */ import { - EmbedBuilder, - ActionRowBuilder, - RoleSelectMenuBuilder, + EmbedBuilder, + ActionRowBuilder, + RoleSelectMenuBuilder, } from 'discord.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; export default { - customId: 'manage_roles', - userPermissions: [], - botPermissions: [], + customId: 'manage_roles', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - // Perform comprehensive checks - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + run: async (client, interaction) => { + try { + // Perform comprehensive checks + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: - checkResult.message || - 'You do not have permission to manage roles in this channel.', - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: + checkResult.message || + 'You do not have permission to manage roles in this channel.', + ephemeral: true, + }); + } - const voiceChannel = interaction.member.voice.channel; + const voiceChannel = interaction.member.voice.channel; - if (!voiceChannel) { - return interaction.reply({ - content: 'You are not in a voice channel.', - ephemeral: true, - }); - } + if (!voiceChannel) { + return interaction.reply({ + content: 'You are not in a voice channel.', + ephemeral: true, + }); + } - // Create the role select menu - const selectMenu = new RoleSelectMenuBuilder() - .setCustomId('role_manage') - .setPlaceholder('Select a role to manage') - .setMinValues(1) - .setMaxValues(1); + // Create the role select menu + const selectMenu = new RoleSelectMenuBuilder() + .setCustomId('role_manage') + .setPlaceholder('Select a role to manage') + .setMinValues(1) + .setMaxValues(1); - // Create an action row and add the select menu to it - const actionRow = new ActionRowBuilder().addComponents(selectMenu); + // Create an action row and add the select menu to it + const actionRow = new ActionRowBuilder().addComponents(selectMenu); - // Create an embed for the interaction response - const embed = new EmbedBuilder() - .setTitle('Manage Channel Roles') - .setDescription( - 'Select a role to grant or remove access to the channel.' - ) - .setColor('Blue') - .addFields( - { name: 'Channel', value: voiceChannel.name, inline: true }, - { - name: 'Current Permissions', - value: 'Use the menu to view and modify role permissions.', - inline: true, - } - ) - .setFooter({ text: 'Select a role to manage its permissions' }); + // Create an embed for the interaction response + const embed = new EmbedBuilder() + .setTitle('Manage Channel Roles') + .setDescription( + 'Select a role to grant or remove access to the channel.' + ) + .setColor('Blue') + .addFields( + { name: 'Channel', value: voiceChannel.name, inline: true }, + { + name: 'Current Permissions', + value: 'Use the menu to view and modify role permissions.', + inline: true, + } + ) + .setFooter({ text: 'Select a role to manage its permissions' }); - // Reply to the interaction with the embed and action row - await interaction.reply({ - embeds: [embed], - components: [actionRow], - ephemeral: true, - }); - } catch (error) { - console.error('Error in manage_roles interaction:', error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); - } - }, + // Reply to the interaction with the embed and action row + await interaction.reply({ + embeds: [embed], + components: [actionRow], + ephemeral: true, + }); + } catch (error) { + console.error('Error in manage_roles interaction:', error); + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/buttons/manage_talk_access.js b/src/buttons/manage_talk_access.js index 4010a77..b99545a 100644 --- a/src/buttons/manage_talk_access.js +++ b/src/buttons/manage_talk_access.js @@ -1,57 +1,57 @@ /** @format */ import { - EmbedBuilder, - ActionRowBuilder, - UserSelectMenuBuilder, + EmbedBuilder, + ActionRowBuilder, + UserSelectMenuBuilder, } from 'discord.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; export default { - customId: 'manage_talk_access', - userPermissions: [], - botPermissions: [], + customId: 'manage_talk_access', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - // Perform comprehensive checks - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + run: async (client, interaction) => { + try { + // Perform comprehensive checks + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: checkResult.message, - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: checkResult.message, + ephemeral: true, + }); + } - // Create the user select menu - const selectMenu = new UserSelectMenuBuilder() - .setCustomId('manage_talk_select') - .setPlaceholder('Select a user to toggle talk access') - .setMinValues(1) - .setMaxValues(1); + // Create the user select menu + const selectMenu = new UserSelectMenuBuilder() + .setCustomId('manage_talk_select') + .setPlaceholder('Select a user to toggle talk access') + .setMinValues(1) + .setMaxValues(1); - const row = new ActionRowBuilder().addComponents(selectMenu); + const row = new ActionRowBuilder().addComponents(selectMenu); - // Send the menu - await interaction.reply({ - content: 'Please select a user to mute/unmute in this channel:', - components: [row], - ephemeral: true, - }); - } catch (error) { - console.error('Error in manage_talk_access button:', error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); - } - }, + // Send the menu + await interaction.reply({ + content: 'Please select a user to mute/unmute in this channel:', + components: [row], + ephemeral: true, + }); + } catch (error) { + console.error('Error in manage_talk_access button:', error); + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/buttons/rename_channel.js b/src/buttons/rename_channel.js index b667f9a..44446db 100644 --- a/src/buttons/rename_channel.js +++ b/src/buttons/rename_channel.js @@ -1,102 +1,102 @@ /** @format */ import { - PermissionsBitField, - ModalBuilder, - TextInputBuilder, - TextInputStyle, - ActionRowBuilder, + PermissionsBitField, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ActionRowBuilder, } from 'discord.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; import joinToSystemSchema from '../schemas/joinToSystemSchema.js'; export default { - customId: 'rename_channel', - userPermissions: [], - botPermissions: [PermissionsBitField.Flags.ManageChannels], + customId: 'rename_channel', + userPermissions: [], + botPermissions: [PermissionsBitField.Flags.ManageChannels], - run: async (client, interaction) => { - try { - // Perform comprehensive checks - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + run: async (client, interaction) => { + try { + // Perform comprehensive checks + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: - checkResult.message || - (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: + checkResult.message || + (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), + ephemeral: true, + }); + } - const voiceChannel = interaction.member.voice.channel; + const voiceChannel = interaction.member.voice.channel; - if (!voiceChannel) { - return interaction.reply({ - content: 'You are not in a voice channel.', - ephemeral: true, - }); - } + if (!voiceChannel) { + return interaction.reply({ + content: 'You are not in a voice channel.', + ephemeral: true, + }); + } - const modal = new ModalBuilder() - .setCustomId('rename_channel_modal') - .setTitle('Rename Voice Channel'); + const modal = new ModalBuilder() + .setCustomId('rename_channel_modal') + .setTitle('Rename Voice Channel'); - const nameInput = new TextInputBuilder() - .setCustomId('new_channel_name') - .setLabel('New channel name:') - .setPlaceholder('Enter new name for the channel') - .setStyle(TextInputStyle.Short) - .setMaxLength(100) - .setRequired(true); + const nameInput = new TextInputBuilder() + .setCustomId('new_channel_name') + .setLabel('New channel name:') + .setPlaceholder('Enter new name for the channel') + .setStyle(TextInputStyle.Short) + .setMaxLength(100) + .setRequired(true); - const actionRow = new ActionRowBuilder().addComponents(nameInput); - modal.addComponents(actionRow); + const actionRow = new ActionRowBuilder().addComponents(nameInput); + modal.addComponents(actionRow); - await interaction.showModal(modal); + await interaction.showModal(modal); - const filter = (i) => i.customId === 'rename_channel_modal'; - const modalSubmission = await interaction - .awaitModalSubmit({ filter, time: 60000 }) - .catch(() => null); + const filter = (i) => i.customId === 'rename_channel_modal'; + const modalSubmission = await interaction + .awaitModalSubmit({ filter, time: 60000 }) + .catch(() => null); - if (!modalSubmission) { - return interaction.followUp({ - content: - 'You took too long to provide a new name. The channel was not renamed.', - ephemeral: true, - }); - } + if (!modalSubmission) { + return interaction.followUp({ + content: + 'You took too long to provide a new name. The channel was not renamed.', + ephemeral: true, + }); + } - const newName = - modalSubmission.fields.getTextInputValue('new_channel_name'); + const newName = + modalSubmission.fields.getTextInputValue('new_channel_name'); - // Rename the channel - await voiceChannel.setName(newName); + // Rename the channel + await voiceChannel.setName(newName); - // Update the database - await joinToSystemSchema.updateOne( - { channelId: voiceChannel.id }, - { channelName: newName } - ); + // Update the database + await joinToSystemSchema.updateOne( + { channelId: voiceChannel.id }, + { channelName: newName } + ); - return modalSubmission.reply({ - content: `The voice channel has been renamed to "${newName}".`, - ephemeral: true, - }); - } catch (error) { - console.error(error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); - } - }, + return modalSubmission.reply({ + content: `The voice channel has been renamed to "${newName}".`, + ephemeral: true, + }); + } catch (error) { + console.error(error); + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/buttons/requestUserInfoBtn.js b/src/buttons/requestUserInfoBtn.js index 78f6249..c6b0394 100644 --- a/src/buttons/requestUserInfoBtn.js +++ b/src/buttons/requestUserInfoBtn.js @@ -6,138 +6,138 @@ import ticketSchema from '../schemas/ticketSchema.js'; import buttonPagination from '../utils/buttonPagination.js'; export default { - customId: 'requestUserInfoBtn', - userPermissions: [], - botPermissions: [], + customId: 'requestUserInfoBtn', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - const { guild, member, channel } = interaction; + run: async (client, interaction) => { + try { + const { guild, member, channel } = interaction; - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); - const ticketSetup = await ticketSetupSchema.findOne({ - guildID: guild.id, - }); - if (!ticketSetup) { - return await interaction.editReply({ - content: - 'Ticket system is not configured properly. Please contact an administrator.', - }); - } + const ticketSetup = await ticketSetupSchema.findOne({ + guildID: guild.id, + }); + if (!ticketSetup) { + return await interaction.editReply({ + content: + 'Ticket system is not configured properly. Please contact an administrator.', + }); + } - // Check if the member has the staff role - if (!member.roles.cache.has(ticketSetup.staffRoleID)) { - return await interaction.editReply({ - content: 'You do not have permission to request user info.', - }); - } + // Check if the member has the staff role + if (!member.roles.cache.has(ticketSetup.staffRoleID)) { + return await interaction.editReply({ + content: 'You do not have permission to request user info.', + }); + } - // Get the current ticket information - const currentTicket = await ticketSchema.findOne({ - guildID: guild.id, - ticketChannelID: channel.id, - closed: false, - }); + // Get the current ticket information + const currentTicket = await ticketSchema.findOne({ + guildID: guild.id, + ticketChannelID: channel.id, + closed: false, + }); - if (!currentTicket) { - return await interaction.editReply({ - content: 'This ticket is not valid or is closed.', - }); - } + if (!currentTicket) { + return await interaction.editReply({ + content: 'This ticket is not valid or is closed.', + }); + } - // Fetch all closed tickets for this user - const tickets = await ticketSchema - .find({ - guildID: guild.id, - ticketMemberID: currentTicket.ticketMemberID, - closed: true, - }) - .sort({ closedAt: -1 }); // Sort by closing date, newest first + // Fetch all closed tickets for this user + const tickets = await ticketSchema + .find({ + guildID: guild.id, + ticketMemberID: currentTicket.ticketMemberID, + closed: true, + }) + .sort({ closedAt: -1 }); // Sort by closing date, newest first - if (!tickets || tickets.length === 0) { - return await interaction.editReply({ - content: 'This user has no closed tickets.', - }); - } + if (!tickets || tickets.length === 0) { + return await interaction.editReply({ + content: 'This user has no closed tickets.', + }); + } - // Format tickets into pages - const pages = tickets.map((ticket) => { - const claimedBy = ticket.claimedBy - ? `<@${ticket.claimedBy}>` - : 'Unclaimed'; - const transcriptURL = `https://transcript.clienterr.com/api/transcript/${ticket.ticketChannelID}`; - const closeReason = ticket.closeReason || 'No reason provided'; + // Format tickets into pages + const pages = tickets.map((ticket) => { + const claimedBy = ticket.claimedBy + ? `<@${ticket.claimedBy}>` + : 'Unclaimed'; + const transcriptURL = `https://transcript.clienterr.com/api/transcript/${ticket.ticketChannelID}`; + const closeReason = ticket.closeReason || 'No reason provided'; - return new EmbedBuilder() - .setTitle(`Ticket ID: ${ticket.ticketChannelID}`) - .setURL(transcriptURL) - .addFields( - { - name: '📝 Subject', - value: ticket.subject || 'No subject provided', - inline: true, - }, - { - name: '🗒️ Description', - value: ticket.description || 'No description provided', - inline: true, - }, - { - name: '📅 Created At', - value: ticket.createdAt - ? `` - : 'Unknown', - inline: true, - }, - { - name: '⏳ Open Duration', - value: ticket.createdAt - ? `` - : 'Unknown', - inline: true, - }, - { - name: '🔒 Closed At', - value: ticket.closedAt - ? `` - : 'Not closed yet', - inline: true, - }, - { - name: '🔖 Claimed By', - value: claimedBy, - inline: true, - }, - { - name: '📝 Close Reason', - value: closeReason, - inline: true, - }, - { - name: '🧑‍💼 Opened By', - value: `<@${ticket.ticketMemberID}>`, - inline: true, - } - ); - }); + return new EmbedBuilder() + .setTitle(`Ticket ID: ${ticket.ticketChannelID}`) + .setURL(transcriptURL) + .addFields( + { + name: '📝 Subject', + value: ticket.subject || 'No subject provided', + inline: true, + }, + { + name: '🗒️ Description', + value: ticket.description || 'No description provided', + inline: true, + }, + { + name: '📅 Created At', + value: ticket.createdAt + ? `` + : 'Unknown', + inline: true, + }, + { + name: '⏳ Open Duration', + value: ticket.createdAt + ? `` + : 'Unknown', + inline: true, + }, + { + name: '🔒 Closed At', + value: ticket.closedAt + ? `` + : 'Not closed yet', + inline: true, + }, + { + name: '🔖 Claimed By', + value: claimedBy, + inline: true, + }, + { + name: '📝 Close Reason', + value: closeReason, + inline: true, + }, + { + name: '🧑‍💼 Opened By', + value: `<@${ticket.ticketMemberID}>`, + inline: true, + } + ); + }); - // Use buttonPagination to display the information - await buttonPagination(interaction, pages); - } catch (err) { - console.error('Error handling request user info button:', err); - if (!interaction.replied && !interaction.deferred) { - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); - } else { - await interaction.editReply({ - content: - 'An error occurred while processing your request. Please try again later.', - }); - } + // Use buttonPagination to display the information + await buttonPagination(interaction, pages); + } catch (err) { + console.error('Error handling request user info button:', err); + if (!interaction.replied && !interaction.deferred) { + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } else { + await interaction.editReply({ + content: + 'An error occurred while processing your request. Please try again later.', + }); } - }, + } + }, }; diff --git a/src/buttons/select-owner.js b/src/buttons/select-owner.js index e3c1954..bf96f15 100644 --- a/src/buttons/select-owner.js +++ b/src/buttons/select-owner.js @@ -1,57 +1,57 @@ /** @format */ import { - EmbedBuilder, - ActionRowBuilder, - UserSelectMenuBuilder, + EmbedBuilder, + ActionRowBuilder, + UserSelectMenuBuilder, } from 'discord.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; export default { - customId: 'select_owner', - userPermissions: [], - botPermissions: [], + customId: 'select_owner', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - // Perform comprehensive checks - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + run: async (client, interaction) => { + try { + // Perform comprehensive checks + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: checkResult.message, - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: checkResult.message, + ephemeral: true, + }); + } - // Create the user select menu - const selectMenu = new UserSelectMenuBuilder() - .setCustomId('owner_select') - .setPlaceholder('Select the new owner') - .setMinValues(1) - .setMaxValues(1); + // Create the user select menu + const selectMenu = new UserSelectMenuBuilder() + .setCustomId('owner_select') + .setPlaceholder('Select the new owner') + .setMinValues(1) + .setMaxValues(1); - const row = new ActionRowBuilder().addComponents(selectMenu); + const row = new ActionRowBuilder().addComponents(selectMenu); - // Send the menu - await interaction.reply({ - content: 'Please select the new owner for this channel:', - components: [row], - ephemeral: true, - }); - } catch (error) { - console.error('Error in select_owner button:', error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); - } - }, + // Send the menu + await interaction.reply({ + content: 'Please select the new owner for this channel:', + components: [row], + ephemeral: true, + }); + } catch (error) { + console.error('Error in select_owner button:', error); + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/buttons/set_limit.js b/src/buttons/set_limit.js index 25e2ac0..2237b47 100644 --- a/src/buttons/set_limit.js +++ b/src/buttons/set_limit.js @@ -1,61 +1,61 @@ /** @format */ import { - ModalBuilder, - ActionRowBuilder, - TextInputBuilder, - TextInputStyle, + ModalBuilder, + ActionRowBuilder, + TextInputBuilder, + TextInputStyle, } from 'discord.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; export default { - customId: 'set_limit', - userPermissions: [], - botPermissions: [], + customId: 'set_limit', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - // Perform comprehensive checks - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + run: async (client, interaction) => { + try { + // Perform comprehensive checks + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: checkResult.message, - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: checkResult.message, + ephemeral: true, + }); + } - // Create the modal - const modal = new ModalBuilder() - .setCustomId('set_limit_modal') - .setTitle('Set Voice Channel User Limit'); + // Create the modal + const modal = new ModalBuilder() + .setCustomId('set_limit_modal') + .setTitle('Set Voice Channel User Limit'); - // Create the text input field - const limitInput = new TextInputBuilder() - .setCustomId('user_limit') - .setLabel('Enter the user limit for the voice channel:') - .setPlaceholder('Enter a number') - .setStyle(TextInputStyle.Short) - .setRequired(true); + // Create the text input field + const limitInput = new TextInputBuilder() + .setCustomId('user_limit') + .setLabel('Enter the user limit for the voice channel:') + .setPlaceholder('Enter a number') + .setStyle(TextInputStyle.Short) + .setRequired(true); - // Add the input field to the modal - const actionRow = new ActionRowBuilder().addComponents(limitInput); - modal.addComponents(actionRow); + // Add the input field to the modal + const actionRow = new ActionRowBuilder().addComponents(limitInput); + modal.addComponents(actionRow); - // Show the modal to the user - await interaction.showModal(modal); - } catch (error) { - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); - } - }, + // Show the modal to the user + await interaction.showModal(modal); + } catch (error) { + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/buttons/supportTicketBtn.js b/src/buttons/supportTicketBtn.js index d74f713..d9d8e18 100644 --- a/src/buttons/supportTicketBtn.js +++ b/src/buttons/supportTicketBtn.js @@ -1,262 +1,262 @@ import { - ModalBuilder, - ActionRowBuilder, - TextInputBuilder, - TextInputStyle, - ChannelType, - EmbedBuilder, - PermissionFlagsBits, - ButtonBuilder, - ButtonStyle, + ModalBuilder, + ActionRowBuilder, + TextInputBuilder, + TextInputStyle, + ChannelType, + EmbedBuilder, + PermissionFlagsBits, + ButtonBuilder, + ButtonStyle, } from 'discord.js'; import ticketSetupSchema from '../schemas/ticketSetupSchema.js'; import ticketSchema from '../schemas/ticketSchema.js'; export default { - customId: 'createTicket', - userPermissions: [], - botPermissions: [ - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.ManageRoles, - ], + customId: 'createTicket', + userPermissions: [], + botPermissions: [ + PermissionFlagsBits.ManageChannels, + PermissionFlagsBits.ManageRoles, + ], - run: async (client, interaction) => { - try { - const { channel, guild, member } = interaction; + run: async (client, interaction) => { + try { + const { channel, guild, member } = interaction; - // Fetch ticket setup - const ticketSetup = await ticketSetupSchema.findOne({ - guildID: guild.id, - ticketChannelID: channel.id, - }); - - if (!ticketSetup) { - return await interaction.reply({ - content: - 'The ticket system has not been set up yet. Please contact an administrator to set it up.', - ephemeral: true, - }); - } + // Fetch ticket setup + const ticketSetup = await ticketSetupSchema.findOne({ + guildID: guild.id, + ticketChannelID: channel.id, + }); - // Check for existing open ticket - const existingTicket = await ticketSchema.findOne({ - guildID: guild.id, - ticketMemberID: member.id, - closed: false, - }); + if (!ticketSetup) { + return await interaction.reply({ + content: + 'The ticket system has not been set up yet. Please contact an administrator to set it up.', + ephemeral: true, + }); + } - if (existingTicket) { - return await interaction.reply({ - content: `You already have an open ticket! Please use <#${existingTicket.ticketChannelID}>.`, - ephemeral: true, - }); - } + // Check for existing open ticket + const existingTicket = await ticketSchema.findOne({ + guildID: guild.id, + ticketMemberID: member.id, + closed: false, + }); - // Handle modal ticket creation - if (ticketSetup.ticketType === 'modal') { - return await handleModalTicket(interaction); - } + if (existingTicket) { + return await interaction.reply({ + content: `You already have an open ticket! Please use <#${existingTicket.ticketChannelID}>.`, + ephemeral: true, + }); + } - // Handle button ticket creation - await handleButtonTicket(interaction, ticketSetup); - } catch (error) { - console.error('Error creating ticket:', error); - await interaction.reply({ - content: - 'There was an error creating your ticket. Please try again later.', - ephemeral: true, - }); + // Handle modal ticket creation + if (ticketSetup.ticketType === 'modal') { + return await handleModalTicket(interaction); } - }, + + // Handle button ticket creation + await handleButtonTicket(interaction, ticketSetup); + } catch (error) { + console.error('Error creating ticket:', error); + await interaction.reply({ + content: + 'There was an error creating your ticket. Please try again later.', + ephemeral: true, + }); + } + }, }; async function handleModalTicket(interaction) { - const ticketModal = new ModalBuilder() - .setTitle('Create a Support Ticket') - .setCustomId('ticketModal') - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setLabel('Ticket Subject') - .setCustomId('ticketSubject') - .setPlaceholder('Enter a subject for your ticket') - .setStyle(TextInputStyle.Short) - .setRequired(true) - .setMaxLength(100) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setLabel('Ticket Description') - .setCustomId('ticketDesc') - .setPlaceholder('Describe your issue in detail') - .setStyle(TextInputStyle.Paragraph) - .setRequired(true) - .setMaxLength(1000) - ) - ); + const ticketModal = new ModalBuilder() + .setTitle('Create a Support Ticket') + .setCustomId('ticketModal') + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Ticket Subject') + .setCustomId('ticketSubject') + .setPlaceholder('Enter a subject for your ticket') + .setStyle(TextInputStyle.Short) + .setRequired(true) + .setMaxLength(100) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel('Ticket Description') + .setCustomId('ticketDesc') + .setPlaceholder('Describe your issue in detail') + .setStyle(TextInputStyle.Paragraph) + .setRequired(true) + .setMaxLength(1000) + ) + ); - await interaction.showModal(ticketModal); + await interaction.showModal(ticketModal); } async function handleButtonTicket(interaction, ticketSetup) { - const { guild, member } = interaction; + const { guild, member } = interaction; - const category = await guild.channels.fetch(ticketSetup.categoryID); - if (!category) { - return await interaction.reply({ - content: - 'The ticket category no longer exists. Please contact an administrator.', - ephemeral: true, - }); - } + const category = await guild.channels.fetch(ticketSetup.categoryID); + if (!category) { + return await interaction.reply({ + content: + 'The ticket category no longer exists. Please contact an administrator.', + ephemeral: true, + }); + } - const staffRole = await guild.roles.fetch(ticketSetup.staffRoleID); - if (!staffRole) { - return await interaction.reply({ - content: - 'The staff role no longer exists. Please contact an administrator.', - ephemeral: true, - }); - } + const staffRole = await guild.roles.fetch(ticketSetup.staffRoleID); + if (!staffRole) { + return await interaction.reply({ + content: + 'The staff role no longer exists. Please contact an administrator.', + ephemeral: true, + }); + } - const ticketCount = await ticketSchema.countDocuments({ - guildID: guild.id, - }); + const ticketCount = await ticketSchema.countDocuments({ + guildID: guild.id, + }); - const ticketChannel = await createTicketChannel( - guild, - category, - member, - staffRole, - ticketCount - ); + const ticketChannel = await createTicketChannel( + guild, + category, + member, + staffRole, + ticketCount + ); - const ticketEmbed = createTicketEmbed(member, guild); - const ticketActions = createTicketActions(); + const ticketEmbed = createTicketEmbed(member, guild); + const ticketActions = createTicketActions(); - await ticketChannel.send({ - content: `${staffRole} - New ticket created by ${member}`, - embeds: [ticketEmbed], - components: [ticketActions], - }); + await ticketChannel.send({ + content: `${staffRole} - New ticket created by ${member}`, + embeds: [ticketEmbed], + components: [ticketActions], + }); - const newTicket = await createTicketDocument( - guild, - member, - ticketChannel, - interaction.channel - ); + const newTicket = await createTicketDocument( + guild, + member, + ticketChannel, + interaction.channel + ); - await interaction.reply({ - content: `Your ticket has been created: <#${ticketChannel.id}>`, - ephemeral: true, - }); + await interaction.reply({ + content: `Your ticket has been created: <#${ticketChannel.id}>`, + ephemeral: true, + }); - // Log ticket creation - if (ticketSetup.logChannelID) { - const logChannel = await guild.channels.fetch(ticketSetup.logChannelID); - if (logChannel) { - await logChannel.send( - `New ticket created by ${member.user.tag} (${member.id}) - <#${ticketChannel.id}>` - ); - } - } + // Log ticket creation + if (ticketSetup.logChannelID) { + const logChannel = await guild.channels.fetch(ticketSetup.logChannelID); + if (logChannel) { + await logChannel.send( + `New ticket created by ${member.user.tag} (${member.id}) - <#${ticketChannel.id}>` + ); + } + } } async function createTicketChannel( - guild, - category, - member, - staffRole, - ticketCount + guild, + category, + member, + staffRole, + ticketCount ) { - return await guild.channels.create({ - name: `ticket-${ticketCount + 1}-${member.user.username}`, - type: ChannelType.GuildText, - parent: category.id, - permissionOverwrites: [ - { - id: guild.id, - deny: [PermissionFlagsBits.ViewChannel], - }, - { - id: member.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.ReadMessageHistory, - ], - }, - { - id: staffRole.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.ReadMessageHistory, - ], - }, - ], - }); + return await guild.channels.create({ + name: `ticket-${ticketCount + 1}-${member.user.username}`, + type: ChannelType.GuildText, + parent: category.id, + permissionOverwrites: [ + { + id: guild.id, + deny: [PermissionFlagsBits.ViewChannel], + }, + { + id: member.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.ReadMessageHistory, + ], + }, + { + id: staffRole.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.ReadMessageHistory, + ], + }, + ], + }); } function createTicketEmbed(member, guild) { - return new EmbedBuilder() - .setColor('DarkNavy') - .setAuthor({ - name: member.user.username, - iconURL: member.user.displayAvatarURL(), - }) - .setTitle('New Support Ticket') - .setDescription( - 'A staff member will be with you shortly. Please explain your issue in as much detail as possible.' - ) - .addFields( - { name: 'Ticket Creator', value: member.toString(), inline: true }, - { - name: 'Created At', - value: new Date().toLocaleString(), - inline: true, - } - ) - .setFooter({ - text: `${guild.name} - Support Ticket`, - iconURL: guild.iconURL(), - }) - .setTimestamp(); + return new EmbedBuilder() + .setColor('DarkNavy') + .setAuthor({ + name: member.user.username, + iconURL: member.user.displayAvatarURL(), + }) + .setTitle('New Support Ticket') + .setDescription( + 'A staff member will be with you shortly. Please explain your issue in as much detail as possible.' + ) + .addFields( + { name: 'Ticket Creator', value: member.toString(), inline: true }, + { + name: 'Created At', + value: new Date().toLocaleString(), + inline: true, + } + ) + .setFooter({ + text: `${guild.name} - Support Ticket`, + iconURL: guild.iconURL(), + }) + .setTimestamp(); } function createTicketActions() { - return new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('closeTicket') - .setLabel('Close Ticket') - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setCustomId('claimTicket') - .setLabel('Claim Ticket') - .setStyle(ButtonStyle.Primary) - ); + return new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('closeTicket') + .setLabel('Close Ticket') + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId('claimTicket') + .setLabel('Claim Ticket') + .setStyle(ButtonStyle.Primary) + ); } async function createTicketDocument( - guild, - member, - ticketChannel, - parentChannel + guild, + member, + ticketChannel, + parentChannel ) { - const newTicket = new ticketSchema({ - guildID: guild.id, - ticketMemberID: member.id, - ticketChannelID: ticketChannel.id, - parentTicketChannelID: parentChannel.id, - closed: false, - membersAdded: [], - claimedBy: null, - status: 'open', - actionLog: [ - `Ticket created by ${member.user.tag} (${member.id}) at ${new Date().toISOString()}`, - ], - }); + const newTicket = new ticketSchema({ + guildID: guild.id, + ticketMemberID: member.id, + ticketChannelID: ticketChannel.id, + parentTicketChannelID: parentChannel.id, + closed: false, + membersAdded: [], + claimedBy: null, + status: 'open', + actionLog: [ + `Ticket created by ${member.user.tag} (${member.id}) at ${new Date().toISOString()}`, + ], + }); - return await newTicket.save(); + return await newTicket.save(); } diff --git a/src/buttons/toggle_lock.js b/src/buttons/toggle_lock.js index 7e46634..2d11a2c 100644 --- a/src/buttons/toggle_lock.js +++ b/src/buttons/toggle_lock.js @@ -4,82 +4,82 @@ import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwn import joinToSystemSchema from '../schemas/joinToSystemSchema.js'; export default { - customId: 'toggle_lock', - userPermissions: [], - botPermissions: [PermissionsBitField.Flags.ManageChannels], + customId: 'toggle_lock', + userPermissions: [], + botPermissions: [PermissionsBitField.Flags.ManageChannels], - run: async (client, interaction) => { - try { - // Perform comprehensive checks - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + run: async (client, interaction) => { + try { + // Perform comprehensive checks + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: - checkResult.message || - (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: + checkResult.message || + (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), + ephemeral: true, + }); + } - const voiceChannel = interaction.member.voice.channel; + const voiceChannel = interaction.member.voice.channel; - if (!voiceChannel) { - return interaction.reply({ - content: 'You are not in a voice channel.', - ephemeral: true, - }); - } + if (!voiceChannel) { + return interaction.reply({ + content: 'You are not in a voice channel.', + ephemeral: true, + }); + } - // Check if the channel is currently locked - const currentLockStatus = voiceChannel.permissionOverwrites.cache.some( - (overwrite) => - overwrite.id === interaction.guild.id && - !overwrite.allow.has(PermissionsBitField.Flags.Connect) - ); + // Check if the channel is currently locked + const currentLockStatus = voiceChannel.permissionOverwrites.cache.some( + (overwrite) => + overwrite.id === interaction.guild.id && + !overwrite.allow.has(PermissionsBitField.Flags.Connect) + ); - // Toggle lock status - if (currentLockStatus) { - await voiceChannel.permissionOverwrites.edit(interaction.guild.id, { - Connect: null, - }); - await joinToSystemSchema.updateOne( - { channelId: voiceChannel.id }, - { isLocked: false } - ); + // Toggle lock status + if (currentLockStatus) { + await voiceChannel.permissionOverwrites.edit(interaction.guild.id, { + Connect: null, + }); + await joinToSystemSchema.updateOne( + { channelId: voiceChannel.id }, + { isLocked: false } + ); - return interaction.reply({ - content: `The voice channel ${voiceChannel.name} has been unlocked.`, - ephemeral: true, - }); - } else { - await voiceChannel.permissionOverwrites.edit(interaction.guild.id, { - Connect: false, - }); - await joinToSystemSchema.updateOne( - { channelId: voiceChannel.id }, - { isLocked: true } - ); + return interaction.reply({ + content: `The voice channel ${voiceChannel.name} has been unlocked.`, + ephemeral: true, + }); + } else { + await voiceChannel.permissionOverwrites.edit(interaction.guild.id, { + Connect: false, + }); + await joinToSystemSchema.updateOne( + { channelId: voiceChannel.id }, + { isLocked: true } + ); - return interaction.reply({ - content: `The voice channel ${voiceChannel.name} has been locked.`, - ephemeral: true, - }); - } - } catch (error) { - console.error(error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); + return interaction.reply({ + content: `The voice channel ${voiceChannel.name} has been locked.`, + ephemeral: true, + }); } - }, + } catch (error) { + console.error(error); + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/commands/admin/bottestchannel.js b/src/commands/admin/bottestchannel.js index 9c68e12..d4863bb 100644 --- a/src/commands/admin/bottestchannel.js +++ b/src/commands/admin/bottestchannel.js @@ -1,88 +1,91 @@ import { - SlashCommandBuilder, - ChannelType, - PermissionFlagsBits, + SlashCommandBuilder, + ChannelType, + PermissionFlagsBits, } from 'discord.js'; +import config from '../../config/config.js'; export default { - data: new SlashCommandBuilder() - .setName('bottestchannel') - .setDescription( - 'Creates a bot-testing channel under a bot-development category for developers only' - ), - run: async (client, interaction) => { - const { guild, member } = interaction; - const developerUserIDs = ['DEVELOPER_ID']; // Replace with actual developer user IDs + data: new SlashCommandBuilder() + .setName('bottestchannel') + .setDescription( + 'Creates a bot-testing channel under a bot-development category for developers only' + ), + botPermissions: [PermissionFlagsBits.ManageChannels], + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: true, + category: 'Developer', + run: async (client, interaction) => { + const { guild, member } = interaction; - // Log the member's ID for debugging - console.log('Member ID:', member.id); + try { + let category = await guild.channels.cache.find( + (c) => + c.name === 'bot-development' && c.type === ChannelType.GuildCategory + ); - // Check if the member's ID is in the list of developer user IDs - if (!developerUserIDs.includes(member.id)) { - return interaction.reply({ - content: 'You do not have permission to use this command.', - ephemeral: true, - }); + if (!category) { + category = await guild.channels.create({ + name: 'bot-development', + type: ChannelType.GuildCategory, + permissionOverwrites: [ + { + id: guild.id, + deny: [PermissionFlagsBits.ViewChannel], + }, + { + id: member.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + ], + }, + ], + }); + } + const existingChannel = guild.channels.find( + (c) => c.name === 'bot-testing' && c.parentId === category.id + ); + if (existingChannel) { + return await interaction.reply({ + content: `A testing channe alredy alredy exis ${existingChannel}`, + ephemeral: true, + }); } - try { - // Create the bot-development category if it doesn't already exist - let category = guild.channels.cache.find( - (c) => - c.name === 'bot-development' && - c.type === ChannelType.GuildCategory - ); - if (!category) { - category = await guild.channels.create({ - name: 'bot-development', - type: ChannelType.GuildCategory, - permissionOverwrites: [ - { - id: guild.id, - deny: [PermissionFlagsBits.ViewChannel], - }, - { - id: member.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - ], - }, - ], - }); - } - - // Create the bot-testing channel under the bot-development category - const channel = await guild.channels.create({ - name: 'bot-testing', - type: ChannelType.GuildText, - parent: category.id, - permissionOverwrites: [ - { - id: guild.id, - deny: [PermissionFlagsBits.ViewChannel], - }, - { - id: member.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - ], - }, + // Create the bot-testing channel under the bot-development category + const channel = await guild.channels.create({ + name: 'bot-testing', + type: ChannelType.GuildText, + parent: category.id, + permissionOverwrites: [ + { + id: guild.id, + deny: [PermissionFlagsBits.ViewChannel], + }, + { + id: member.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, ], - }); + }, + ], + }); - await interaction.reply({ - content: `Channel ${channel} created successfully under the ${category.name} category for developers.`, - ephemeral: true, - }); - } catch (error) { - console.error('Error creating channel:', error); - await interaction.reply({ - content: - 'There was an error creating the channel. Please try again later.', - ephemeral: true, - }); - } - }, + await interaction.reply({ + content: `Channel ${channel} created successfully under the ${category.name} category for developers.`, + ephemeral: true, + }); + } catch (error) { + console.error('Error creating channel:', error); + await interaction.reply({ + content: + 'There was an error creating the channel. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/commands/admin/disableping.js b/src/commands/admin/disableping.js deleted file mode 100644 index 6929ccf..0000000 --- a/src/commands/admin/disableping.js +++ /dev/null @@ -1,97 +0,0 @@ -import { SlashCommandBuilder, PermissionsBitField } from 'discord.js'; - -const noPingAdmins = new Set(); -const warningCooldowns = new Map(); - -export default { - data: new SlashCommandBuilder() - .setName('disableping') - .setDescription( - 'Enable or disable the no-ping feature for administrators.' - ) - .addStringOption((option) => - option - .setName('action') - .setDescription('Choose enable or disable') - .setRequired(true) - .addChoices( - { name: 'enable', value: 'enable' }, - { name: 'disable', value: 'disable' } - ) - ) - .toJSON(), - userPermissions: [PermissionsBitField.Flags.ADMINISTRATOR], - botPermissions: [], - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'Admin', - - run: async (client, interaction) => { - try { - const action = interaction.options.getString('action'); - - if (action === 'enable') { - noPingAdmins.add(interaction.user.id); - await interaction.reply({ - content: - 'No-ping feature enabled. Users will not be able to mention you.', - ephemeral: true, - }); - } else if (action === 'disable') { - noPingAdmins.delete(interaction.user.id); - await interaction.reply({ - content: 'No-ping feature disabled. Users can mention you now.', - ephemeral: true, - }); - } - } catch (error) { - console.error('Error in disableping command:', error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); - } - }, - - checkMentions: async (client, message) => { - if (message.mentions.users.size > 0) { - message.mentions.users.forEach(async (user) => { - if (noPingAdmins.has(user.id)) { - try { - const member = await message.guild.members.fetch( - message.author.id - ); - if ( - !member.permissions.has( - PermissionsBitField.Flags.ADMINISTRATOR - ) - ) { - const now = Date.now(); - const userCooldown = warningCooldowns.get(user.id); - - if (!userCooldown || now - userCooldown > 10000) { - await message.delete(); - await message.author.send( - `Please avoid mentioning admins in server: ${message.guild.name}.` - ); - - warningCooldowns.set(user.id, now); - } - } - } catch (error) { - console.error('Error fetching member or sending DM:', error); - if (error.code !== 50007) { - // 50007: Cannot send messages to this user - await message.channel.send( - `Unable to send a warning message to ${message.author.username} because they have direct messages disabled.` - ); - } - } - } - }); - } - }, -}; diff --git a/src/commands/admin/jointoCreate.js b/src/commands/admin/jointoCreate.js index 10c6617..94b73d8 100644 --- a/src/commands/admin/jointoCreate.js +++ b/src/commands/admin/jointoCreate.js @@ -1,246 +1,240 @@ import { - SlashCommandBuilder, - ChannelType, - PermissionFlagsBits, - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, + SlashCommandBuilder, + ChannelType, + PermissionFlagsBits, + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, } from 'discord.js'; import JoinToSystem from '../../schemas/joinToSystemSetup.js'; export default { - data: new SlashCommandBuilder() - .setName('jointosystem') - .setDescription('Manage the join-to-create system in your server.') - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) - .addSubcommand((subcommand) => - subcommand - .setName('setup') + data: new SlashCommandBuilder() + .setName('jointosystem') + .setDescription('Manage the join-to-create system in your server.') + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) + .addSubcommand((subcommand) => + subcommand + .setName('setup') + .setDescription( + 'Setup or update the join-to-create system in your server.' + ) + .addChannelOption((option) => + option + .setName('join-channel') .setDescription( - 'Setup or update the join-to-create system in your server.' + 'The voice channel where users will join to create new channels.' ) - .addChannelOption((option) => - option - .setName('join-channel') - .setDescription( - 'The voice channel where users will join to create new channels.' - ) - .setRequired(true) - .addChannelTypes(ChannelType.GuildVoice) - ) - .addChannelOption((option) => - option - .setName('control-channel') - .setDescription('The text channel for control messages.') - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) - ) - .addChannelOption((option) => - option - .setName('category') - .setDescription( - 'The category where new channels will be created.' - ) - .setRequired(true) - .addChannelTypes(ChannelType.GuildCategory) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('remove') - .setDescription( - 'Remove the join-to-create system setup for the guild.' - ) - ) - .toJSON(), - - userPermissions: [PermissionFlagsBits.Administrator], - botPermissions: [ - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.ManageRoles, - ], - category: 'Admin', - - run: async (client, interaction) => { - const subcommand = interaction.options.getSubcommand(); - - switch (subcommand) { - case 'setup': - await handleSetup(interaction); - break; - case 'remove': - await handleRemove(interaction); - break; - } - }, + .setRequired(true) + .addChannelTypes(ChannelType.GuildVoice) + ) + .addChannelOption((option) => + option + .setName('control-channel') + .setDescription('The text channel for control messages.') + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ) + .addChannelOption((option) => + option + .setName('category') + .setDescription('The category where new channels will be created.') + .setRequired(true) + .addChannelTypes(ChannelType.GuildCategory) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Remove the join-to-create system setup for the guild.') + ) + .toJSON(), + + userPermissions: [PermissionFlagsBits.Administrator], + botPermissions: [ + PermissionFlagsBits.ManageChannels, + PermissionFlagsBits.ManageRoles, + ], + category: 'Admin', + + run: async (client, interaction) => { + const subcommand = interaction.options.getSubcommand(); + + switch (subcommand) { + case 'setup': + await handleSetup(interaction); + break; + case 'remove': + await handleRemove(interaction); + break; + } + }, }; async function handleSetup(interaction) { - try { - const { guild, options } = interaction; - - const joinChannel = options.getChannel('join-channel'); - const controlChannel = options.getChannel('control-channel'); - const category = options.getChannel('category'); - - await interaction.deferReply({ ephemeral: true }); - - // Validate permissions - if ( - !controlChannel - .permissionsFor(guild.members.me) - .has(PermissionFlagsBits.SendMessages) - ) { - return await interaction.editReply( - "I don't have permission to send messages in the specified control channel." - ); - } - if ( - !category - .permissionsFor(guild.members.me) - .has(PermissionFlagsBits.ManageChannels) - ) { - return await interaction.editReply( - "I don't have permission to manage channels in the specified category." - ); - } - - const joinToCreateEmbed = new EmbedBuilder() - .setTitle('Join-to-Create System') - .setDescription( - 'Join the voice channel to create a new voice channel.' - ) - .setColor('Blue') - .setFooter({ text: 'Join-to-Create' }) - .setTimestamp(); - - const joinToSystemSetupEmbed = new EmbedBuilder() - .setTitle('Join-to-Create System Setup') - .setColor('Green') - .setDescription( - 'Join-to-Create system setup complete with the following settings:' - ) - .addFields( - { name: 'Join Channel', value: `${joinChannel}`, inline: true }, - { - name: 'Control Channel', - value: `${controlChannel}`, - inline: true, - }, - { name: 'Category', value: `${category}`, inline: true } - ) - .setTimestamp(); - - const actionRow1 = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('select_owner') - .setLabel('Owner') - .setStyle(ButtonStyle.Primary) - .setEmoji('👑'), - new ButtonBuilder() - .setCustomId('manage_members') - .setLabel('Members') - .setStyle(ButtonStyle.Primary) - .setEmoji('👥'), - new ButtonBuilder() - .setCustomId('set_limit') - .setLabel('Set Limit') - .setStyle(ButtonStyle.Primary) - .setEmoji('⚙️'), - new ButtonBuilder() - .setCustomId('toggle_lock') - .setLabel('Lock') - .setStyle(ButtonStyle.Primary) - .setEmoji('🔒'), - new ButtonBuilder() - .setCustomId('rename_channel') - .setLabel('Rename') - .setStyle(ButtonStyle.Primary) - .setEmoji('✏️') + try { + const { guild, options } = interaction; + + const joinChannel = options.getChannel('join-channel'); + const controlChannel = options.getChannel('control-channel'); + const category = options.getChannel('category'); + + await interaction.deferReply({ ephemeral: true }); + + // Validate permissions + if ( + !controlChannel + .permissionsFor(guild.members.me) + .has(PermissionFlagsBits.SendMessages) + ) { + return await interaction.editReply( + "I don't have permission to send messages in the specified control channel." ); - - const actionRow2 = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('manage_roles') - .setLabel('Roles') - .setStyle(ButtonStyle.Primary) - .setEmoji('👥'), - new ButtonBuilder() - .setCustomId('kick_user') - .setLabel('Kick') - .setStyle(ButtonStyle.Primary) - .setEmoji('🛴'), - new ButtonBuilder() - .setCustomId('manage_talk_access') - .setLabel('Talk Access') - .setStyle(ButtonStyle.Primary) - .setEmoji('🎙️') + } + if ( + !category + .permissionsFor(guild.members.me) + .has(PermissionFlagsBits.ManageChannels) + ) { + return await interaction.editReply( + "I don't have permission to manage channels in the specified category." ); - - let setup = await JoinToSystem.findOne({ guildId: guild.id }); - - if (setup) { - setup.joinToCreateChannelId = joinChannel.id; - setup.controlChannelId = controlChannel.id; - setup.categoryId = category.id; - } else { - setup = new JoinToSystem({ - guildId: guild.id, - joinToCreateChannelId: joinChannel.id, - controlChannelId: controlChannel.id, - categoryId: category.id, - }); - } - - await setup.save(); - - await controlChannel.send({ - embeds: [joinToCreateEmbed], - components: [actionRow1, actionRow2], - }); - - await interaction.editReply({ - content: 'Join-to-create system setup successful!', - embeds: [joinToSystemSetupEmbed], - }); - } catch (error) { - console.error('Error during join-to-create setup:', error); - await interaction.editReply({ - content: - 'There was an error during the join-to-create setup. Please check my permissions and try again.', - ephemeral: true, + } + + const joinToCreateEmbed = new EmbedBuilder() + .setTitle('Join-to-Create System') + .setDescription('Join the voice channel to create a new voice channel.') + .setColor('Blue') + .setFooter({ text: 'Join-to-Create' }) + .setTimestamp(); + + const joinToSystemSetupEmbed = new EmbedBuilder() + .setTitle('Join-to-Create System Setup') + .setColor('Green') + .setDescription( + 'Join-to-Create system setup complete with the following settings:' + ) + .addFields( + { name: 'Join Channel', value: `${joinChannel}`, inline: true }, + { + name: 'Control Channel', + value: `${controlChannel}`, + inline: true, + }, + { name: 'Category', value: `${category}`, inline: true } + ) + .setTimestamp(); + + const actionRow1 = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('select_owner') + .setLabel('Owner') + .setStyle(ButtonStyle.Primary) + .setEmoji('👑'), + new ButtonBuilder() + .setCustomId('manage_members') + .setLabel('Members') + .setStyle(ButtonStyle.Primary) + .setEmoji('👥'), + new ButtonBuilder() + .setCustomId('set_limit') + .setLabel('Set Limit') + .setStyle(ButtonStyle.Primary) + .setEmoji('⚙️'), + new ButtonBuilder() + .setCustomId('toggle_lock') + .setLabel('Lock') + .setStyle(ButtonStyle.Primary) + .setEmoji('🔒'), + new ButtonBuilder() + .setCustomId('rename_channel') + .setLabel('Rename') + .setStyle(ButtonStyle.Primary) + .setEmoji('✏️') + ); + + const actionRow2 = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('manage_roles') + .setLabel('Roles') + .setStyle(ButtonStyle.Primary) + .setEmoji('👥'), + new ButtonBuilder() + .setCustomId('kick_user') + .setLabel('Kick') + .setStyle(ButtonStyle.Primary) + .setEmoji('🛴'), + new ButtonBuilder() + .setCustomId('manage_talk_access') + .setLabel('Talk Access') + .setStyle(ButtonStyle.Primary) + .setEmoji('🎙️') + ); + + let setup = await JoinToSystem.findOne({ guildId: guild.id }); + + if (setup) { + setup.joinToCreateChannelId = joinChannel.id; + setup.controlChannelId = controlChannel.id; + setup.categoryId = category.id; + } else { + setup = new JoinToSystem({ + guildId: guild.id, + joinToCreateChannelId: joinChannel.id, + controlChannelId: controlChannel.id, + categoryId: category.id, }); - } + } + + await setup.save(); + + await controlChannel.send({ + embeds: [joinToCreateEmbed], + components: [actionRow1, actionRow2], + }); + + await interaction.editReply({ + content: 'Join-to-create system setup successful!', + embeds: [joinToSystemSetupEmbed], + }); + } catch (error) { + console.error('Error during join-to-create setup:', error); + await interaction.editReply({ + content: + 'There was an error during the join-to-create setup. Please check my permissions and try again.', + ephemeral: true, + }); + } } async function handleRemove(interaction) { - try { - const { guild } = interaction; - - await interaction.deferReply({ ephemeral: true }); + try { + const { guild } = interaction; - const setup = await JoinToSystem.findOneAndDelete({ - guildId: guild.id, - }); + await interaction.deferReply({ ephemeral: true }); - if (!setup) { - return await interaction.editReply({ - content: 'No join-to-create setup found for this guild.', - ephemeral: true, - }); - } + const setup = await JoinToSystem.findOneAndDelete({ + guildId: guild.id, + }); - await interaction.editReply({ - content: 'Join-to-create system setup has been removed for the guild.', - ephemeral: true, - }); - } catch (error) { - console.error('Error during join-to-create removal:', error); - await interaction.editReply({ - content: - 'There was an error during the removal of the join-to-create setup. Please try again later.', - ephemeral: true, + if (!setup) { + return await interaction.editReply({ + content: 'No join-to-create setup found for this guild.', + ephemeral: true, }); - } + } + + await interaction.editReply({ + content: 'Join-to-create system setup has been removed for the guild.', + ephemeral: true, + }); + } catch (error) { + console.error('Error during join-to-create removal:', error); + await interaction.editReply({ + content: + 'There was an error during the removal of the join-to-create setup. Please try again later.', + ephemeral: true, + }); + } } diff --git a/src/commands/admin/purge.js b/src/commands/admin/purge.js index e9165d5..1f113c7 100644 --- a/src/commands/admin/purge.js +++ b/src/commands/admin/purge.js @@ -1,98 +1,98 @@ import { - SlashCommandBuilder, - PermissionsBitField, - EmbedBuilder, + SlashCommandBuilder, + PermissionsBitField, + EmbedBuilder, } from 'discord.js'; export default { - data: new SlashCommandBuilder() - .setName('purge') - .setDescription('Delete a specified number of messages.') - .addIntegerOption((option) => - option - .setName('amount') - .setDescription('Number of messages to delete (1-100)') - .setRequired(true) - .setMinValue(1) - .setMaxValue(100) - ) - .addUserOption((option) => - option - .setName('user') - .setDescription('Only delete messages from this user') - .setRequired(false) - ) - .toJSON(), - userPermissions: [PermissionsBitField.Flags.MANAGE_MESSAGES], - botPermissions: [PermissionsBitField.Flags.MANAGE_MESSAGES], - cooldown: 5, - nwfwMode: false, - testMode: false, - devOnly: false, - category: 'Admin', - prefix: true, + data: new SlashCommandBuilder() + .setName('purge') + .setDescription('Delete a specified number of messages.') + .addIntegerOption((option) => + option + .setName('amount') + .setDescription('Number of messages to delete (1-100)') + .setRequired(true) + .setMinValue(1) + .setMaxValue(100) + ) + .addUserOption((option) => + option + .setName('user') + .setDescription('Only delete messages from this user') + .setRequired(false) + ) + .toJSON(), + userPermissions: [PermissionsBitField.Flags.MANAGE_MESSAGES], + botPermissions: [PermissionsBitField.Flags.MANAGE_MESSAGES], + cooldown: 5, + nwfwMode: false, + testMode: false, + devOnly: false, + category: 'Admin', + prefix: true, - run: async (client, interaction) => { - const amount = interaction.options.getInteger('amount'); - const user = interaction.options.getUser('user'); + run: async (client, interaction) => { + const amount = interaction.options.getInteger('amount'); + const user = interaction.options.getUser('user'); - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); - try { - let messages; - if (user) { - messages = await interaction.channel.messages.fetch({ limit: 100 }); - messages = messages - .filter((m) => m.author.id === user.id) - .first(amount); - } else { - messages = await interaction.channel.messages.fetch({ - limit: amount, - }); - } + try { + let messages; + if (user) { + messages = await interaction.channel.messages.fetch({ limit: 100 }); + messages = messages + .filter((m) => m.author.id === user.id) + .first(amount); + } else { + messages = await interaction.channel.messages.fetch({ + limit: amount, + }); + } - const deletedMessages = await interaction.channel.bulkDelete( - messages, - true - ); + const deletedMessages = await interaction.channel.bulkDelete( + messages, + true + ); - const embed = new EmbedBuilder() - .setColor('#00FF00') - .setTitle('Purge Command Executed') - .setDescription( - `Successfully deleted ${deletedMessages.size} messages.` - ) - .addFields( - { - name: 'Channel', - value: interaction.channel.name, - inline: true, - }, - { name: 'Executor', value: interaction.user.tag, inline: true }, - { - name: 'Target User', - value: user ? user.tag : 'All Users', - inline: true, - } - ) - .setTimestamp(); + const embed = new EmbedBuilder() + .setColor('#00FF00') + .setTitle('Purge Command Executed') + .setDescription( + `Successfully deleted ${deletedMessages.size} messages.` + ) + .addFields( + { + name: 'Channel', + value: interaction.channel.name, + inline: true, + }, + { name: 'Executor', value: interaction.user.tag, inline: true }, + { + name: 'Target User', + value: user ? user.tag : 'All Users', + inline: true, + } + ) + .setTimestamp(); - await interaction.editReply({ embeds: [embed] }); + await interaction.editReply({ embeds: [embed] }); - // Log the action - const logChannel = interaction.guild.channels.cache.find( - (channel) => channel.name === 'mod-logs' - ); - if (logChannel) { - await logChannel.send({ embeds: [embed] }); - } - } catch (error) { - console.error('Error in purge command:', error); - await interaction.editReply({ - content: - 'There was an error trying to purge messages. Make sure the messages are not older than 14 days.', - ephemeral: true, - }); + // Log the action + const logChannel = interaction.guild.channels.cache.find( + (channel) => channel.name === 'mod-logs' + ); + if (logChannel) { + await logChannel.send({ embeds: [embed] }); } - }, + } catch (error) { + console.error('Error in purge command:', error); + await interaction.editReply({ + content: + 'There was an error trying to purge messages. Make sure the messages are not older than 14 days.', + ephemeral: true, + }); + } + }, }; diff --git a/src/commands/admin/welcome.js b/src/commands/admin/welcome.js index 0480762..7e2298a 100644 --- a/src/commands/admin/welcome.js +++ b/src/commands/admin/welcome.js @@ -1,197 +1,194 @@ import { - SlashCommandBuilder, - PermissionsBitField, - ChannelType, - EmbedBuilder, + SlashCommandBuilder, + PermissionsBitField, + ChannelType, + EmbedBuilder, } from 'discord.js'; import welcomeSchema from '../../schemas/welcomeSchema.js'; export default { - data: new SlashCommandBuilder() - .setName('welcome') - .setDescription('Manage the welcome feature.') - .addSubcommand((subcommand) => - subcommand - .setName('enable') - .setDescription('Enable the welcome feature and set options.') - .addChannelOption((option) => - option - .setName('channel') - .setDescription('The welcome channel') - .addChannelTypes(ChannelType.GuildText) - .setRequired(true) - ) - .addRoleOption((option) => - option - .setName('joinrole') - .setDescription('The join role for new members') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('message') - .setDescription('The welcome message') - .setRequired(true) + data: new SlashCommandBuilder() + .setName('welcome') + .setDescription('Manage the welcome feature.') + .addSubcommand((subcommand) => + subcommand + .setName('enable') + .setDescription('Enable the welcome feature and set options.') + .addChannelOption((option) => + option + .setName('channel') + .setDescription('The welcome channel') + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ) + .addRoleOption((option) => + option + .setName('joinrole') + .setDescription('The join role for new members') + .setRequired(true) + ) + .addStringOption((option) => + option + .setName('message') + .setDescription('The welcome message') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('disable') + .setDescription('Disable the welcome feature.') + ) + .addSubcommand((subcommand) => + subcommand + .setName('status') + .setDescription('Check the current welcome feature status.') + ) + .addSubcommand((subcommand) => + subcommand.setName('test').setDescription('Test the welcome message.') + ) + .toJSON(), + userPermissions: [PermissionsBitField.ADMINISTRATOR], + botPermissions: [PermissionsBitField.ManageRoles], + cooldown: 5, + category: 'Admin', + + run: async (client, interaction) => { + await interaction.deferReply({ ephemeral: true }); + const subcommand = interaction.options.getSubcommand(); + const guildId = interaction.guild.id; + + try { + let welcomeSettings = await welcomeSchema.findOne({ guildId }); + if (!welcomeSettings) { + welcomeSettings = new welcomeSchema({ guildId }); + } + + switch (subcommand) { + case 'enable': { + const channel = interaction.options.getChannel('channel'); + const role = interaction.options.getRole('joinrole'); + const message = interaction.options.getString('message'); + + if ( + !channel + .permissionsFor(interaction.guild.members.me) + .has(PermissionsBitField.Flags.SendMessages) + ) { + return interaction.editReply( + "I don't have permission to send messages in the specified channel." + ); + } + + if ( + role.position >= interaction.guild.members.me.roles.highest.position + ) { + return interaction.editReply( + "The specified role is higher than or equal to my highest role. I can't assign it." + ); + } + + welcomeSettings.enabled = true; + welcomeSettings.channelId = channel.id; + welcomeSettings.roleId = role.id; + welcomeSettings.message = message; + + await welcomeSettings.save(); + + const embed = new EmbedBuilder() + .setColor('#00FF00') + .setTitle('Welcome Feature Enabled') + .addFields( + { + name: 'Channel', + value: channel.toString(), + inline: true, + }, + { name: 'Role', value: role.toString(), inline: true }, + { name: 'Message', value: message } ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('disable') - .setDescription('Disable the welcome feature.') - ) - .addSubcommand((subcommand) => - subcommand - .setName('status') - .setDescription('Check the current welcome feature status.') - ) - .addSubcommand((subcommand) => - subcommand.setName('test').setDescription('Test the welcome message.') - ) - .toJSON(), - userPermissions: [PermissionsBitField.ADMINISTRATOR], - botPermissions: [PermissionsBitField.ManageRoles], - cooldown: 5, - category: 'Admin', - - run: async (client, interaction) => { - await interaction.deferReply({ ephemeral: true }); - const subcommand = interaction.options.getSubcommand(); - const guildId = interaction.guild.id; - - try { - let welcomeSettings = await welcomeSchema.findOne({ guildId }); - if (!welcomeSettings) { - welcomeSettings = new welcomeSchema({ guildId }); - } - - switch (subcommand) { - case 'enable': { - const channel = interaction.options.getChannel('channel'); - const role = interaction.options.getRole('joinrole'); - const message = interaction.options.getString('message'); - - if ( - !channel - .permissionsFor(interaction.guild.members.me) - .has(PermissionsBitField.Flags.SendMessages) - ) { - return interaction.editReply( - "I don't have permission to send messages in the specified channel." - ); - } - - if ( - role.position >= - interaction.guild.members.me.roles.highest.position - ) { - return interaction.editReply( - "The specified role is higher than or equal to my highest role. I can't assign it." - ); - } - - welcomeSettings.enabled = true; - welcomeSettings.channelId = channel.id; - welcomeSettings.roleId = role.id; - welcomeSettings.message = message; - - await welcomeSettings.save(); - - const embed = new EmbedBuilder() - .setColor('#00FF00') - .setTitle('Welcome Feature Enabled') - .addFields( - { - name: 'Channel', - value: channel.toString(), - inline: true, - }, - { name: 'Role', value: role.toString(), inline: true }, - { name: 'Message', value: message } - ) - .setTimestamp(); - - return interaction.editReply({ embeds: [embed] }); - } - - case 'disable': - welcomeSettings.enabled = false; - await welcomeSettings.save(); - return interaction.editReply( - 'Welcome feature has been disabled.' - ); - - case 'status': { - const embed = new EmbedBuilder() - .setColor(welcomeSettings.enabled ? '#00FF00' : '#FF0000') - .setTitle('Welcome Feature Status') - .addFields({ - name: 'Status', - value: welcomeSettings.enabled ? 'Enabled' : 'Disabled', - }); - - if (welcomeSettings.enabled) { - const channel = interaction.guild.channels.cache.get( - welcomeSettings.channelId - ); - const role = interaction.guild.roles.cache.get( - welcomeSettings.roleId - ); - - embed.addFields( - { - name: 'Channel', - value: channel ? channel.toString() : 'Not set', - inline: true, - }, - { - name: 'Role', - value: role ? role.toString() : 'Not set', - inline: true, - }, - { - name: 'Message', - value: welcomeSettings.message || 'Not set', - } - ); - } - - return interaction.editReply({ embeds: [embed] }); - } - - case 'test': { - if (!welcomeSettings.enabled) { - return interaction.editReply( - 'Welcome feature is not enabled. Enable it first to test.' - ); - } - - const channel = interaction.guild.channels.cache.get( - welcomeSettings.channelId - ); - if (!channel) { - return interaction.editReply( - 'The configured welcome channel no longer exists.' - ); - } - - const testMessage = welcomeSettings.message.replace( - '{user}', - interaction.user.toString() - ); - await channel.send(testMessage); - return interaction.editReply( - 'Test welcome message sent to the configured channel.' - ); - } - - default: - return interaction.editReply('Invalid subcommand.'); - } - } catch (error) { - console.error('Error in welcome command:', error); - return interaction.editReply( - 'There was an error processing your request. Please try again later.' - ); + .setTimestamp(); + + return interaction.editReply({ embeds: [embed] }); + } + + case 'disable': + welcomeSettings.enabled = false; + await welcomeSettings.save(); + return interaction.editReply('Welcome feature has been disabled.'); + + case 'status': { + const embed = new EmbedBuilder() + .setColor(welcomeSettings.enabled ? '#00FF00' : '#FF0000') + .setTitle('Welcome Feature Status') + .addFields({ + name: 'Status', + value: welcomeSettings.enabled ? 'Enabled' : 'Disabled', + }); + + if (welcomeSettings.enabled) { + const channel = interaction.guild.channels.cache.get( + welcomeSettings.channelId + ); + const role = interaction.guild.roles.cache.get( + welcomeSettings.roleId + ); + + embed.addFields( + { + name: 'Channel', + value: channel ? channel.toString() : 'Not set', + inline: true, + }, + { + name: 'Role', + value: role ? role.toString() : 'Not set', + inline: true, + }, + { + name: 'Message', + value: welcomeSettings.message || 'Not set', + } + ); + } + + return interaction.editReply({ embeds: [embed] }); + } + + case 'test': { + if (!welcomeSettings.enabled) { + return interaction.editReply( + 'Welcome feature is not enabled. Enable it first to test.' + ); + } + + const channel = interaction.guild.channels.cache.get( + welcomeSettings.channelId + ); + if (!channel) { + return interaction.editReply( + 'The configured welcome channel no longer exists.' + ); + } + + const testMessage = welcomeSettings.message.replace( + '{user}', + interaction.user.toString() + ); + await channel.send(testMessage); + return interaction.editReply( + 'Test welcome message sent to the configured channel.' + ); + } + + default: + return interaction.editReply('Invalid subcommand.'); } - }, + } catch (error) { + console.error('Error in welcome command:', error); + return interaction.editReply( + 'There was an error processing your request. Please try again later.' + ); + } + }, }; diff --git a/src/commands/developer/createitem.js b/src/commands/developer/createitem.js index 78aec1b..2e14dae 100644 --- a/src/commands/developer/createitem.js +++ b/src/commands/developer/createitem.js @@ -5,148 +5,148 @@ import { Item } from '../../schemas/economy.js'; import mconfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('createitem') - .setDescription('Create a new item for the economy system.') - .addStringOption((option) => - option - .setName('itemid') - .setDescription('The unique ID for the item.') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('name') - .setDescription('The name of the item.') - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName('price') - .setDescription('The price of the item.') - .setRequired(true) - .setMinValue(1) - ) - .addStringOption((option) => - option - .setName('description') - .setDescription('A description of the item.') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('category') - .setDescription('The category of the item.') - .setRequired(false) - ) - .addStringOption((option) => - option - .setName('emoji') - .setDescription('The emoji for the item.') - .setRequired(false) - ) - .toJSON(), - userPermissions: [], - botPermissions: [], - category: 'Devloper', + data: new SlashCommandBuilder() + .setName('createitem') + .setDescription('Create a new item for the economy system.') + .addStringOption((option) => + option + .setName('itemid') + .setDescription('The unique ID for the item.') + .setRequired(true) + ) + .addStringOption((option) => + option + .setName('name') + .setDescription('The name of the item.') + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName('price') + .setDescription('The price of the item.') + .setRequired(true) + .setMinValue(1) + ) + .addStringOption((option) => + option + .setName('description') + .setDescription('A description of the item.') + .setRequired(true) + ) + .addStringOption((option) => + option + .setName('category') + .setDescription('The category of the item.') + .setRequired(false) + ) + .addStringOption((option) => + option + .setName('emoji') + .setDescription('The emoji for the item.') + .setRequired(false) + ) + .toJSON(), + userPermissions: [], + botPermissions: [], + category: 'Developer', - cooldown: 10, - nsfwMode: false, - testMode: false, - devOnly: true, + cooldown: 10, + nsfwMode: false, + testMode: false, + devOnly: true, - run: async (client, interaction) => { - try { - const itemId = interaction.options.getString('itemid'); - const name = interaction.options.getString('name'); - const price = interaction.options.getInteger('price'); - const description = interaction.options.getString('description'); - const category = - interaction.options.getString('category') || 'Miscellaneous'; - const emoji = interaction.options.getString('emoji') || '🔹'; + run: async (client, interaction) => { + try { + const itemId = interaction.options.getString('itemid'); + const name = interaction.options.getString('name'); + const price = interaction.options.getInteger('price'); + const description = interaction.options.getString('description'); + const category = + interaction.options.getString('category') || 'Miscellaneous'; + const emoji = interaction.options.getString('emoji') || '🔹'; - // Check if the item ID already exists - const existingItem = await Item.findOne({ itemId }); - if (existingItem) { - return interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Item Already Exists', - 'An item with this ID already exists.' - ), - ], - ephemeral: true, - }); - } + // Check if the item ID already exists + const existingItem = await Item.findOne({ itemId }); + if (existingItem) { + return interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Item Already Exists', + 'An item with this ID already exists.' + ), + ], + ephemeral: true, + }); + } - // Create and save the new item - const newItem = await Item.create({ - itemId, - name, - price, - description, - category, - emoji, - }); + // Create and save the new item + const newItem = await Item.create({ + itemId, + name, + price, + description, + category, + emoji, + }); - const embed = new EmbedBuilder() - .setColor(mconfig.embedColorSuccess) - .setTitle('✅ Item Created') - .setDescription( - `Item '${name}' has been created with ID '${itemId}', priced at ${price} clienterr coins.` - ) - .addFields( - { name: 'ID', value: itemId, inline: true }, - { name: 'Name', value: name, inline: true }, - { - name: 'Price', - value: `${price} clienterr coins`, - inline: true, - }, - { name: 'Description', value: description, inline: false }, - { name: 'Category', value: category, inline: true }, - { name: 'Emoji', value: emoji, inline: true } - ) - .setFooter({ - text: `Created by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }) - .setTimestamp(); + const embed = new EmbedBuilder() + .setColor(mconfig.embedColorSuccess) + .setTitle('✅ Item Created') + .setDescription( + `Item '${name}' has been created with ID '${itemId}', priced at ${price} clienterr coins.` + ) + .addFields( + { name: 'ID', value: itemId, inline: true }, + { name: 'Name', value: name, inline: true }, + { + name: 'Price', + value: `${price} clienterr coins`, + inline: true, + }, + { name: 'Description', value: description, inline: false }, + { name: 'Category', value: category, inline: true }, + { name: 'Emoji', value: emoji, inline: true } + ) + .setFooter({ + text: `Created by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }) + .setTimestamp(); - await interaction.reply({ embeds: [embed] }); - } catch (error) { - await interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Error', - 'There was an error creating the item.' - ), - ], - ephemeral: true, - }); - throw error; - } - }, + await interaction.reply({ embeds: [embed] }); + } catch (error) { + await interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Error', + 'There was an error creating the item.' + ), + ], + ephemeral: true, + }); + throw error; + } + }, }; function createErrorEmbed(interaction, title, description) { - return new EmbedBuilder() - .setColor(mconfig.embedColorError) - .setTitle(`❌ ${title}`) - .setDescription(description) - .setFooter({ - text: `Requested by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }) - .setTimestamp(); + return new EmbedBuilder() + .setColor(mconfig.embedColorError) + .setTitle(`❌ ${title}`) + .setDescription(description) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }) + .setTimestamp(); } diff --git a/src/commands/developer/delitem.js b/src/commands/developer/delitem.js index 873dfab..3180a9b 100644 --- a/src/commands/developer/delitem.js +++ b/src/commands/developer/delitem.js @@ -5,108 +5,108 @@ import { Item } from '../../schemas/economy.js'; import mconfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('delitem') - .setDescription('Delete an existing item from the economy system.') - .addStringOption((option) => - option - .setName('itemid') - .setDescription('The unique ID of the item to delete.') - .setRequired(true) - ) - .toJSON(), - userPermissions: [], - botPermissions: [], - category: 'Devloper', - cooldown: 10, - nsfwMode: false, - testMode: false, - devOnly: true, + data: new SlashCommandBuilder() + .setName('delitem') + .setDescription('Delete an existing item from the economy system.') + .addStringOption((option) => + option + .setName('itemid') + .setDescription('The unique ID of the item to delete.') + .setRequired(true) + ) + .toJSON(), + userPermissions: [], + botPermissions: [], + category: 'Developer', + cooldown: 10, + nsfwMode: false, + testMode: false, + devOnly: true, - /** - * Executes the delitem command. - * @param {Client} client - The Discord client instance. - * @param {CommandInteraction} interaction - The interaction object. - */ - run: async (client, interaction) => { - try { - const itemId = interaction.options.getString('itemid'); + /** + * Executes the delitem command. + * @param {Client} client - The Discord client instance. + * @param {CommandInteraction} interaction - The interaction object. + */ + run: async (client, interaction) => { + try { + const itemId = interaction.options.getString('itemid'); - // Find and delete the item - const deletedItem = await Item.findOneAndDelete({ itemId }); + // Find and delete the item + const deletedItem = await Item.findOneAndDelete({ itemId }); - if (!deletedItem) { - return interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Item Not Found', - 'An item with this ID does not exist.' - ), - ], - ephemeral: true, - }); - } - - const embed = new EmbedBuilder() - .setColor(mconfig.embedColorSuccess) - .setTitle('✅ Item Deleted') - .setDescription( - `Item '${deletedItem.name}' with ID '${itemId}' has been deleted.` - ) - .addFields( - { name: 'ID', value: deletedItem.itemId, inline: true }, - { name: 'Name', value: deletedItem.name, inline: true }, - { - name: 'Price', - value: `${deletedItem.price} clienterr coins`, - inline: true, - }, - { - name: 'Category', - value: deletedItem.category || 'N/A', - inline: true, - } - ) - .setFooter({ - text: `Deleted by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed] }); - } catch (error) { - await interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Error', - 'There was an error deleting the item.' - ), - ], - ephemeral: true, - }); - throw error; + if (!deletedItem) { + return interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Item Not Found', + 'An item with this ID does not exist.' + ), + ], + ephemeral: true, + }); } - }, -}; -function createErrorEmbed(interaction, title, description) { - return new EmbedBuilder() - .setColor(mconfig.embedColorError) - .setTitle(`❌ ${title}`) - .setDescription(description) - .setFooter({ - text: `Requested by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ + const embed = new EmbedBuilder() + .setColor(mconfig.embedColorSuccess) + .setTitle('✅ Item Deleted') + .setDescription( + `Item '${deletedItem.name}' with ID '${itemId}' has been deleted.` + ) + .addFields( + { name: 'ID', value: deletedItem.itemId, inline: true }, + { name: 'Name', value: deletedItem.name, inline: true }, + { + name: 'Price', + value: `${deletedItem.price} clienterr coins`, + inline: true, + }, + { + name: 'Category', + value: deletedItem.category || 'N/A', + inline: true, + } + ) + .setFooter({ + text: `Deleted by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ format: 'png', dynamic: true, size: 1024, - }), - }) - .setTimestamp(); + }), + }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); + } catch (error) { + await interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Error', + 'There was an error deleting the item.' + ), + ], + ephemeral: true, + }); + throw error; + } + }, +}; + +function createErrorEmbed(interaction, title, description) { + return new EmbedBuilder() + .setColor(mconfig.embedColorError) + .setTitle(`❌ ${title}`) + .setDescription(description) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }) + .setTimestamp(); } diff --git a/src/commands/developer/dm.js b/src/commands/developer/dm.js index 3c0dad1..7cc5fd9 100644 --- a/src/commands/developer/dm.js +++ b/src/commands/developer/dm.js @@ -1,235 +1,235 @@ import { - SlashCommandBuilder, - PermissionFlagsBits, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, + SlashCommandBuilder, + PermissionFlagsBits, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, } from 'discord.js'; import Bottleneck from 'bottleneck'; const limiter = new Bottleneck({ - minTime: 1000, - maxConcurrent: 1, + minTime: 1000, + maxConcurrent: 1, }); export default { - data: new SlashCommandBuilder() - .setName('dm') - .setDescription( - 'Send a direct message to a user, role, or all members in the server' - ) - .addSubcommand((subcommand) => - subcommand - .setName('user') - .setDescription('Send a DM to a specific user') - .addUserOption((option) => - option - .setName('target') - .setDescription('The user to send the DM to') - .setRequired(true) + data: new SlashCommandBuilder() + .setName('dm') + .setDescription( + 'Send a direct message to a user, role, or all members in the server' + ) + .addSubcommand((subcommand) => + subcommand + .setName('user') + .setDescription('Send a DM to a specific user') + .addUserOption((option) => + option + .setName('target') + .setDescription('The user to send the DM to') + .setRequired(true) + ) + .addStringOption((option) => + option + .setName('message') + .setDescription( + "The message to send (Use {user} for recipient's name)" ) - .addStringOption((option) => - option - .setName('message') - .setDescription( - "The message to send (Use {user} for recipient's name)" - ) - .setRequired(true) + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('role') + .setDescription('Send a DM to all users with a specific role') + .addRoleOption((option) => + option + .setName('target') + .setDescription('The role to send the DM to') + .setRequired(true) + ) + .addStringOption((option) => + option + .setName('message') + .setDescription( + "The message to send (Use {user} for recipient's name)" ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('role') - .setDescription('Send a DM to all users with a specific role') - .addRoleOption((option) => - option - .setName('target') - .setDescription('The role to send the DM to') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('message') - .setDescription( - "The message to send (Use {user} for recipient's name)" - ) - .setRequired(true) - ) - ) - .toJSON(), - - userPermissions: [PermissionFlagsBits.ManageMessages], - botPermissions: [PermissionFlagsBits.SendMessages], - cooldown: 5, - nwfwMode: false, - testMode: false, - devOnly: true, - category: 'Devloper', - prefix: false, - - run: async (client, interaction) => { - const subcommand = interaction.options.getSubcommand(); - let message = interaction.options.getString('message').trim(); - - const sendMessage = async (user) => { - if (!user || user.bot) { - return { success: false, reason: 'USER_NOT_FOUND_OR_BOT' }; - } - - try { - const personalizedMessage = message.replace( - /{user}/g, - user.displayName - ); - await limiter.schedule(() => user.send(personalizedMessage)); - return { success: true }; - } catch (error) { - if (error.code === 50007) { - return { success: false, reason: 'DM_CLOSED' }; - } else if (error.code === 10013) { - return { success: false, reason: 'USER_NOT_FOUND' }; - } else if (error.code === 50013) { - return { success: false, reason: 'MISSING_PERMISSIONS' }; - } else if (error.code === 50016) { - return { success: false, reason: 'RATE_LIMIT' }; - } - return { success: false, reason: 'UNKNOWN', error }; - } - }; + .setRequired(true) + ) + ) + .toJSON(), + + userPermissions: [PermissionFlagsBits.ManageMessages], + botPermissions: [PermissionFlagsBits.SendMessages], + cooldown: 5, + nwfwMode: false, + testMode: false, + devOnly: true, + category: 'Developer', + prefix: false, + + run: async (client, interaction) => { + const subcommand = interaction.options.getSubcommand(); + let message = interaction.options.getString('message').trim(); + + const sendMessage = async (user) => { + if (!user || user.bot) { + return { success: false, reason: 'USER_NOT_FOUND_OR_BOT' }; + } + + try { + const personalizedMessage = message.replace( + /{user}/g, + user.displayName + ); + await limiter.schedule(() => user.send(personalizedMessage)); + return { success: true }; + } catch (error) { + if (error.code === 50007) { + return { success: false, reason: 'DM_CLOSED' }; + } else if (error.code === 10013) { + return { success: false, reason: 'USER_NOT_FOUND' }; + } else if (error.code === 50013) { + return { success: false, reason: 'MISSING_PERMISSIONS' }; + } else if (error.code === 50016) { + return { success: false, reason: 'RATE_LIMIT' }; + } + return { success: false, reason: 'UNKNOWN', error }; + } + }; + + const handleProcess = async (members, description) => { + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('confirm') + .setLabel('Confirm') + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId('cancel') + .setLabel('Cancel') + .setStyle(ButtonStyle.Danger) + ); + + await interaction.reply({ + content: `Are you sure you want to send this message to ${members.size} ${description}?`, + components: [row], + ephemeral: true, + }); + + const filter = (i) => i.user.id === interaction.user.id; + const confirmation = await interaction.channel + .awaitMessageComponent({ filter, time: 30000 }) + .catch(() => null); + + if (!confirmation || confirmation.customId === 'cancel') { + return interaction.editReply({ + content: 'Command cancelled.', + components: [], + }); + } - const handleProcess = async (members, description) => { - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('confirm') - .setLabel('Confirm') - .setStyle(ButtonStyle.Primary), - new ButtonBuilder() - .setCustomId('cancel') - .setLabel('Cancel') - .setStyle(ButtonStyle.Danger) - ); - - await interaction.reply({ - content: `Are you sure you want to send this message to ${members.size} ${description}?`, - components: [row], + let successCount = 0; + let failureCount = 0; + let count = 0; + const totalMembers = members.size; + const updateInterval = Math.max(1, Math.floor(totalMembers / 20)); // Update every 5% + let cancelled = false; + + const processMember = async (member) => { + if (cancelled) return; + const result = await sendMessage(member.user); + if (result.success) successCount++; + else failureCount++; + count++; + + if (count % updateInterval === 0 || count === totalMembers) { + const progress = ((count / totalMembers) * 100).toFixed(2); + const progressBar = + '█'.repeat(Math.floor(progress / 5)) + + '░'.repeat(20 - Math.floor(progress / 5)); + await interaction.editReply({ + content: `Progress: ${progressBar} ${progress}%\n${count}/${totalMembers} messages sent`, + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('cancel_sending') + .setLabel('Cancel Sending') + .setStyle(ButtonStyle.Danger) + ), + ], ephemeral: true, - }); - - const filter = (i) => i.user.id === interaction.user.id; - const confirmation = await interaction.channel - .awaitMessageComponent({ filter, time: 30000 }) - .catch(() => null); - - if (!confirmation || confirmation.customId === 'cancel') { - return interaction.editReply({ - content: 'Command cancelled.', - components: [], - }); - } - - let successCount = 0; - let failureCount = 0; - let count = 0; - const totalMembers = members.size; - const updateInterval = Math.max(1, Math.floor(totalMembers / 20)); // Update every 5% - let cancelled = false; - - const processMember = async (member) => { - if (cancelled) return; - const result = await sendMessage(member.user); - if (result.success) successCount++; - else failureCount++; - count++; - - if (count % updateInterval === 0 || count === totalMembers) { - const progress = ((count / totalMembers) * 100).toFixed(2); - const progressBar = - '█'.repeat(Math.floor(progress / 5)) + - '░'.repeat(20 - Math.floor(progress / 5)); - await interaction.editReply({ - content: `Progress: ${progressBar} ${progress}%\n${count}/${totalMembers} messages sent`, - components: [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('cancel_sending') - .setLabel('Cancel Sending') - .setStyle(ButtonStyle.Danger) - ), - ], - ephemeral: true, - }); - } - }; - - const cancelListener = - interaction.channel.createMessageComponentCollector({ - filter, - time: 600000, - }); - cancelListener.on('collect', async (i) => { - if (i.customId === 'cancel_sending') { - cancelled = true; - await i.update({ - content: 'Cancelling the operation. Please wait...', - components: [], - }); - } - }); - - for (const member of members.values()) { - await processMember(member); - if (cancelled) break; - } - - cancelListener.stop(); - - const finalMessage = cancelled - ? `Operation cancelled. ${successCount} messages sent, ${failureCount} failed.` - : `Finished! ${successCount} messages sent, ${failureCount} failed.`; - - await interaction.editReply({ - content: finalMessage, - components: [], - }); + }); + } }; - if (subcommand === 'user') { - const user = interaction.options.getUser('target'); - const result = await sendMessage(user); - - if (result.success) { - await interaction.reply({ - content: `Message sent to ${user.tag}`, - ephemeral: true, - }); - } else { - let errorMessage = `Failed to send message to ${user.tag}. `; - switch (result.reason) { - case 'DM_CLOSED': - errorMessage += 'They have their DMs closed.'; - break; - case 'USER_NOT_FOUND': - errorMessage += 'User not found.'; - break; - case 'MISSING_PERMISSIONS': - errorMessage += 'Missing permissions to send message.'; - break; - case 'RATE_LIMIT': - errorMessage += 'Rate limit hit. Please try again later.'; - break; - default: - errorMessage += 'An unknown error occurred.'; - } - await interaction.reply({ content: errorMessage, ephemeral: true }); - } - } else if (subcommand === 'role') { - const role = interaction.options.getRole('target'); - const members = role.members; - await handleProcess(members, `members with the ${role.name} role`); - } else if (subcommand === 'all') { - const members = await interaction.guild.members.fetch(); - const humanMembers = members.filter((member) => !member.user.bot); - await handleProcess(humanMembers, 'members in the server'); + const cancelListener = + interaction.channel.createMessageComponentCollector({ + filter, + time: 600000, + }); + cancelListener.on('collect', async (i) => { + if (i.customId === 'cancel_sending') { + cancelled = true; + await i.update({ + content: 'Cancelling the operation. Please wait...', + components: [], + }); + } + }); + + for (const member of members.values()) { + await processMember(member); + if (cancelled) break; + } + + cancelListener.stop(); + + const finalMessage = cancelled + ? `Operation cancelled. ${successCount} messages sent, ${failureCount} failed.` + : `Finished! ${successCount} messages sent, ${failureCount} failed.`; + + await interaction.editReply({ + content: finalMessage, + components: [], + }); + }; + + if (subcommand === 'user') { + const user = interaction.options.getUser('target'); + const result = await sendMessage(user); + + if (result.success) { + await interaction.reply({ + content: `Message sent to ${user.tag}`, + ephemeral: true, + }); + } else { + let errorMessage = `Failed to send message to ${user.tag}. `; + switch (result.reason) { + case 'DM_CLOSED': + errorMessage += 'They have their DMs closed.'; + break; + case 'USER_NOT_FOUND': + errorMessage += 'User not found.'; + break; + case 'MISSING_PERMISSIONS': + errorMessage += 'Missing permissions to send message.'; + break; + case 'RATE_LIMIT': + errorMessage += 'Rate limit hit. Please try again later.'; + break; + default: + errorMessage += 'An unknown error occurred.'; + } + await interaction.reply({ content: errorMessage, ephemeral: true }); } - }, + } else if (subcommand === 'role') { + const role = interaction.options.getRole('target'); + const members = role.members; + await handleProcess(members, `members with the ${role.name} role`); + } else if (subcommand === 'all') { + const members = await interaction.guild.members.fetch(); + const humanMembers = members.filter((member) => !member.user.bot); + await handleProcess(humanMembers, 'members in the server'); + } + }, }; diff --git a/src/commands/developer/eco.js b/src/commands/developer/eco.js index a6b3eb4..2349220 100644 --- a/src/commands/developer/eco.js +++ b/src/commands/developer/eco.js @@ -1,224 +1,224 @@ /** @format */ import { - SlashCommandBuilder, - EmbedBuilder, - PermissionFlagsBits, + SlashCommandBuilder, + EmbedBuilder, + PermissionFlagsBits, } from 'discord.js'; import { Balance, Transaction } from '../../schemas/economy.js'; import mconfig from '../../config/messageConfig.js'; const COLORS = { - DEFAULT: 0x7289da, // Discord blurple - SUCCESS: 0x43b581, // Discord green - WARNING: 0xfaa61a, // Discord yellow - INFO: 0x5865f2, // Discord blue - ERROR: 0xed4245, // Discord red + DEFAULT: 0x7289da, // Discord blurple + SUCCESS: 0x43b581, // Discord green + WARNING: 0xfaa61a, // Discord yellow + INFO: 0x5865f2, // Discord blue + ERROR: 0xed4245, // Discord red }; export default { - data: new SlashCommandBuilder() - .setName('eco') - .setDescription('Economy management commands') - .addSubcommand((subcommand) => - subcommand - .setName('add') - .setDescription("Add coins to a user's balance") - .addUserOption((option) => - option - .setName('user') - .setDescription('The user you want to add coins to') - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName('amount') - .setDescription('The amount of coins to add') - .setRequired(true) - .setMinValue(1) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('subtract') - .setDescription("Subtract coins from a user's balance") - .addUserOption((option) => - option - .setName('user') - .setDescription('The user you want to subtract coins from') - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName('amount') - .setDescription('The amount of coins to subtract') - .setRequired(true) - .setMinValue(1) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('set') - .setDescription("Set a user's balance to a specific amount") - .addUserOption((option) => - option - .setName('user') - .setDescription('The user whose balance you want to set') - .setRequired(true) - ) - .addIntegerOption((option) => - option - .setName('amount') - .setDescription('The amount to set the balance to') - .setRequired(true) - .setMinValue(0) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('view') - .setDescription("View a user's balance") - .addUserOption((option) => - option - .setName('user') - .setDescription('The user whose balance you want to view') - .setRequired(true) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('reset') - .setDescription("Reset a user's balance to 0") - .addUserOption((option) => - option - .setName('user') - .setDescription('The user whose balance you want to reset') - .setRequired(true) - ) - ) - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) - .toJSON(), - userPermissions: ['Administrator'], - botPermissions: [], - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: true, - category: 'Devloper', + data: new SlashCommandBuilder() + .setName('eco') + .setDescription('Economy management commands') + .addSubcommand((subcommand) => + subcommand + .setName('add') + .setDescription("Add coins to a user's balance") + .addUserOption((option) => + option + .setName('user') + .setDescription('The user you want to add coins to') + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName('amount') + .setDescription('The amount of coins to add') + .setRequired(true) + .setMinValue(1) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('subtract') + .setDescription("Subtract coins from a user's balance") + .addUserOption((option) => + option + .setName('user') + .setDescription('The user you want to subtract coins from') + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName('amount') + .setDescription('The amount of coins to subtract') + .setRequired(true) + .setMinValue(1) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('set') + .setDescription("Set a user's balance to a specific amount") + .addUserOption((option) => + option + .setName('user') + .setDescription('The user whose balance you want to set') + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName('amount') + .setDescription('The amount to set the balance to') + .setRequired(true) + .setMinValue(0) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('view') + .setDescription("View a user's balance") + .addUserOption((option) => + option + .setName('user') + .setDescription('The user whose balance you want to view') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('reset') + .setDescription("Reset a user's balance to 0") + .addUserOption((option) => + option + .setName('user') + .setDescription('The user whose balance you want to reset') + .setRequired(true) + ) + ) + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) + .toJSON(), + userPermissions: ['Administrator'], + botPermissions: [], + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: true, + category: 'Developer', - run: async (client, interaction) => { - const subcommand = interaction.options.getSubcommand(); - const user = interaction.options.getUser('user'); - const amount = interaction.options.getInteger('amount'); + run: async (client, interaction) => { + const subcommand = interaction.options.getSubcommand(); + const user = interaction.options.getUser('user'); + const amount = interaction.options.getInteger('amount'); - try { - let userBalance = await Balance.findOneAndUpdate( - { userId: user.id }, - { $setOnInsert: { balance: 0, bank: 0 } }, - { upsert: true, new: true } - ); + try { + let userBalance = await Balance.findOneAndUpdate( + { userId: user.id }, + { $setOnInsert: { balance: 0, bank: 0 } }, + { upsert: true, new: true } + ); - let responseMessage; - let color = COLORS.DEFAULT; // Default color - let transactionType; + let responseMessage; + let color = COLORS.DEFAULT; // Default color + let transactionType; - switch (subcommand) { - case 'add': - userBalance.balance += amount; - responseMessage = `Added ${amount} coins to ${user.username}'s balance. New balance: ${userBalance.balance} coins.`; - color = COLORS.SUCCESS; - transactionType = 'add'; - break; - case 'subtract': - if (userBalance.balance < amount) { - return interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Insufficient Balance', - `${user.username} does not have enough balance to subtract ${amount} coins.` - ), - ], - ephemeral: true, - }); - } - userBalance.balance -= amount; - responseMessage = `Subtracted ${amount} coins from ${user.username}'s balance. New balance: ${userBalance.balance} coins.`; - color = COLORS.WARNING; - transactionType = 'subtract'; - break; - case 'set': - userBalance.balance = amount; - responseMessage = `Set ${user.username}'s balance to ${amount} coins.`; - color = COLORS.INFO; - transactionType = 'set'; - break; - case 'view': - responseMessage = `${user.username}'s current balance: ${userBalance.balance} coins.`; - // color remains default - break; - case 'reset': - userBalance.balance = 0; - responseMessage = `Reset ${user.username}'s balance to 0 coins.`; - color = COLORS.WARNING; - transactionType = 'reset'; - break; - default: - throw new Error('Invalid subcommand'); - } - - if (subcommand !== 'view') { - await userBalance.save(); - await Transaction.create({ - userId: user.id, - type: transactionType, - amount: subcommand === 'reset' ? userBalance.balance : amount, - executorId: interaction.user.id, + switch (subcommand) { + case 'add': + userBalance.balance += amount; + responseMessage = `Added ${amount} coins to ${user.username}'s balance. New balance: ${userBalance.balance} coins.`; + color = COLORS.SUCCESS; + transactionType = 'add'; + break; + case 'subtract': + if (userBalance.balance < amount) { + return interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Insufficient Balance', + `${user.username} does not have enough balance to subtract ${amount} coins.` + ), + ], + ephemeral: true, }); - } - - const embed = new EmbedBuilder() - .setDescription(responseMessage) - .setColor(color) - .setFooter({ - text: `Executed by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }) - .setTimestamp(); + } + userBalance.balance -= amount; + responseMessage = `Subtracted ${amount} coins from ${user.username}'s balance. New balance: ${userBalance.balance} coins.`; + color = COLORS.WARNING; + transactionType = 'subtract'; + break; + case 'set': + userBalance.balance = amount; + responseMessage = `Set ${user.username}'s balance to ${amount} coins.`; + color = COLORS.INFO; + transactionType = 'set'; + break; + case 'view': + responseMessage = `${user.username}'s current balance: ${userBalance.balance} coins.`; + // color remains default + break; + case 'reset': + userBalance.balance = 0; + responseMessage = `Reset ${user.username}'s balance to 0 coins.`; + color = COLORS.WARNING; + transactionType = 'reset'; + break; + default: + throw new Error('Invalid subcommand'); + } - await interaction.reply({ embeds: [embed] }); - } catch (error) { - console.error('Economy command error:', error); - await interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Error', - 'There was an error processing your request. Please try again later.' - ), - ], - ephemeral: true, - }); + if (subcommand !== 'view') { + await userBalance.save(); + await Transaction.create({ + userId: user.id, + type: transactionType, + amount: subcommand === 'reset' ? userBalance.balance : amount, + executorId: interaction.user.id, + }); } - }, -}; -function createErrorEmbed(interaction, title, description) { - return new EmbedBuilder() - .setColor(COLORS.ERROR) - .setTitle(`❌ ${title}`) - .setDescription(description) - .setFooter({ - text: `Requested by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ + const embed = new EmbedBuilder() + .setDescription(responseMessage) + .setColor(color) + .setFooter({ + text: `Executed by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ format: 'png', dynamic: true, size: 1024, - }), - }) - .setTimestamp(); + }), + }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); + } catch (error) { + console.error('Economy command error:', error); + await interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Error', + 'There was an error processing your request. Please try again later.' + ), + ], + ephemeral: true, + }); + } + }, +}; + +function createErrorEmbed(interaction, title, description) { + return new EmbedBuilder() + .setColor(COLORS.ERROR) + .setTitle(`❌ ${title}`) + .setDescription(description) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }) + .setTimestamp(); } diff --git a/src/commands/developer/emojiapp.js b/src/commands/developer/emojiapp.js index c8ba765..8ae6b41 100644 --- a/src/commands/developer/emojiapp.js +++ b/src/commands/developer/emojiapp.js @@ -1,312 +1,365 @@ -import axios from 'axios'; -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +/** @format */ -const data = new SlashCommandBuilder() - .setName('emoji-app') - .setDescription('Manage application emojis') - .addSubcommand((command) => - command - .setName('create') - .setDescription('Create app emojis') - .addStringOption((option) => - option - .setName('emojis') - .setDescription('The emojis to add (space-separated)') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('names') - .setDescription('The names of the emojis (comma-separated)') - .setRequired(true) - ) - ) - .addSubcommand((command) => - command - .setName('remove') - .setDescription('Remove an app emoji') - .addStringOption((option) => - option - .setName('emoji-id') - .setDescription('The ID of the emoji to remove') - .setRequired(true) - ) - ) - .addSubcommand((command) => - command - .setName('edit') - .setDescription('Edit an app emoji name') - .addStringOption((option) => - option - .setName('emoji-id') - .setDescription('The ID of the emoji to edit') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('new-name') - .setDescription('The new name for the emoji') - .setRequired(true) - ) - ) - .addSubcommand((command) => - command.setName('list').setDescription('List all app emojis') - ) - .toJSON(); +import { + SlashCommandBuilder, + EmbedBuilder, + PermissionFlagsBits, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, +} from 'discord.js'; +import buttonPagination from '../../utils/buttonPagination.js'; +import mconfig from '../../config/messageConfig.js'; export default { - data, - userPermissions: [], - botPermissions: [], - category: 'Misc', - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, - prefix: false, + data: new SlashCommandBuilder() + .setName('servers') + .setDescription('Manage and view information about servers the bot is in.') + .addSubcommand((subcommand) => + subcommand + .setName('list') + .setDescription('List servers the bot is in and provide invite links') + .addBooleanOption((option) => + option + .setName('detailed') + .setDescription('Show detailed server information') + .setRequired(false) + ) + .addStringOption((option) => + option + .setName('sort') + .setDescription('Sort servers by a specific criteria') + .setRequired(false) + .addChoices( + { name: 'Name', value: 'name' }, + { name: 'Member Count', value: 'memberCount' }, + { name: 'Creation Date', value: 'createdAt' } + ) + ) + ) - run: async (client, interaction) => { - await interaction.deferReply(); - const { options } = interaction; - const subCommand = options.getSubcommand(); + .addSubcommand((subcommand) => + subcommand + .setName('leave') + .setDescription('Make the bot leave a specified server by its ID.') + .addStringOption((option) => + option + .setName('server-id') + .setDescription('The ID of the server the bot should leave.') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('check') + .setDescription( + 'Check if the bot is in specified servers by their IDs.' + ) + .addStringOption((option) => + option + .setName('server-ids') + .setDescription('Comma-separated IDs of the servers to check.') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('user') + .setDescription('Show the number of servers a user owns.') + .addUserOption((option) => + option + .setName('user') + .setDescription('The user to check.') + .setRequired(true) + ) + ) + .toJSON(), + userPermissions: [PermissionFlagsBits.Administrator], + botPermissions: [], + cooldown: 10, + nsfwMode: false, + testMode: false, + devOnly: true, + category: 'Developer', - const emojiManager = new EmojiManager( - process.env.APPLICATION_ID, - process.env.TOKEN - ); + run: async (client, interaction) => { + await interaction.deferReply(); + const subcommand = interaction.options.getSubcommand(); - try { - switch (subCommand) { - case 'create': - await handleCreateCommand(emojiManager, options, interaction); - break; - case 'remove': - await handleRemoveCommand(emojiManager, options, interaction); - break; - case 'edit': - await handleEditCommand(emojiManager, options, interaction); - break; - case 'list': - await handleListCommand(emojiManager, interaction); - break; - default: - throw new Error('Invalid subcommand'); - } - } catch (error) { - console.error('Command execution error:', error); - await sendErrorMessage( - interaction, - 'An error occurred while processing the command. Please try again later.' - ); + try { + switch (subcommand) { + case 'list': + await handleListSubcommand(client, interaction); + break; + case 'leave': + await handleLeaveSubcommand(client, interaction); + break; + case 'check': + await handleCheckSubcommand(client, interaction); + break; + case 'user': + await handleUserSubcommand(client, interaction); + break; + default: + throw new Error(`Unknown subcommand: ${subcommand}`); } - }, + } catch (error) { + console.error(`Error in servers command (${subcommand}):`, error); + await interaction.editReply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; -class EmojiManager { - constructor(applicationId, token) { - this.applicationId = applicationId; - this.token = token; - this.baseUrl = `https://discord.com/api/v10/applications/${this.applicationId}/emojis`; - } - - async apiCall(method, endpoint = '', data = null) { - const config = { - method, - url: `${this.baseUrl}${endpoint}`, - headers: { Authorization: `Bot ${this.token}` }, - data, - }; +async function handleListSubcommand(client, interaction) { + const isDetailed = interaction.options.getBoolean('detailed') ?? false; + const sortOption = interaction.options.getString('sort') ?? 'name'; + let guilds = await Promise.all( + client.guilds.cache.map(async (guild) => { + let inviteLink = 'No invite link available'; try { - const response = await axios(config); - return response.data; + const channels = guild.channels.cache.filter( + (channel) => + channel.type === 0 && + channel + .permissionsFor(guild.members.me) + .has(PermissionFlagsBits.CreateInstantInvite) + ); + if (channels.size > 0) { + const invite = await channels + .first() + .createInvite({ maxAge: 0, maxUses: 0 }); + inviteLink = invite.url; + } } catch (error) { - console.error( - `API Call Error - ${method} ${endpoint}:`, - error.response?.data || error.message - ); - throw error; + console.error(`Could not create invite for guild ${guild.id}:`, error); } - } - - async createEmoji(name, image) { - return this.apiCall('POST', '', { name, image }); - } - - async removeEmoji(emojiId) { - return this.apiCall('DELETE', `/${emojiId}`); - } + return { + name: guild.name, + memberCount: guild.memberCount, + id: guild.id, + inviteLink, + owner: await guild.fetchOwner(), + createdAt: guild.createdAt, + boostLevel: guild.premiumTier, + }; + }) + ); + guilds.sort((a, b) => { + if (sortOption === 'memberCount') { + return b.memberCount - a.memberCount; + } else if (sortOption === 'createdAt') { + return b.createdAt - a.createdAt; + } else { + return a.name.localeCompare(b.name); + } + }); - async editEmoji(emojiId, name) { - return this.apiCall('PATCH', `/${emojiId}`, { name }); - } + if (guilds.length === 0) { + return await interaction.editReply('The bot is not in any servers.'); + } - async listEmojis() { - return this.apiCall('GET'); - } + const embeds = createServerListEmbeds( + client, + interaction, + guilds, + isDetailed, + sortOption + ); + await buttonPagination(interaction, embeds, { + time: 5 * 60 * 1000, // 5 minutes + showPageIndicator: true, + allowUserNavigation: true, + }); } -async function handleCreateCommand(emojiManager, options, interaction) { - const emojis = options.getString('emojis').split(' '); - const names = options - .getString('names') - .split(',') - .map((name) => name.trim()); +function createServerListEmbeds( + client, + interaction, + guilds, + isDetailed, + sortOption +) { + const MAX_FIELDS = isDetailed ? 4 : 8; + const embeds = []; - if (emojis.length !== names.length) { - await sendErrorMessage( - interaction, - 'The number of emojis and names must match.' - ); - return; - } + for (let i = 0; i < guilds.length; i += MAX_FIELDS) { + const currentGuilds = guilds.slice(i, i + MAX_FIELDS); + const embed = new EmbedBuilder() + .setTitle('Servers List') + .setDescription( + `The bot is in **${guilds.length}** servers. Sorted by: ${sortOption}` + ) + .setColor(mconfig.embedColorSuccess) + .setThumbnail(client.user.displayAvatarURL()) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }); - const createdEmojis = []; + currentGuilds.forEach((guild) => { + const fieldValue = isDetailed + ? `ID: ${guild.id}\nMembers: ${guild.memberCount}\nOwner: ${guild.owner.user.tag}\nCreated: ${guild.createdAt.toDateString()}\nBoost Level: ${guild.boostLevel}\n[Invite Link](${guild.inviteLink})` + : `ID: ${guild.id}\nMembers: ${guild.memberCount}\n[Invite Link](${guild.inviteLink})`; - for (let i = 0; i < emojis.length; i++) { - try { - const { imageBuffer, isAnimated } = await getEmojiImage(emojis[i]); - const base64Image = imageBuffer.toString('base64'); - const createData = { - name: names[i], - image: `data:image/${isAnimated ? 'gif' : 'png'};base64,${base64Image}`, - }; + embed.addFields({ name: guild.name, value: fieldValue, inline: true }); + }); - const createdEmoji = await emojiManager.createEmoji( - createData.name, - createData.image - ); - createdEmojis.push( - `<${isAnimated ? 'a' : ''}:${createdEmoji.name}:${createdEmoji.id}>` - ); - } catch (error) { - console.error(`Error creating emoji ${emojis[i]}:`, error); - await sendErrorMessage( - interaction, - `Failed to create emoji ${emojis[i]}: ${error.message}` - ); - } - } + embeds.push(embed); + } - if (createdEmojis.length > 0) { - await sendSuccessMessage( - interaction, - `Created emojis: ${createdEmojis.join(' ')}` - ); - } else { - await sendErrorMessage( - interaction, - 'No emojis were created. Please check the provided data and try again.' - ); - } + return embeds; } -async function handleRemoveCommand(emojiManager, options, interaction) { - const emojiId = options.getString('emoji-id'); - try { - await emojiManager.removeEmoji(emojiId); - await sendSuccessMessage( - interaction, - `Removed emoji with ID: ${emojiId}` - ); - } catch (error) { - await sendErrorMessage( - interaction, - `Failed to remove emoji: ${error.message}` - ); - } -} +async function handleLeaveSubcommand(client, interaction) { + const serverId = interaction.options.getString('server-id'); + const guild = client.guilds.cache.get(serverId); -async function handleEditCommand(emojiManager, options, interaction) { - const emojiId = options.getString('emoji-id'); - const newName = options.getString('new-name'); - try { - const editedEmoji = await emojiManager.editEmoji(emojiId, newName); - await sendSuccessMessage( - interaction, - `Emoji name updated successfully to: ${editedEmoji.name}` - ); - } catch (error) { - await sendErrorMessage( - interaction, - `Failed to edit emoji: ${error.message}` - ); - } -} + if (!guild) { + return await interaction.editReply( + `I am not in a server with the ID ${serverId}.` + ); + } -async function handleListCommand(emojiManager, interaction) { - try { - const { items: emojisList } = await emojiManager.listEmojis(); + const confirmButton = new ButtonBuilder() + .setCustomId('confirm_leave') + .setLabel('Confirm Leave') + .setStyle(ButtonStyle.Danger); - if (Array.isArray(emojisList) && emojisList.length > 0) { - // Format the emoji list message - const emojiListMessage = emojisList - .map( - (emoji) => - `<${emoji.animated ? 'a' : ''}:${emoji.name}:${emoji.id}> \`${emoji.name}\` (ID: ${emoji.id})` - ) - .join('\n'); + const cancelButton = new ButtonBuilder() + .setCustomId('cancel_leave') + .setLabel('Cancel') + .setStyle(ButtonStyle.Secondary); - await sendSuccessMessage( - interaction, - `List of all emojis:\n${emojiListMessage}` - ); - } else { - await sendInfoMessage( - interaction, - 'No emojis found for this application.' - ); - } - } catch (error) { - // Send an error message if the emoji list retrieval fails - await sendErrorMessage( - interaction, - `Failed to retrieve emoji list: ${error.message}` - ); - } + const row = new ActionRowBuilder().addComponents(confirmButton, cancelButton); + + const response = await interaction.editReply({ + content: `Are you sure you want me to leave the server **${guild.name}** (ID: ${serverId})?`, + components: [row], + }); + try { + const confirmation = await response.awaitMessageComponent({ + filter: (i) => i.user.id === interaction.user.id, + time: 30000, + }); + + if (confirmation.customId === 'confirm_leave') { + await guild.leave(); + await confirmation.update({ + content: `I have left the server **${guild.name}** (ID: ${serverId}).`, + components: [], + }); + } else { + await confirmation.update({ + content: 'Server leave cancelled.', + components: [], + }); + } + } catch (error) { + await interaction.editReply({ + content: + 'No response received within 30 seconds, cancelling server leave.', + components: [], + }); + throw error; + } } -async function getEmojiImage(emoji) { - let imageBuffer; - let isAnimated = false; +async function handleCheckSubcommand(client, interaction) { + const serverIds = interaction.options + .getString('server-ids') + .split(',') + .map((id) => id.trim()); + if (serverIds.length > 10) { + return await interaction.editReply( + 'Please provide 10 or fewer server IDs to check.' + ); + } - if (emoji.startsWith('<:') || emoji.startsWith(' { + const guild = client.guilds.cache.get(serverId); + if (guild) { + const owner = await guild.fetchOwner(); + return new EmbedBuilder() + .setTitle(`Server Information: ${guild.name}`) + .setDescription(`The bot is in this server.`) + .addFields( + { name: 'Server ID', value: serverId, inline: true }, + { name: 'Owner', value: owner.user.tag, inline: true }, + { + name: 'Members', + value: guild.memberCount.toString(), + inline: true, + }, + { + name: 'Created At', + value: guild.createdAt.toDateString(), + inline: true, + }, + { + name: 'Boost Level', + value: guild.premiumTier.toString(), + inline: true, + } + ) + .setColor(mconfig.embedColorSuccess); + } else { + return new EmbedBuilder() + .setTitle(`Server Not Found`) + .setDescription(`The bot is not in a server with the ID ${serverId}.`) + .setColor(mconfig.embedColorError); + } + }) + ); - return { imageBuffer, isAnimated }; + await buttonPagination(interaction, results, { + time: 5 * 60 * 1000, // 5 minutes + showPageIndicator: true, + allowUserNavigation: true, + }); } -async function sendErrorMessage(interaction, message) { - const embed = new EmbedBuilder() - .setTitle('Error') - .setDescription(message) - .setColor('Red'); - await interaction.editReply({ embeds: [embed] }); -} +async function handleUserSubcommand(client, interaction) { + const user = interaction.options.getUser('user'); + const userServers = client.guilds.cache.filter( + (guild) => guild.ownerId === user.id + ); + const serverCount = userServers.size; -async function sendSuccessMessage(interaction, message) { - const embed = new EmbedBuilder() - .setTitle('Success') - .setDescription(message) - .setColor('Green'); - await interaction.editReply({ embeds: [embed] }); -} + const serverList = userServers + .map( + (guild) => + `- ${guild.name} (ID: ${guild.id}, Members: ${guild.memberCount})` + ) + .join('\n'); + + const embed = new EmbedBuilder() + .setTitle(`Servers Owned by ${user.username}`) + .setDescription( + `User ${user.username} (ID: ${user.id}) owns **${serverCount}** server(s) that the bot is in.` + ) + .setColor(mconfig.embedColorSuccess) + .addFields({ + name: 'Server List', + value: serverList || 'No servers found.', + }) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }); -async function sendInfoMessage(interaction, message) { - const embed = new EmbedBuilder() - .setTitle('Info') - .setDescription(message) - .setColor('Red'); - await interaction.editReply({ embeds: [embed] }); + await interaction.editReply({ embeds: [embed] }); } diff --git a/src/commands/developer/servers.js b/src/commands/developer/servers.js index 8712bcd..3f88603 100644 --- a/src/commands/developer/servers.js +++ b/src/commands/developer/servers.js @@ -1,371 +1,357 @@ /** @format */ import { - SlashCommandBuilder, - EmbedBuilder, - PermissionFlagsBits, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, + SlashCommandBuilder, + EmbedBuilder, + PermissionFlagsBits, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, } from 'discord.js'; import paginateEmbeds from '../../utils/buttonPagination.js'; import mconfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('servers') - .setDescription( - 'Manage and view information about servers the bot is in.' - ) - .addSubcommand((subcommand) => - subcommand - .setName('list') - .setDescription( - 'List servers the bot is in and provide invite links' - ) - .addBooleanOption((option) => - option - .setName('detailed') - .setDescription('Show detailed server information') - .setRequired(false) + data: new SlashCommandBuilder() + .setName('servers') + .setDescription('Manage and view information about servers the bot is in.') + .addSubcommand((subcommand) => + subcommand + .setName('list') + .setDescription('List servers the bot is in and provide invite links') + .addBooleanOption((option) => + option + .setName('detailed') + .setDescription('Show detailed server information') + .setRequired(false) + ) + .addStringOption((option) => + option + .setName('sort') + .setDescription('Sort servers by a specific criteria') + .setRequired(false) + .addChoices( + { name: 'Name', value: 'name' }, + { name: 'Member Count', value: 'memberCount' }, + { name: 'Creation Date', value: 'createdAt' } ) - .addStringOption((option) => - option - .setName('sort') - .setDescription('Sort servers by a specific criteria') - .setRequired(false) - .addChoices( - { name: 'Name', value: 'name' }, - { name: 'Member Count', value: 'memberCount' }, - { name: 'Creation Date', value: 'createdAt' } - ) - ) - ) + ) + ) - .addSubcommand((subcommand) => - subcommand - .setName('leave') - .setDescription('Make the bot leave a specified server by its ID.') - .addStringOption((option) => - option - .setName('server-id') - .setDescription('The ID of the server the bot should leave.') - .setRequired(true) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('check') - .setDescription( - 'Check if the bot is in specified servers by their IDs.' - ) - .addStringOption((option) => - option - .setName('server-ids') - .setDescription( - 'Comma-separated IDs of the servers to check.' - ) - .setRequired(true) - ) - ) - .addSubcommand((subcommand) => - subcommand + .addSubcommand((subcommand) => + subcommand + .setName('leave') + .setDescription('Make the bot leave a specified server by its ID.') + .addStringOption((option) => + option + .setName('server-id') + .setDescription('The ID of the server the bot should leave.') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('check') + .setDescription( + 'Check if the bot is in specified servers by their IDs.' + ) + .addStringOption((option) => + option + .setName('server-ids') + .setDescription('Comma-separated IDs of the servers to check.') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('user') + .setDescription('Show the number of servers a user owns.') + .addUserOption((option) => + option .setName('user') - .setDescription('Show the number of servers a user owns.') - .addUserOption((option) => - option - .setName('user') - .setDescription('The user to check.') - .setRequired(true) - ) - ) - .toJSON(), - userPermissions: [PermissionFlagsBits.Administrator], - botPermissions: [], - cooldown: 10, - nsfwMode: false, - testMode: false, - devOnly: true, - category: 'Devloper', + .setDescription('The user to check.') + .setRequired(true) + ) + ) + .toJSON(), + userPermissions: [PermissionFlagsBits.Administrator], + botPermissions: [], + cooldown: 10, + nsfwMode: false, + testMode: false, + devOnly: true, + category: 'Developer', - run: async (client, interaction) => { - await interaction.deferReply(); - const subcommand = interaction.options.getSubcommand(); + run: async (client, interaction) => { + await interaction.deferReply(); + const subcommand = interaction.options.getSubcommand(); - try { - switch (subcommand) { - case 'list': - await handleListSubcommand(client, interaction); - break; - case 'leave': - await handleLeaveSubcommand(client, interaction); - break; - case 'check': - await handleCheckSubcommand(client, interaction); - break; - case 'user': - await handleUserSubcommand(client, interaction); - break; - default: - throw new Error(`Unknown subcommand: ${subcommand}`); - } - } catch (error) { - console.error(`Error in servers command (${subcommand}):`, error); - await interaction.editReply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); + try { + switch (subcommand) { + case 'list': + await handleListSubcommand(client, interaction); + break; + case 'leave': + await handleLeaveSubcommand(client, interaction); + break; + case 'check': + await handleCheckSubcommand(client, interaction); + break; + case 'user': + await handleUserSubcommand(client, interaction); + break; + default: + throw new Error(`Unknown subcommand: ${subcommand}`); } - }, + } catch (error) { + console.error(`Error in servers command (${subcommand}):`, error); + await interaction.editReply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; async function handleListSubcommand(client, interaction) { - const isDetailed = interaction.options.getBoolean('detailed') ?? false; - const sortOption = interaction.options.getString('sort') ?? 'name'; + const isDetailed = interaction.options.getBoolean('detailed') ?? false; + const sortOption = interaction.options.getString('sort') ?? 'name'; - let guilds = await Promise.all( - client.guilds.cache.map(async (guild) => { - let inviteLink = 'No invite link available'; - try { - const channels = guild.channels.cache.filter( - (channel) => - channel.type === 0 && - channel - .permissionsFor(guild.members.me) - .has(PermissionFlagsBits.CreateInstantInvite) - ); - if (channels.size > 0) { - const invite = await channels - .first() - .createInvite({ maxAge: 0, maxUses: 0 }); - inviteLink = invite.url; - } - } catch (error) { - console.error( - `Could not create invite for guild ${guild.id}:`, - error - ); - } - return { - name: guild.name, - memberCount: guild.memberCount, - id: guild.id, - inviteLink, - owner: await guild.fetchOwner(), - createdAt: guild.createdAt, - boostLevel: guild.premiumTier, - }; - }) - ); - guilds.sort((a, b) => { - if (sortOption === 'memberCount') { - return b.memberCount - a.memberCount; - } else if (sortOption === 'createdAt') { - return b.createdAt - a.createdAt; - } else { - return a.name.localeCompare(b.name); + let guilds = await Promise.all( + client.guilds.cache.map(async (guild) => { + let inviteLink = 'No invite link available'; + try { + const channels = guild.channels.cache.filter( + (channel) => + channel.type === 0 && + channel + .permissionsFor(guild.members.me) + .has(PermissionFlagsBits.CreateInstantInvite) + ); + if (channels.size > 0) { + const invite = await channels + .first() + .createInvite({ maxAge: 0, maxUses: 0 }); + inviteLink = invite.url; + } + } catch (error) { + console.error(`Could not create invite for guild ${guild.id}:`, error); } - }); + return { + name: guild.name, + memberCount: guild.memberCount, + id: guild.id, + inviteLink, + owner: await guild.fetchOwner(), + createdAt: guild.createdAt, + boostLevel: guild.premiumTier, + }; + }) + ); + guilds.sort((a, b) => { + if (sortOption === 'memberCount') { + return b.memberCount - a.memberCount; + } else if (sortOption === 'createdAt') { + return b.createdAt - a.createdAt; + } else { + return a.name.localeCompare(b.name); + } + }); - if (guilds.length === 0) { - return await interaction.editReply('The bot is not in any servers.'); - } + if (guilds.length === 0) { + return await interaction.editReply('The bot is not in any servers.'); + } - const embeds = createServerListEmbeds( - client, - interaction, - guilds, - isDetailed, - sortOption - ); - await paginateEmbeds(interaction, embeds); + const embeds = createServerListEmbeds( + client, + interaction, + guilds, + isDetailed, + sortOption + ); + await paginateEmbeds(interaction, embeds); } function createServerListEmbeds( - client, - interaction, - guilds, - isDetailed, - sortOption + client, + interaction, + guilds, + isDetailed, + sortOption ) { - const MAX_FIELDS = isDetailed ? 4 : 8; - const embeds = []; + const MAX_FIELDS = isDetailed ? 4 : 8; + const embeds = []; - for (let i = 0; i < guilds.length; i += MAX_FIELDS) { - const currentGuilds = guilds.slice(i, i + MAX_FIELDS); - const embed = new EmbedBuilder() - .setTitle('Servers List') - .setDescription( - `The bot is in **${guilds.length}** servers. Sorted by: ${sortOption}` - ) - .setColor(mconfig.embedColorSuccess) - .setThumbnail(client.user.displayAvatarURL()) - .setFooter({ - text: `Page ${Math.floor(i / MAX_FIELDS) + 1}/${Math.ceil(guilds.length / MAX_FIELDS)} • Requested by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }); + for (let i = 0; i < guilds.length; i += MAX_FIELDS) { + const currentGuilds = guilds.slice(i, i + MAX_FIELDS); + const embed = new EmbedBuilder() + .setTitle('Servers List') + .setDescription( + `The bot is in **${guilds.length}** servers. Sorted by: ${sortOption}` + ) + .setColor(mconfig.embedColorSuccess) + .setThumbnail(client.user.displayAvatarURL()) + .setFooter({ + text: `Page ${Math.floor(i / MAX_FIELDS) + 1}/${Math.ceil(guilds.length / MAX_FIELDS)} • Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }); - currentGuilds.forEach((guild) => { - const fieldValue = isDetailed - ? `ID: ${guild.id}\nMembers: ${guild.memberCount}\nOwner: ${guild.owner.user.tag}\nCreated: ${guild.createdAt.toDateString()}\nBoost Level: ${guild.boostLevel}\n[Invite Link](${guild.inviteLink})` - : `ID: ${guild.id}\nMembers: ${guild.memberCount}\n[Invite Link](${guild.inviteLink})`; + currentGuilds.forEach((guild) => { + const fieldValue = isDetailed + ? `ID: ${guild.id}\nMembers: ${guild.memberCount}\nOwner: ${guild.owner.user.tag}\nCreated: ${guild.createdAt.toDateString()}\nBoost Level: ${guild.boostLevel}\n[Invite Link](${guild.inviteLink})` + : `ID: ${guild.id}\nMembers: ${guild.memberCount}\n[Invite Link](${guild.inviteLink})`; - embed.addFields({ name: guild.name, value: fieldValue, inline: true }); - }); + embed.addFields({ name: guild.name, value: fieldValue, inline: true }); + }); - embeds.push(embed); - } + embeds.push(embed); + } - return embeds; + return embeds; } async function handleLeaveSubcommand(client, interaction) { - const serverId = interaction.options.getString('server-id'); - const guild = client.guilds.cache.get(serverId); + const serverId = interaction.options.getString('server-id'); + const guild = client.guilds.cache.get(serverId); - if (!guild) { - return await interaction.editReply( - `I am not in a server with the ID ${serverId}.` - ); - } + if (!guild) { + return await interaction.editReply( + `I am not in a server with the ID ${serverId}.` + ); + } - const confirmButton = new ButtonBuilder() - .setCustomId('confirm_leave') - .setLabel('Confirm Leave') - .setStyle(ButtonStyle.Danger); + const confirmButton = new ButtonBuilder() + .setCustomId('confirm_leave') + .setLabel('Confirm Leave') + .setStyle(ButtonStyle.Danger); - const cancelButton = new ButtonBuilder() - .setCustomId('cancel_leave') - .setLabel('Cancel') - .setStyle(ButtonStyle.Secondary); + const cancelButton = new ButtonBuilder() + .setCustomId('cancel_leave') + .setLabel('Cancel') + .setStyle(ButtonStyle.Secondary); - const row = new ActionRowBuilder().addComponents( - confirmButton, - cancelButton - ); + const row = new ActionRowBuilder().addComponents(confirmButton, cancelButton); - const response = await interaction.editReply({ - content: `Are you sure you want me to leave the server **${guild.name}** (ID: ${serverId})?`, - components: [row], - }); - try { - const confirmation = await response.awaitMessageComponent({ - filter: (i) => i.user.id === interaction.user.id, - time: 30000, - }); + const response = await interaction.editReply({ + content: `Are you sure you want me to leave the server **${guild.name}** (ID: ${serverId})?`, + components: [row], + }); + try { + const confirmation = await response.awaitMessageComponent({ + filter: (i) => i.user.id === interaction.user.id, + time: 30000, + }); - if (confirmation.customId === 'confirm_leave') { - await guild.leave(); - await confirmation.update({ - content: `I have left the server **${guild.name}** (ID: ${serverId}).`, - components: [], - }); - } else { - await confirmation.update({ - content: 'Server leave cancelled.', - components: [], - }); - } - } catch (error) { - await interaction.editReply({ - content: - 'No response received within 30 seconds, cancelling server leave.', - components: [], + if (confirmation.customId === 'confirm_leave') { + await guild.leave(); + await confirmation.update({ + content: `I have left the server **${guild.name}** (ID: ${serverId}).`, + components: [], }); - throw error; - } + } else { + await confirmation.update({ + content: 'Server leave cancelled.', + components: [], + }); + } + } catch (error) { + await interaction.editReply({ + content: + 'No response received within 30 seconds, cancelling server leave.', + components: [], + }); + throw error; + } } async function handleCheckSubcommand(client, interaction) { - const serverIds = interaction.options - .getString('server-ids') - .split(',') - .map((id) => id.trim()); - if (serverIds.length > 10) { - return await interaction.editReply( - 'Please provide 10 or fewer server IDs to check.' - ); - } + const serverIds = interaction.options + .getString('server-ids') + .split(',') + .map((id) => id.trim()); + if (serverIds.length > 10) { + return await interaction.editReply( + 'Please provide 10 or fewer server IDs to check.' + ); + } - const results = await Promise.all( - serverIds.map(async (serverId) => { - const guild = client.guilds.cache.get(serverId); - if (guild) { - const owner = await guild.fetchOwner(); - return new EmbedBuilder() - .setTitle(`Server Information: ${guild.name}`) - .setDescription(`The bot is in this server.`) - .addFields( - { name: 'Server ID', value: serverId, inline: true }, - { name: 'Owner', value: owner.user.tag, inline: true }, - { - name: 'Members', - value: guild.memberCount.toString(), - inline: true, - }, - { - name: 'Created At', - value: guild.createdAt.toDateString(), - inline: true, - }, - { - name: 'Boost Level', - value: guild.premiumTier.toString(), - inline: true, - } - ) - .setColor(mconfig.embedColorSuccess); - } else { - return new EmbedBuilder() - .setTitle(`Server Not Found`) - .setDescription( - `The bot is not in a server with the ID ${serverId}.` - ) - .setColor(mconfig.embedColorError); - } - }) - ); + const results = await Promise.all( + serverIds.map(async (serverId) => { + const guild = client.guilds.cache.get(serverId); + if (guild) { + const owner = await guild.fetchOwner(); + return new EmbedBuilder() + .setTitle(`Server Information: ${guild.name}`) + .setDescription(`The bot is in this server.`) + .addFields( + { name: 'Server ID', value: serverId, inline: true }, + { name: 'Owner', value: owner.user.tag, inline: true }, + { + name: 'Members', + value: guild.memberCount.toString(), + inline: true, + }, + { + name: 'Created At', + value: guild.createdAt.toDateString(), + inline: true, + }, + { + name: 'Boost Level', + value: guild.premiumTier.toString(), + inline: true, + } + ) + .setColor(mconfig.embedColorSuccess); + } else { + return new EmbedBuilder() + .setTitle(`Server Not Found`) + .setDescription(`The bot is not in a server with the ID ${serverId}.`) + .setColor(mconfig.embedColorError); + } + }) + ); - await interaction.editReply({ embeds: results }); + await interaction.editReply({ embeds: results }); } async function handleUserSubcommand(client, interaction) { - const user = interaction.options.getUser('user'); - const userServers = client.guilds.cache.filter( - (guild) => guild.ownerId === user.id - ); - const serverCount = userServers.size; + const user = interaction.options.getUser('user'); + const userServers = client.guilds.cache.filter( + (guild) => guild.ownerId === user.id + ); + const serverCount = userServers.size; - const serverList = userServers - .map( - (guild) => - `- ${guild.name} (ID: ${guild.id}, Members: ${guild.memberCount})` - ) - .join('\n'); + const serverList = userServers + .map( + (guild) => + `- ${guild.name} (ID: ${guild.id}, Members: ${guild.memberCount})` + ) + .join('\n'); - const embed = new EmbedBuilder() - .setTitle(`Servers Owned by ${user.username}`) - .setDescription( - `User ${user.username} (ID: ${user.id}) owns **${serverCount}** server(s) that the bot is in.` - ) - .setColor(mconfig.embedColorSuccess) - .addFields({ - name: 'Server List', - value: serverList || 'No servers found.', - }) - .setFooter({ - text: `Requested by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }); + const embed = new EmbedBuilder() + .setTitle(`Servers Owned by ${user.username}`) + .setDescription( + `User ${user.username} (ID: ${user.id}) owns **${serverCount}** server(s) that the bot is in.` + ) + .setColor(mconfig.embedColorSuccess) + .addFields({ + name: 'Server List', + value: serverList || 'No servers found.', + }) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }); - await interaction.editReply({ embeds: [embed] }); + await interaction.editReply({ embeds: [embed] }); } diff --git a/src/commands/developer/setPrefix.js b/src/commands/developer/setPrefix.js index 6dc88ab..14c8921 100644 --- a/src/commands/developer/setPrefix.js +++ b/src/commands/developer/setPrefix.js @@ -2,106 +2,106 @@ import { UserPrefix } from '../../schemas/prefix.js'; import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; const DISALLOWED_PREFIXES = [ - '/', - '\\', - '@', - '#', - '$', - '&', - '(', - ')', - '{', - '}', - '[', - ']', + '/', + '\\', + '@', + '#', + '$', + '&', + '(', + ')', + '{', + '}', + '[', + ']', ]; export default { - data: new SlashCommandBuilder() - .setName('setprefix') - .setDescription('Set a prefix for a user or remove it (dev only)') - .addUserOption((option) => - option - .setName('user') - .setDescription('The user to set the prefix for') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('prefix') - .setDescription('The new prefix to set (use "noprefix" to remove)') - .setRequired(true) - ), - userPermissions: [], - botPermissions: [], - category: 'Misc', - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: true, - category: 'Devloper', + data: new SlashCommandBuilder() + .setName('setprefix') + .setDescription('Set a prefix for a user or remove it (dev only)') + .addUserOption((option) => + option + .setName('user') + .setDescription('The user to set the prefix for') + .setRequired(true) + ) + .addStringOption((option) => + option + .setName('prefix') + .setDescription('The new prefix to set (use "noprefix" to remove)') + .setRequired(true) + ), + userPermissions: [], + botPermissions: [], + category: 'Misc', + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: true, + category: 'Developer', - run: async (client, interaction) => { - try { - const targetUser = interaction.options.getUser('user'); - const newPrefix = interaction.options.getString('prefix'); + run: async (client, interaction) => { + try { + const targetUser = interaction.options.getUser('user'); + const newPrefix = interaction.options.getString('prefix'); - if (!targetUser) { - return interaction.reply({ - content: 'Please provide a valid user.', - ephemeral: true, - }); - } - - await updatePrefix(interaction, targetUser, newPrefix); - } catch (error) { - console.error('Error in setprefix command:', error); - await interaction.reply({ - content: - 'An error occurred while processing the command. Please try again later.', - ephemeral: true, - }); + if (!targetUser) { + return interaction.reply({ + content: 'Please provide a valid user.', + ephemeral: true, + }); } - }, + + await updatePrefix(interaction, targetUser, newPrefix); + } catch (error) { + console.error('Error in setprefix command:', error); + await interaction.reply({ + content: + 'An error occurred while processing the command. Please try again later.', + ephemeral: true, + }); + } + }, }; async function updatePrefix(interaction, targetUser, newPrefix) { - if (DISALLOWED_PREFIXES.includes(newPrefix) && newPrefix !== 'noprefix') { - return interaction.reply({ - content: `The prefix "${newPrefix}" is not allowed as it may conflict with Discord or bot functionality.`, - ephemeral: true, - }); - } + if (DISALLOWED_PREFIXES.includes(newPrefix) && newPrefix !== 'noprefix') { + return interaction.reply({ + content: `The prefix "${newPrefix}" is not allowed as it may conflict with Discord or bot functionality.`, + ephemeral: true, + }); + } - const finalPrefix = newPrefix === 'noprefix' ? '' : newPrefix; - let updateData; - let responseMessage; + const finalPrefix = newPrefix === 'noprefix' ? '' : newPrefix; + let updateData; + let responseMessage; - if (newPrefix === 'noprefix') { - updateData = { exemptFromPrefix: true, prefix: '' }; - responseMessage = `Prefix for ${targetUser.tag} has been removed and they are now exempt from using a prefix.`; - } else { - updateData = { exemptFromPrefix: false, prefix: finalPrefix }; - responseMessage = `Prefix for ${targetUser.tag} has been updated to \`${finalPrefix}\`.`; - } + if (newPrefix === 'noprefix') { + updateData = { exemptFromPrefix: true, prefix: '' }; + responseMessage = `Prefix for ${targetUser.tag} has been removed and they are now exempt from using a prefix.`; + } else { + updateData = { exemptFromPrefix: false, prefix: finalPrefix }; + responseMessage = `Prefix for ${targetUser.tag} has been updated to \`${finalPrefix}\`.`; + } - try { - await UserPrefix.findOneAndUpdate( - { userId: targetUser.id }, - { $set: { ...updateData, userId: targetUser.id } }, - { upsert: true, new: true, runValidators: true } - ); + try { + await UserPrefix.findOneAndUpdate( + { userId: targetUser.id }, + { $set: { ...updateData, userId: targetUser.id } }, + { upsert: true, new: true, runValidators: true } + ); - await interaction.reply({ - content: responseMessage, - ephemeral: true, - }); - } catch (error) { - console.error('Error updating prefix:', error); - await interaction.reply({ - content: - 'An error occurred while updating the prefix. Please try again later.', - ephemeral: true, - }); - } + await interaction.reply({ + content: responseMessage, + ephemeral: true, + }); + } catch (error) { + console.error('Error updating prefix:', error); + await interaction.reply({ + content: + 'An error occurred while updating the prefix. Please try again later.', + ephemeral: true, + }); + } } diff --git a/src/commands/economy/balance.js b/src/commands/economy/balance.js index 1d2d896..5c2984d 100644 --- a/src/commands/economy/balance.js +++ b/src/commands/economy/balance.js @@ -5,56 +5,56 @@ import { Balance } from '../../schemas/economy.js'; import emoji from '../../config/emoji.js'; export default { - data: new SlashCommandBuilder() - .setName('balance') - .setDescription('Check your balance.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nwfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('balance') + .setDescription('Check your balance.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nwfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - const coin = emoji.coin; - const userId = interaction.user.id; + run: async (client, interaction) => { + const coin = emoji.coin; + const userId = interaction.user.id; - // Fetch the user's balance from the database - let userBalance = await Balance.findOne({ userId }); + // Fetch the user's balance from the database + let userBalance = await Balance.findOne({ userId }); - // If the user does not exist in the database, create a new entry - if (!userBalance) { - userBalance = new Balance({ userId }); - await userBalance.save(); - } + // If the user does not exist in the database, create a new entry + if (!userBalance) { + userBalance = new Balance({ userId }); + await userBalance.save(); + } - // Create an embed to display the user's balance - const balanceEmbed = new EmbedBuilder() - .setColor('#00FF00') // Green color for positive information - .setTitle(`Balance Information`) - .setDescription(`Here is your current balance information:`) - .addFields( - { - name: 'Wallet Balance', - value: `${userBalance.balance} ${coin} clienterr coins`, - inline: true, - }, - { - name: 'Bank Balance', - value: `${userBalance.bank} ${coin} clienterr coins`, - inline: true, - } - ) - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + // Create an embed to display the user's balance + const balanceEmbed = new EmbedBuilder() + .setColor('#00FF00') // Green color for positive information + .setTitle(`Balance Information`) + .setDescription(`Here is your current balance information:`) + .addFields( + { + name: 'Wallet Balance', + value: `${userBalance.balance} ${coin} clienterr coins`, + inline: true, + }, + { + name: 'Bank Balance', + value: `${userBalance.bank} ${coin} clienterr coins`, + inline: true, + } + ) + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - // Reply with the embed containing the user's balance - await interaction.reply({ embeds: [balanceEmbed] }); - }, + // Reply with the embed containing the user's balance + await interaction.reply({ embeds: [balanceEmbed] }); + }, }; diff --git a/src/commands/economy/bank.js b/src/commands/economy/bank.js index c22da50..3ff5470 100644 --- a/src/commands/economy/bank.js +++ b/src/commands/economy/bank.js @@ -4,49 +4,49 @@ import { Balance } from '../../schemas/economy.js'; import emoji from '../../config/emoji.js'; export default { - data: new SlashCommandBuilder() - .setName('bank') - .setDescription('Your bank balance.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nwfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('bank') + .setDescription('Your bank balance.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nwfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - try { - const coin = emoji.coin; - const userId = interaction.user.id; + run: async (client, interaction) => { + try { + const coin = emoji.coin; + const userId = interaction.user.id; - let userBalance = await Balance.findOne({ userId }); + let userBalance = await Balance.findOne({ userId }); - if (!userBalance) { - userBalance = new Balance({ userId }); - await userBalance.save(); - } + if (!userBalance) { + userBalance = new Balance({ userId }); + await userBalance.save(); + } - const bankEmbed = new EmbedBuilder() - .setColor('#0000FF') // Blue color for bank information - .setTitle('Bank Balance Information') - .setDescription(`Here is your current bank balance:`) - .addFields({ - name: `Bank Balance ${coin}`, - value: `${userBalance.bank} coins`, - inline: true, - }) - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + const bankEmbed = new EmbedBuilder() + .setColor('#0000FF') // Blue color for bank information + .setTitle('Bank Balance Information') + .setDescription(`Here is your current bank balance:`) + .addFields({ + name: `Bank Balance ${coin}`, + value: `${userBalance.bank} coins`, + inline: true, + }) + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - await interaction.reply({ embeds: [bankEmbed] }); - } catch (error) { - throw error; - } - }, + await interaction.reply({ embeds: [bankEmbed] }); + } catch (error) { + throw error; + } + }, }; diff --git a/src/commands/economy/beg.js b/src/commands/economy/beg.js index 0db426a..fd1aa6c 100644 --- a/src/commands/economy/beg.js +++ b/src/commands/economy/beg.js @@ -4,76 +4,75 @@ import { SlashCommandBuilder, EmbedBuilder } from 'discord.js'; import { Balance } from '../../schemas/economy.js'; export default { - data: new SlashCommandBuilder() - .setName('beg') - .setDescription('Beg for some money.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 10, // Adjust the cooldown as necessary - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('beg') + .setDescription('Beg for some money.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 10, // Adjust the cooldown as necessary + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - const userId = interaction.user.id; - const emoji = '🙏'; // Using a praying hands emoji for begging - const minAmount = 1; // Minimum amount to be received - const maxAmount = 10; // Maximum amount to be received - const hourlyCooldown = 60 * 60 * 1000; // 1 hour in milliseconds + run: async (client, interaction) => { + const userId = interaction.user.id; + const emoji = '🙏'; // Using a praying hands emoji for begging + const minAmount = 1; // Minimum amount to be received + const maxAmount = 10; // Maximum amount to be received + const hourlyCooldown = 60 * 60 * 1000; // 1 hour in milliseconds - // Fetch the user's balance from the database - let userBalance = await Balance.findOne({ userId }); + // Fetch the user's balance from the database + let userBalance = await Balance.findOne({ userId }); - // If the user does not exist in the database, create a new entry - if (!userBalance) { - userBalance = new Balance({ userId }); - } + // If the user does not exist in the database, create a new entry + if (!userBalance) { + userBalance = new Balance({ userId }); + } - const now = Date.now(); - if ( - userBalance.lastBeg && - now - userBalance.lastBeg.getTime() < hourlyCooldown - ) { - const timeLeft = - hourlyCooldown - (now - userBalance.lastBeg.getTime()); - const minutes = Math.floor(timeLeft / (1000 * 60)); - const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); - return interaction.reply( - `You have already begged in this hour. Please try again in ${minutes} minutes and ${seconds} seconds.` - ); - } + const now = Date.now(); + if ( + userBalance.lastBeg && + now - userBalance.lastBeg.getTime() < hourlyCooldown + ) { + const timeLeft = hourlyCooldown - (now - userBalance.lastBeg.getTime()); + const minutes = Math.floor(timeLeft / (1000 * 60)); + const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); + return interaction.reply( + `You have already begged in this hour. Please try again in ${minutes} minutes and ${seconds} seconds.` + ); + } - // Generate a random amount for the beg - const amount = - Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; + // Generate a random amount for the beg + const amount = + Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; - // Update the user's balance and last beg time - userBalance.balance += amount; - userBalance.lastBeg = new Date(); - await userBalance.save(); + // Update the user's balance and last beg time + userBalance.balance += amount; + userBalance.lastBeg = new Date(); + await userBalance.save(); - // Create an embed to display the result of the beg command - const begEmbed = new EmbedBuilder() - .setColor('#00FF00') // Green color to indicate success - .setTitle('Begging Results') - .setDescription( - `${emoji} You begged and received ${amount} clienterr coins!` - ) - .addFields({ - name: 'New Balance', - value: `${userBalance.balance} clienterr coins`, - inline: true, - }) - .setFooter({ - text: `Beg by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + // Create an embed to display the result of the beg command + const begEmbed = new EmbedBuilder() + .setColor('#00FF00') // Green color to indicate success + .setTitle('Begging Results') + .setDescription( + `${emoji} You begged and received ${amount} clienterr coins!` + ) + .addFields({ + name: 'New Balance', + value: `${userBalance.balance} clienterr coins`, + inline: true, + }) + .setFooter({ + text: `Beg by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - // Reply with the embed - await interaction.reply({ embeds: [begEmbed] }); - }, + // Reply with the embed + await interaction.reply({ embeds: [begEmbed] }); + }, }; diff --git a/src/commands/economy/coinflip.js b/src/commands/economy/coinflip.js index 15562d1..06fca12 100644 --- a/src/commands/economy/coinflip.js +++ b/src/commands/economy/coinflip.js @@ -6,112 +6,109 @@ import { config } from '../../config/config.js'; import mconfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('coinflip') - .setDescription( - 'Gamble a specified amount of clienterr coins by flipping a virtual clienterr coin.' - ) - .addStringOption((option) => - option - .setName('roll_result') - .setDescription('Your choice: "heads" or "tails".') - .setRequired(true) - .addChoices( - { name: 'Heads', value: 'heads' }, - { name: 'Tails', value: 'tails' } - ) - ) - .addIntegerOption((option) => - option - .setName('gamble_amount') - .setDescription('The amount of clienterr coins you want to gamble.') - .setMinValue(1) - .setMaxValue(30) - .setRequired(true) - ) - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 10, - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('coinflip') + .setDescription( + 'Gamble a specified amount of clienterr coins by flipping a virtual clienterr coin.' + ) + .addStringOption((option) => + option + .setName('roll_result') + .setDescription('Your choice: "heads" or "tails".') + .setRequired(true) + .addChoices( + { name: 'Heads', value: 'heads' }, + { name: 'Tails', value: 'tails' } + ) + ) + .addIntegerOption((option) => + option + .setName('gamble_amount') + .setDescription('The amount of clienterr coins you want to gamble.') + .setMinValue(1) + .setMaxValue(30) + .setRequired(true) + ) + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 10, + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - const userId = interaction.user.id; - const rollResult = interaction.options.getString('roll_result'); - const gambleAmount = interaction.options.getInteger('gamble_amount'); - const hourlyCooldown = 10 * 60 * 1000; // 1 hour in milliseconds + run: async (client, interaction) => { + const userId = interaction.user.id; + const rollResult = interaction.options.getString('roll_result'); + const gambleAmount = interaction.options.getInteger('gamble_amount'); + const hourlyCooldown = 10 * 60 * 1000; // 1 hour in milliseconds - if (gambleAmount < 1 || gambleAmount > 25) { - const embed = new EmbedBuilder().setDescription( - 'The bet amount must be between 1 and 25 clienterr coins.' - ); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } + if (gambleAmount < 1 || gambleAmount > 25) { + const embed = new EmbedBuilder().setDescription( + 'The bet amount must be between 1 and 25 clienterr coins.' + ); + return interaction.reply({ embeds: [embed], ephemeral: true }); + } - // Fetch the user's balance from the database - let userBalance = await Balance.findOne({ userId }); + // Fetch the user's balance from the database + let userBalance = await Balance.findOne({ userId }); - // If the user does not exist in the database, create a new entry - if (!userBalance) { - userBalance = new Balance({ userId }); - } + // If the user does not exist in the database, create a new entry + if (!userBalance) { + userBalance = new Balance({ userId }); + } - const now = Date.now(); - if ( - userBalance.lastcoin && - now - userBalance.lastcoin.getTime() < hourlyCooldown - ) { - const timeLeft = - hourlyCooldown - (now - userBalance.lastcoin.getTime()); - const minutes = Math.floor(timeLeft / (1000 * 60)); - const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); - return interaction.reply( - `You have already flipped a coin in this hour. Please try again in ${minutes} minutes and ${seconds} seconds.` - ); - } + const now = Date.now(); + if ( + userBalance.lastcoin && + now - userBalance.lastcoin.getTime() < hourlyCooldown + ) { + const timeLeft = hourlyCooldown - (now - userBalance.lastcoin.getTime()); + const minutes = Math.floor(timeLeft / (1000 * 60)); + const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); + return interaction.reply( + `You have already flipped a coin in this hour. Please try again in ${minutes} minutes and ${seconds} seconds.` + ); + } - // Check if the user has enough balance to gamble - if (userBalance.balance < gambleAmount) { - const embed = new EmbedBuilder() - .setDescription( - 'You do not have enough balance to gamble that amount.' - ) - .setColor(mconfig.embedColorError); + // Check if the user has enough balance to gamble + if (userBalance.balance < gambleAmount) { + const embed = new EmbedBuilder() + .setDescription('You do not have enough balance to gamble that amount.') + .setColor(mconfig.embedColorError); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } + return interaction.reply({ embeds: [embed], ephemeral: true }); + } - // Generate a random result (heads or tails) - const coinResult = Math.random() < 0.5 ? 'heads' : 'tails'; + // Generate a random result (heads or tails) + const coinResult = Math.random() < 0.5 ? 'heads' : 'tails'; - // Determine the outcome of the clienterr coinflip - let outcome; - let color; - if (rollResult === coinResult) { - userBalance.balance += gambleAmount; - outcome = `You won ${gambleAmount} clienterr coins!`; - color = mconfig.embedColorSuccess; - } else { - userBalance.balance -= gambleAmount; - outcome = `You lost ${gambleAmount} clienterr coins.`; - color = mconfig.embedColorError; - } + // Determine the outcome of the clienterr coinflip + let outcome; + let color; + if (rollResult === coinResult) { + userBalance.balance += gambleAmount; + outcome = `You won ${gambleAmount} clienterr coins!`; + color = mconfig.embedColorSuccess; + } else { + userBalance.balance -= gambleAmount; + outcome = `You lost ${gambleAmount} clienterr coins.`; + color = mconfig.embedColorError; + } - // Save the updated balance and last coin flip time to the database - userBalance.lastcoin = new Date(); - await userBalance.save(); + // Save the updated balance and last coin flip time to the database + userBalance.lastcoin = new Date(); + await userBalance.save(); - const embed = new EmbedBuilder() - .setDescription( - `${outcome} The clienterr coin landed on ${coinResult}. Your new balance is ${userBalance.balance} clienterr coins.` - ) - .setColor(color); + const embed = new EmbedBuilder() + .setDescription( + `${outcome} The clienterr coin landed on ${coinResult}. Your new balance is ${userBalance.balance} clienterr coins.` + ) + .setColor(color); - // Reply with the outcome of the clienterr coinflip - await interaction.reply({ embeds: [embed] }); - }, + // Reply with the outcome of the clienterr coinflip + await interaction.reply({ embeds: [embed] }); + }, }; diff --git a/src/commands/economy/crime.js b/src/commands/economy/crime.js index 1209bec..e094ad1 100644 --- a/src/commands/economy/crime.js +++ b/src/commands/economy/crime.js @@ -2,79 +2,78 @@ import { SlashCommandBuilder, EmbedBuilder } from 'discord.js'; import { Balance } from '../../schemas/economy.js'; export default { - data: new SlashCommandBuilder() - .setName('crime') - .setDescription('Commit a crime and risk it all.'), - userPermissions: [], - botPermissions: [], - cooldown: 20, // 20 seconds cooldown - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('crime') + .setDescription('Commit a crime and risk it all.'), + userPermissions: [], + botPermissions: [], + cooldown: 20, // 20 seconds cooldown + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - const userId = interaction.user.id; - const CrimeCooldown = 6 * 60 * 60 * 1000; // 6 hours in milliseconds - const MinimumBalanceToCommitCrime = 20; // Minimum balance required to commit a crime + run: async (client, interaction) => { + const userId = interaction.user.id; + const CrimeCooldown = 6 * 60 * 60 * 1000; // 6 hours in milliseconds + const MinimumBalanceToCommitCrime = 20; // Minimum balance required to commit a crime - // Fetch user's balance - let userBalance = await Balance.findOne({ userId }); - if (!userBalance) { - userBalance = new Balance({ userId, balance: 0 }); - } + // Fetch user's balance + let userBalance = await Balance.findOne({ userId }); + if (!userBalance) { + userBalance = new Balance({ userId, balance: 0 }); + } - // Check if the user has enough balance to commit a crime - if (userBalance.balance < MinimumBalanceToCommitCrime) { - return interaction.reply( - `You need at least ${MinimumBalanceToCommitCrime} clienterr coins to commit a crime. Your current balance is ${userBalance.balance} clienterr coins.` - ); - } + // Check if the user has enough balance to commit a crime + if (userBalance.balance < MinimumBalanceToCommitCrime) { + return interaction.reply( + `You need at least ${MinimumBalanceToCommitCrime} clienterr coins to commit a crime. Your current balance is ${userBalance.balance} clienterr coins.` + ); + } - const now = Date.now(); - if ( - userBalance.lastCrime && - now - userBalance.lastCrime.getTime() < CrimeCooldown - ) { - const timeLeft = - CrimeCooldown - (now - userBalance.lastCrime.getTime()); - const minutes = Math.floor(timeLeft / (1000 * 60)); - const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); - return interaction.reply( - `You have already committed a crime. Please try again in ${minutes} minutes and ${seconds} seconds.` - ); - } + const now = Date.now(); + if ( + userBalance.lastCrime && + now - userBalance.lastCrime.getTime() < CrimeCooldown + ) { + const timeLeft = CrimeCooldown - (now - userBalance.lastCrime.getTime()); + const minutes = Math.floor(timeLeft / (1000 * 60)); + const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); + return interaction.reply( + `You have already committed a crime. Please try again in ${minutes} minutes and ${seconds} seconds.` + ); + } - // Determine the outcome of the crime - const crimeOutcome = Math.random() < 0.6; // 60% success rate - const amount = Math.floor(Math.random() * 30) + 1; // Random amount between 1 and 30 + // Determine the outcome of the crime + const crimeOutcome = Math.random() < 0.6; // 60% success rate + const amount = Math.floor(Math.random() * 30) + 1; // Random amount between 1 and 30 - let crimeMessage = ''; - let color = ''; + let crimeMessage = ''; + let color = ''; - if (crimeOutcome) { - userBalance.balance += amount; - crimeMessage = `Success! You committed a crime and earned ${amount} clienterr coins. Your balance is now ${userBalance.balance} clienterr coins.`; - color = '#00FF00'; // Green for success - } else { - // Ensure balance doesn't go negative - userBalance.balance = Math.max(userBalance.balance - amount, 0); - crimeMessage = `Failure! You got caught and lost ${amount} clienterr coins. Your balance is now ${userBalance.balance} clienterr coins.`; - color = '#FF0000'; // Red for failure - } + if (crimeOutcome) { + userBalance.balance += amount; + crimeMessage = `Success! You committed a crime and earned ${amount} clienterr coins. Your balance is now ${userBalance.balance} clienterr coins.`; + color = '#00FF00'; // Green for success + } else { + // Ensure balance doesn't go negative + userBalance.balance = Math.max(userBalance.balance - amount, 0); + crimeMessage = `Failure! You got caught and lost ${amount} clienterr coins. Your balance is now ${userBalance.balance} clienterr coins.`; + color = '#FF0000'; // Red for failure + } - // Save the updated balance to the database - userBalance.lastCrime = new Date(); - await userBalance.save(); + // Save the updated balance to the database + userBalance.lastCrime = new Date(); + await userBalance.save(); - // Create the embed message - const rEmbed = new EmbedBuilder() - .setColor(color) - .setTitle('Crime Commitment') - .setDescription(crimeMessage); + // Create the embed message + const rEmbed = new EmbedBuilder() + .setColor(color) + .setTitle('Crime Commitment') + .setDescription(crimeMessage); - // Reply with the embed message - await interaction.reply({ embeds: [rEmbed] }); - }, + // Reply with the embed message + await interaction.reply({ embeds: [rEmbed] }); + }, }; diff --git a/src/commands/economy/daily.js b/src/commands/economy/daily.js index 7501469..3d8ef97 100644 --- a/src/commands/economy/daily.js +++ b/src/commands/economy/daily.js @@ -4,78 +4,76 @@ import { SlashCommandBuilder, EmbedBuilder } from 'discord.js'; import { Balance } from '../../schemas/economy.js'; export default { - data: new SlashCommandBuilder() - .setName('daily') - .setDescription('Claim your daily reward.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('daily') + .setDescription('Claim your daily reward.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - const userId = interaction.user.id; - const emoji = '🎁'; // Using a gift emoji for the daily reward - const minAmount = 1; // Minimum amount to be received - const maxAmount = 25; // Maximum amount to be received - const dailyCooldown = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + run: async (client, interaction) => { + const userId = interaction.user.id; + const emoji = '🎁'; // Using a gift emoji for the daily reward + const minAmount = 1; // Minimum amount to be received + const maxAmount = 25; // Maximum amount to be received + const dailyCooldown = 24 * 60 * 60 * 1000; // 24 hours in milliseconds - // Fetch the user's balance from the database - let userBalance = await Balance.findOne({ userId }); + // Fetch the user's balance from the database + let userBalance = await Balance.findOne({ userId }); - if (!userBalance) { - userBalance = new Balance({ userId }); - } + if (!userBalance) { + userBalance = new Balance({ userId }); + } - const now = Date.now(); - if ( - userBalance.lastDaily && - now - new Date(userBalance.lastDaily).getTime() < dailyCooldown - ) { - const timeLeft = - dailyCooldown - (now - new Date(userBalance.lastDaily).getTime()); - const hours = Math.floor(timeLeft / (1000 * 60 * 60)); - const minutes = Math.floor( - (timeLeft % (1000 * 60 * 60)) / (1000 * 60) - ); + const now = Date.now(); + if ( + userBalance.lastDaily && + now - new Date(userBalance.lastDaily).getTime() < dailyCooldown + ) { + const timeLeft = + dailyCooldown - (now - new Date(userBalance.lastDaily).getTime()); + const hours = Math.floor(timeLeft / (1000 * 60 * 60)); + const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle('Daily Reward') - .setDescription( - `You have already claimed your daily reward. Please try again in ${hours} hours and ${minutes} minutes.` - ); + const embed = new EmbedBuilder() + .setColor(0xff0000) + .setTitle('Daily Reward') + .setDescription( + `You have already claimed your daily reward. Please try again in ${hours} hours and ${minutes} minutes.` + ); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } + return interaction.reply({ embeds: [embed], ephemeral: true }); + } - // Generate a random amount for the daily reward - const amount = - Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; + // Generate a random amount for the daily reward + const amount = + Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; - // Update the user's balance and last daily claim time - userBalance.balance += amount; - userBalance.lastDaily = new Date(); - await userBalance.save(); + // Update the user's balance and last daily claim time + userBalance.balance += amount; + userBalance.lastDaily = new Date(); + await userBalance.save(); - // Create an embed for the response - const embed = new EmbedBuilder() - .setColor(0x00ff00) - .setTitle('Daily Reward') - .setDescription( - `${emoji} You have claimed your daily reward of ${amount} coins!` - ) - .addFields({ - name: 'New Balance', - value: `${userBalance.balance} coins`, - inline: true, - }); + // Create an embed for the response + const embed = new EmbedBuilder() + .setColor(0x00ff00) + .setTitle('Daily Reward') + .setDescription( + `${emoji} You have claimed your daily reward of ${amount} coins!` + ) + .addFields({ + name: 'New Balance', + value: `${userBalance.balance} coins`, + inline: true, + }); - // Reply with the embed - await interaction.reply({ embeds: [embed] }); - }, + // Reply with the embed + await interaction.reply({ embeds: [embed] }); + }, }; diff --git a/src/commands/economy/deposit.js b/src/commands/economy/deposit.js index 9028152..7fde8f0 100644 --- a/src/commands/economy/deposit.js +++ b/src/commands/economy/deposit.js @@ -4,79 +4,79 @@ import { SlashCommandBuilder, EmbedBuilder } from 'discord.js'; import { Balance, Transaction } from '../../schemas/economy.js'; export default { - data: new SlashCommandBuilder() - .setName('deposit') - .setDescription( - 'Deposit a specified amount of your balance into your bank account.' - ) - .addIntegerOption((option) => - option - .setName('amount') - .setDescription('The amount to deposit') - .setRequired(true) - ) - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nwfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', + data: new SlashCommandBuilder() + .setName('deposit') + .setDescription( + 'Deposit a specified amount of your balance into your bank account.' + ) + .addIntegerOption((option) => + option + .setName('amount') + .setDescription('The amount to deposit') + .setRequired(true) + ) + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nwfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', - run: async (client, interaction) => { - const userId = interaction.user.id; - const amount = interaction.options.getInteger('amount'); + run: async (client, interaction) => { + const userId = interaction.user.id; + const amount = interaction.options.getInteger('amount'); - if (amount <= 0) { - return interaction.reply('Please enter a valid amount to deposit.'); - } + if (amount <= 0) { + return interaction.reply('Please enter a valid amount to deposit.'); + } - // Fetch the user's balance from the database - let userBalance = await Balance.findOne({ userId }); + // Fetch the user's balance from the database + let userBalance = await Balance.findOne({ userId }); - // If the user does not exist in the database, create a new entry - if (!userBalance) { - userBalance = new Balance({ userId }); - await userBalance.save(); - } + // If the user does not exist in the database, create a new entry + if (!userBalance) { + userBalance = new Balance({ userId }); + await userBalance.save(); + } - // Check if the user has enough balance to deposit - if (userBalance.balance < amount) { - return interaction.reply( - 'You do not have enough balance to deposit that amount.' - ); - } + // Check if the user has enough balance to deposit + if (userBalance.balance < amount) { + return interaction.reply( + 'You do not have enough balance to deposit that amount.' + ); + } - // Update the user's balance and bank amount - userBalance.balance -= amount; - userBalance.bank += amount; - await userBalance.save(); - const depositTransaction = new Transaction({ - userId: userId, - type: 'deposit', - amount: amount, - }); - depositTransaction.save(); + // Update the user's balance and bank amount + userBalance.balance -= amount; + userBalance.bank += amount; + await userBalance.save(); + const depositTransaction = new Transaction({ + userId: userId, + type: 'deposit', + amount: amount, + }); + depositTransaction.save(); - // Create an embed to display the deposit information - const embed = new EmbedBuilder() - .setTitle('Deposit Successful') - .setDescription( - `You have deposited ${amount} clienterr coins into your bank.` - ) - .setColor('#00FF00') - .setFooter({ - text: `Deposit by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }) - .setTimestamp(); + // Create an embed to display the deposit information + const embed = new EmbedBuilder() + .setTitle('Deposit Successful') + .setDescription( + `You have deposited ${amount} clienterr coins into your bank.` + ) + .setColor('#00FF00') + .setFooter({ + text: `Deposit by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }) + .setTimestamp(); - // Send the embed as the reply - await interaction.reply({ embeds: [embed] }); - }, + // Send the embed as the reply + await interaction.reply({ embeds: [embed] }); + }, }; diff --git a/src/commands/economy/hourly.js b/src/commands/economy/hourly.js index b15f658..1c92ae5 100644 --- a/src/commands/economy/hourly.js +++ b/src/commands/economy/hourly.js @@ -2,76 +2,76 @@ import { SlashCommandBuilder, EmbedBuilder } from 'discord.js'; import { Balance } from '../../schemas/economy.js'; export default { - data: new SlashCommandBuilder() - .setName('hourly') - .setDescription('Claim your hourly reward.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 500, - nwfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('hourly') + .setDescription('Claim your hourly reward.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 500, + nwfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - const userId = interaction.user.id; - const emoji = '⏳'; // Using an hourglass emoji for the hourly reward - const minAmount = 1; // Minimum amount to be received - const maxAmount = 7; // Maximum amount to be received - const hourlyCooldown = 60 * 60 * 1000; // 1 hour in milliseconds + run: async (client, interaction) => { + const userId = interaction.user.id; + const emoji = '⏳'; // Using an hourglass emoji for the hourly reward + const minAmount = 1; // Minimum amount to be received + const maxAmount = 7; // Maximum amount to be received + const hourlyCooldown = 60 * 60 * 1000; // 1 hour in milliseconds - // Fetch the user's balance from the database - let userBalance = await Balance.findOne({ userId }); + // Fetch the user's balance from the database + let userBalance = await Balance.findOne({ userId }); - // If the user does not exist in the database, create a new entry - if (!userBalance) { - userBalance = new Balance({ userId }); - } + // If the user does not exist in the database, create a new entry + if (!userBalance) { + userBalance = new Balance({ userId }); + } - // Check if the user has already claimed their hourly reward - const now = Date.now(); - if ( - userBalance.lastHourly && - now - userBalance.lastHourly.getTime() < hourlyCooldown - ) { - const timeLeft = - hourlyCooldown - (now - userBalance.lastHourly.getTime()); - const minutes = Math.floor(timeLeft / (1000 * 60)); - const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); - return interaction.reply( - `You have already claimed your hourly reward. Please try again in ${minutes} minutes and ${seconds} seconds.` - ); - } + // Check if the user has already claimed their hourly reward + const now = Date.now(); + if ( + userBalance.lastHourly && + now - userBalance.lastHourly.getTime() < hourlyCooldown + ) { + const timeLeft = + hourlyCooldown - (now - userBalance.lastHourly.getTime()); + const minutes = Math.floor(timeLeft / (1000 * 60)); + const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); + return interaction.reply( + `You have already claimed your hourly reward. Please try again in ${minutes} minutes and ${seconds} seconds.` + ); + } - // Generate a random amount for the hourly reward - const amount = - Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; + // Generate a random amount for the hourly reward + const amount = + Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; - // Update the user's balance and last hourly claim time - userBalance.balance += amount; - userBalance.lastHourly = new Date(); - await userBalance.save(); + // Update the user's balance and last hourly claim time + userBalance.balance += amount; + userBalance.lastHourly = new Date(); + await userBalance.save(); - // Create an embed to display the reward information - const embed = new EmbedBuilder() - .setTitle('Hourly Reward') - .setDescription( - `${emoji} You have claimed your hourly reward of ${amount} clienterr coins!` - ) - .setColor('#00FF00') - .setFooter({ - text: `Requested by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }) - .setTimestamp(); + // Create an embed to display the reward information + const embed = new EmbedBuilder() + .setTitle('Hourly Reward') + .setDescription( + `${emoji} You have claimed your hourly reward of ${amount} clienterr coins!` + ) + .setColor('#00FF00') + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }) + .setTimestamp(); - // Send the embed as the reply - await interaction.reply({ embeds: [embed] }); - }, + // Send the embed as the reply + await interaction.reply({ embeds: [embed] }); + }, }; diff --git a/src/commands/economy/inventory.js b/src/commands/economy/inventory.js index a02e970..05077b2 100644 --- a/src/commands/economy/inventory.js +++ b/src/commands/economy/inventory.js @@ -2,57 +2,57 @@ import { SlashCommandBuilder } from 'discord.js'; import { Inventory, Item } from '../../schemas/economy.js'; export default { - data: new SlashCommandBuilder() - .setName('inventory') - .setDescription('Displays your inventory.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, - - run: async (client, interaction) => { - const userId = interaction.user.id; - - // Fetch the user's inventory from the database - const userInventory = await Inventory.findOne({ userId }); - - if (!userInventory || userInventory.items.length === 0) { - return interaction.reply('Your inventory is empty.'); + data: new SlashCommandBuilder() + .setName('inventory') + .setDescription('Displays your inventory.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, + + run: async (client, interaction) => { + const userId = interaction.user.id; + + // Fetch the user's inventory from the database + const userInventory = await Inventory.findOne({ userId }); + + if (!userInventory || userInventory.items.length === 0) { + return interaction.reply('Your inventory is empty.'); + } + + // Fetch item details for each item in the inventory + const itemDetailsPromises = userInventory.items.map( + async (inventoryItem) => { + const item = await Item.findOne({ + itemId: inventoryItem.itemId, + }); + return { + name: item.name, + description: item.description, + quantity: inventoryItem.quantity, + }; } - - // Fetch item details for each item in the inventory - const itemDetailsPromises = userInventory.items.map( - async (inventoryItem) => { - const item = await Item.findOne({ - itemId: inventoryItem.itemId, - }); - return { - name: item.name, - description: item.description, - quantity: inventoryItem.quantity, - }; - } - ); - - const itemDetails = await Promise.all(itemDetailsPromises); - - // Build the response as an Embed - const embed = { - color: 0x00ff00, // Green color for positive response - title: 'Your Inventory', - description: 'Here is a list of items in your inventory:', - fields: itemDetails.map((item) => ({ - name: item.name, - value: `Quantity: ${item.quantity}\nDescription: ${item.description}`, - })), - }; - - // Reply with the user's inventory as an Embed - interaction.reply({ embeds: [embed] }); - }, + ); + + const itemDetails = await Promise.all(itemDetailsPromises); + + // Build the response as an Embed + const embed = { + color: 0x00ff00, // Green color for positive response + title: 'Your Inventory', + description: 'Here is a list of items in your inventory:', + fields: itemDetails.map((item) => ({ + name: item.name, + value: `Quantity: ${item.quantity}\nDescription: ${item.description}`, + })), + }; + + // Reply with the user's inventory as an Embed + interaction.reply({ embeds: [embed] }); + }, }; diff --git a/src/commands/economy/items.js b/src/commands/economy/items.js index 67f2208..23d4541 100644 --- a/src/commands/economy/items.js +++ b/src/commands/economy/items.js @@ -5,49 +5,49 @@ import { Item } from '../../schemas/economy.js'; import pagination from '../../utils/buttonPagination.js'; export default { - data: new SlashCommandBuilder() - .setName('items') - .setDescription('Displays all items in the economy system.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', + data: new SlashCommandBuilder() + .setName('items') + .setDescription('Displays all items in the economy system.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', - run: async (client, interaction) => { - // Fetch all items from the database - const items = await Item.find(); + run: async (client, interaction) => { + // Fetch all items from the database + const items = await Item.find(); - if (items.length === 0) { - return interaction.reply('No items found in the economy system.'); - } + if (items.length === 0) { + return interaction.reply('No items found in the economy system.'); + } - const pages = items.map((item, index) => { - return new EmbedBuilder() - .setColor('#0099ff') - .setTitle('Economy Item') - .setFooter({ text: `Item ${index + 1} of ${items.length}` }) - .addFields( - { name: 'Name', value: item.name, inline: true }, - { name: 'ID', value: item.itemId.toString(), inline: true }, - { - name: 'Price', - value: `${item.price} clienterr coins`, - inline: true, - }, - { - name: 'Description', - value: item.description, - inline: false, - }, - { name: 'Category', value: item.category, inline: true } - ); - }); + const pages = items.map((item, index) => { + return new EmbedBuilder() + .setColor('#0099ff') + .setTitle('Economy Item') + .setFooter({ text: `Item ${index + 1} of ${items.length}` }) + .addFields( + { name: 'Name', value: item.name, inline: true }, + { name: 'ID', value: item.itemId.toString(), inline: true }, + { + name: 'Price', + value: `${item.price} clienterr coins`, + inline: true, + }, + { + name: 'Description', + value: item.description, + inline: false, + }, + { name: 'Category', value: item.category, inline: true } + ); + }); - // Use the pagination utility to handle pagination - await pagination(interaction, pages); - }, + // Use the pagination utility to handle pagination + await pagination(interaction, pages); + }, }; diff --git a/src/commands/economy/leaderboard.js b/src/commands/economy/leaderboard.js index 32da8ee..d99e1d3 100644 --- a/src/commands/economy/leaderboard.js +++ b/src/commands/economy/leaderboard.js @@ -6,114 +6,114 @@ const ITEMS_PER_PAGE = 12; const LEADERBOARD_LIMIT = 50; export default { - data: new SlashCommandBuilder() - .setName('leaderboard') - .setDescription( - 'Displays the leaderboard based on user balances and bank.' - ), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', + data: new SlashCommandBuilder() + .setName('leaderboard') + .setDescription( + 'Displays the leaderboard based on user balances and bank.' + ), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', - run: async (client, interaction) => { - await interaction.deferReply(); + run: async (client, interaction) => { + await interaction.deferReply(); - try { - const balances = await fetchBalances(); - if (balances.length === 0) { - return interaction.editReply('No users found in the leaderboard.'); - } + try { + const balances = await fetchBalances(); + if (balances.length === 0) { + return interaction.editReply('No users found in the leaderboard.'); + } - const leaderboardEntries = await createLeaderboardEntries( - client, - balances - ); - const pages = createLeaderboardPages(leaderboardEntries); + const leaderboardEntries = await createLeaderboardEntries( + client, + balances + ); + const pages = createLeaderboardPages(leaderboardEntries); - await pagination(interaction, pages); - } catch (error) { - console.error('Error in leaderboard command:', error); - await interaction.editReply( - 'An error occurred while fetching the leaderboard.' - ); - } - }, + await pagination(interaction, pages); + } catch (error) { + console.error('Error in leaderboard command:', error); + await interaction.editReply( + 'An error occurred while fetching the leaderboard.' + ); + } + }, }; async function fetchBalances() { - return Balance.aggregate([ - { - $project: { - userId: 1, - balance: 1, - bank: 1, - totalBalance: { $add: ['$balance', '$bank'] }, - }, + return Balance.aggregate([ + { + $project: { + userId: 1, + balance: 1, + bank: 1, + totalBalance: { $add: ['$balance', '$bank'] }, }, - { $sort: { totalBalance: -1 } }, - { $limit: LEADERBOARD_LIMIT }, - ]).exec(); + }, + { $sort: { totalBalance: -1 } }, + { $limit: LEADERBOARD_LIMIT }, + ]).exec(); } async function createLeaderboardEntries(client, balances) { - return Promise.all( - balances.map(async (balance, index) => { - const userTag = await fetchUserTag(client, balance.userId); - return { - index: index + 1, - userTag, - totalBalance: balance.totalBalance, - wallet: balance.balance, - bank: balance.bank, - }; - }) - ); + return Promise.all( + balances.map(async (balance, index) => { + const userTag = await fetchUserTag(client, balance.userId); + return { + index: index + 1, + userTag, + totalBalance: balance.totalBalance, + wallet: balance.balance, + bank: balance.bank, + }; + }) + ); } async function fetchUserTag(client, userId) { - try { - const user = await client.users.fetch(userId); - return user.tag; - } catch { - return 'Unknown User'; - } + try { + const user = await client.users.fetch(userId); + return user.tag; + } catch { + return 'Unknown User'; + } } function createLeaderboardPages(entries) { - const pages = []; - for (let i = 0; i < entries.length; i += ITEMS_PER_PAGE) { - const pageEntries = entries.slice(i, i + ITEMS_PER_PAGE); - const embed = createPageEmbed(pageEntries, i, entries.length); - pages.push(embed); - } - return pages; + const pages = []; + for (let i = 0; i < entries.length; i += ITEMS_PER_PAGE) { + const pageEntries = entries.slice(i, i + ITEMS_PER_PAGE); + const embed = createPageEmbed(pageEntries, i, entries.length); + pages.push(embed); + } + return pages; } function createPageEmbed(entries, startIndex, totalEntries) { - const fields = entries.map((entry) => ({ - name: `${getRankEmoji(entry.index)} **${entry.index}. ${entry.userTag}**`, - value: formatEntryValue(entry), - inline: true, - })); + const fields = entries.map((entry) => ({ + name: `${getRankEmoji(entry.index)} **${entry.index}. ${entry.userTag}**`, + value: formatEntryValue(entry), + inline: true, + })); - return new EmbedBuilder() - .setTitle('🏆 Leaderboard') - .addFields(fields) - .setColor(0xffd700) - .setFooter({ - text: `Page ${Math.floor(startIndex / ITEMS_PER_PAGE) + 1} of ${Math.ceil(totalEntries / ITEMS_PER_PAGE)}`, - }); + return new EmbedBuilder() + .setTitle('🏆 Leaderboard') + .addFields(fields) + .setColor(0xffd700) + .setFooter({ + text: `Page ${Math.floor(startIndex / ITEMS_PER_PAGE) + 1} of ${Math.ceil(totalEntries / ITEMS_PER_PAGE)}`, + }); } function getRankEmoji(rank) { - const emojis = ['🥇', '🥈', '🥉']; - return emojis[rank - 1] || '🏅'; + const emojis = ['🥇', '🥈', '🥉']; + return emojis[rank - 1] || '🏅'; } function formatEntryValue(entry) { - return `Total: ${entry.totalBalance.toLocaleString()} coins\nWallet: ${entry.wallet.toLocaleString()} | Bank: ${entry.bank.toLocaleString()}`; + return `Total: ${entry.totalBalance.toLocaleString()} coins\nWallet: ${entry.wallet.toLocaleString()} | Bank: ${entry.bank.toLocaleString()}`; } diff --git a/src/commands/economy/shop.js b/src/commands/economy/shop.js index 501ba8e..51113d1 100644 --- a/src/commands/economy/shop.js +++ b/src/commands/economy/shop.js @@ -1,74 +1,74 @@ /** @format */ import { - SlashCommandBuilder, - StringSelectMenuBuilder, - ActionRowBuilder, - EmbedBuilder, + SlashCommandBuilder, + StringSelectMenuBuilder, + ActionRowBuilder, + EmbedBuilder, } from 'discord.js'; import { Item } from '../../schemas/economy.js'; import { config } from '../../config/config.js'; import mconfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('shop') - .setDescription('Buy items from the shop.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('shop') + .setDescription('Buy items from the shop.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - try { - const items = await Item.find().lean(); + run: async (client, interaction) => { + try { + const items = await Item.find().lean(); - if (!items || items.length === 0) { - return interaction.reply({ - content: '❌ No items available in the shop.', - ephemeral: true, - }); - } + if (!items || items.length === 0) { + return interaction.reply({ + content: '❌ No items available in the shop.', + ephemeral: true, + }); + } - const shopSSM = new StringSelectMenuBuilder() - .setCustomId('shop') - .setPlaceholder('Select an item to buy') - .addOptions( - items.map((item) => ({ - label: `${item.emoji} ${item.name}`, - description: `${item.description} - ${item.price} clienterr coin(s)`, - value: item.itemId, - })) - ); + const shopSSM = new StringSelectMenuBuilder() + .setCustomId('shop') + .setPlaceholder('Select an item to buy') + .addOptions( + items.map((item) => ({ + label: `${item.emoji} ${item.name}`, + description: `${item.description} - ${item.price} clienterr coin(s)`, + value: item.itemId, + })) + ); - const row = new ActionRowBuilder().addComponents(shopSSM); + const row = new ActionRowBuilder().addComponents(shopSSM); - const embed = new EmbedBuilder() - .setColor('#0099ff') - .setTitle('🛍️ Shop') - .setDescription('Select an item to buy from the menu below.') - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + const embed = new EmbedBuilder() + .setColor('#0099ff') + .setTitle('🛍️ Shop') + .setDescription('Select an item to buy from the menu below.') + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - await interaction.reply({ - embeds: [embed], - components: [row], - ephemeral: true, - }); - } catch (error) { - await interaction.reply({ - content: '❌ There was a problem retrieving shop items.', - ephemeral: true, - }); - throw error; - } - }, + await interaction.reply({ + embeds: [embed], + components: [row], + ephemeral: true, + }); + } catch (error) { + await interaction.reply({ + content: '❌ There was a problem retrieving shop items.', + ephemeral: true, + }); + throw error; + } + }, }; diff --git a/src/commands/economy/slots.js b/src/commands/economy/slots.js index b17ae99..29a0e2f 100644 --- a/src/commands/economy/slots.js +++ b/src/commands/economy/slots.js @@ -19,7 +19,9 @@ const spinningAnimation = ''; export default { data: new SlashCommandBuilder() .setName('slots') - .setDescription('Bet your money in the slot machine! Earn up to 10x your money!') + .setDescription( + 'Bet your money in the slot machine! Earn up to 10x your money!' + ) .addStringOption((option) => option .setName('bet') diff --git a/src/commands/economy/weekly.js b/src/commands/economy/weekly.js index 19b9ed6..5965a6c 100644 --- a/src/commands/economy/weekly.js +++ b/src/commands/economy/weekly.js @@ -4,66 +4,64 @@ import { SlashCommandBuilder } from 'discord.js'; import { Balance } from '../../schemas/economy.js'; export default { - data: new SlashCommandBuilder() - .setName('weekly') - .setDescription('Claim your weekly reward.') - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, // Not used as we have a weekly cooldown - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - prefix: true, + data: new SlashCommandBuilder() + .setName('weekly') + .setDescription('Claim your weekly reward.') + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, // Not used as we have a weekly cooldown + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', + prefix: true, - run: async (client, interaction) => { - const userId = interaction.user.id; - const emoji = '🎁'; // Using a gift emoji for the weekly reward - const minAmount = 20; // Minimum amount to be received - const maxAmount = 60; // Maximum amount to be received - const weeklyCooldown = 7 * 24 * 60 * 60 * 1000; // One week in milliseconds + run: async (client, interaction) => { + const userId = interaction.user.id; + const emoji = '🎁'; // Using a gift emoji for the weekly reward + const minAmount = 20; // Minimum amount to be received + const maxAmount = 60; // Maximum amount to be received + const weeklyCooldown = 7 * 24 * 60 * 60 * 1000; // One week in milliseconds - // Fetch the user's balance from the database - let userBalance = await Balance.findOne({ userId }); + // Fetch the user's balance from the database + let userBalance = await Balance.findOne({ userId }); - // If the user does not exist in the database, create a new entry - if (!userBalance) { - userBalance = new Balance({ userId, balance: 0, lastWeekly: null }); - } + // If the user does not exist in the database, create a new entry + if (!userBalance) { + userBalance = new Balance({ userId, balance: 0, lastWeekly: null }); + } - // Check if the user has already claimed their weekly reward - const now = Date.now(); - const timeSinceLastWeekly = - now - (userBalance.lastWeekly ? userBalance.lastWeekly.getTime() : 0); - if (userBalance.lastWeekly && timeSinceLastWeekly < weeklyCooldown) { - const timeLeft = weeklyCooldown - timeSinceLastWeekly; - const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); - const hours = Math.floor( - (timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) - ); - const minutes = Math.floor( - (timeLeft % (1000 * 60 * 60)) / (1000 * 60) - ); - return interaction.reply({ - content: `You have already claimed your weekly reward. Please try again in ${days} days, ${hours} hours, and ${minutes} minutes.`, - ephemeral: true, - }); - } + // Check if the user has already claimed their weekly reward + const now = Date.now(); + const timeSinceLastWeekly = + now - (userBalance.lastWeekly ? userBalance.lastWeekly.getTime() : 0); + if (userBalance.lastWeekly && timeSinceLastWeekly < weeklyCooldown) { + const timeLeft = weeklyCooldown - timeSinceLastWeekly; + const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); + const hours = Math.floor( + (timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) + ); + const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); + return interaction.reply({ + content: `You have already claimed your weekly reward. Please try again in ${days} days, ${hours} hours, and ${minutes} minutes.`, + ephemeral: true, + }); + } - // Generate a random amount for the weekly reward - const amount = - Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; + // Generate a random amount for the weekly reward + const amount = + Math.floor(Math.random() * (maxAmount - minAmount + 1)) + minAmount; - // Update the user's balance and last weekly claim time - userBalance.balance += amount; - userBalance.lastWeekly = new Date(); - await userBalance.save(); + // Update the user's balance and last weekly claim time + userBalance.balance += amount; + userBalance.lastWeekly = new Date(); + await userBalance.save(); - // Reply with the amount received - await interaction.reply({ - content: `${emoji} You have claimed your weekly reward of ${amount} coins! Your new balance is ${userBalance.balance} coins.`, - ephemeral: true, - }); - }, + // Reply with the amount received + await interaction.reply({ + content: `${emoji} You have claimed your weekly reward of ${amount} coins! Your new balance is ${userBalance.balance} coins.`, + ephemeral: true, + }); + }, }; diff --git a/src/commands/economy/withdraw.js b/src/commands/economy/withdraw.js index dfee776..4afaeff 100644 --- a/src/commands/economy/withdraw.js +++ b/src/commands/economy/withdraw.js @@ -5,110 +5,110 @@ import { Balance, Transaction } from '../../schemas/economy.js'; import mconfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('withdraw') - .setDescription( - 'Withdraw a specified amount of clienterr coins from your bank account.' - ) - .addIntegerOption((option) => - option - .setName('amount') - .setDescription('The amount to withdraw') - .setRequired(true) - .setMinValue(1) - ) - .toJSON(), - userPermissions: [], - botPermissions: [], - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, - category: 'economy', - - run: async (client, interaction) => { - const userId = interaction.user.id; - const amount = interaction.options.getInteger('amount'); + data: new SlashCommandBuilder() + .setName('withdraw') + .setDescription( + 'Withdraw a specified amount of clienterr coins from your bank account.' + ) + .addIntegerOption((option) => + option + .setName('amount') + .setDescription('The amount to withdraw') + .setRequired(true) + .setMinValue(1) + ) + .toJSON(), + userPermissions: [], + botPermissions: [], + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: false, + category: 'economy', - // Fetch or create the user's balance - let userBalance = await Balance.findOneAndUpdate( - { userId }, - { $setOnInsert: { balance: 0, bank: 0 } }, - { upsert: true, new: true } - ); + run: async (client, interaction) => { + const userId = interaction.user.id; + const amount = interaction.options.getInteger('amount'); - if (userBalance.bank < amount) { - return interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Insufficient Funds', - `You only have ${userBalance.bank} clienterr coins in your bank.` - ), - ], - ephemeral: true, - }); - } + // Fetch or create the user's balance + let userBalance = await Balance.findOneAndUpdate( + { userId }, + { $setOnInsert: { balance: 0, bank: 0 } }, + { upsert: true, new: true } + ); - // Update the user's balance - userBalance = await Balance.findOneAndUpdate( - { userId }, - { $inc: { bank: -amount, balance: amount } }, - { new: true } - ); - - // Record the transaction - await Transaction.create({ - userId, - type: 'withdraw', - amount, + if (userBalance.bank < amount) { + return interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Insufficient Funds', + `You only have ${userBalance.bank} clienterr coins in your bank.` + ), + ], + ephemeral: true, }); + } - // Create and send the success embed - const embed = new EmbedBuilder() - .setTitle('💰 Withdrawal Successful') - .setDescription( - `You have withdrawn ${amount} clienterr coins from your bank.` - ) - .setColor(mconfig.embedColorSuccess) - .addFields( - { - name: '🏦 New Bank Balance', - value: userBalance.bank.toString(), - inline: true, - }, - { - name: '👛 New Wallet Balance', - value: userBalance.balance.toString(), - inline: true, - } - ) - .setFooter({ - text: `Withdrawal by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), - }) - .setTimestamp(); + // Update the user's balance + userBalance = await Balance.findOneAndUpdate( + { userId }, + { $inc: { bank: -amount, balance: amount } }, + { new: true } + ); - await interaction.reply({ embeds: [embed] }); - }, -}; + // Record the transaction + await Transaction.create({ + userId, + type: 'withdraw', + amount, + }); -function createErrorEmbed(interaction, title, description) { - return new EmbedBuilder() - .setColor(mconfig.embedColorError) - .setTitle(`❌ ${title}`) - .setDescription(description) + // Create and send the success embed + const embed = new EmbedBuilder() + .setTitle('💰 Withdrawal Successful') + .setDescription( + `You have withdrawn ${amount} clienterr coins from your bank.` + ) + .setColor(mconfig.embedColorSuccess) + .addFields( + { + name: '🏦 New Bank Balance', + value: userBalance.bank.toString(), + inline: true, + }, + { + name: '👛 New Wallet Balance', + value: userBalance.balance.toString(), + inline: true, + } + ) .setFooter({ - text: `Requested by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), + text: `Withdrawal by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), }) .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); + }, +}; + +function createErrorEmbed(interaction, title, description) { + return new EmbedBuilder() + .setColor(mconfig.embedColorError) + .setTitle(`❌ ${title}`) + .setDescription(description) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ + format: 'png', + dynamic: true, + size: 1024, + }), + }) + .setTimestamp(); } diff --git a/src/commands/image/cat.js b/src/commands/image/cat.js index 8757177..5695fef 100644 --- a/src/commands/image/cat.js +++ b/src/commands/image/cat.js @@ -7,38 +7,36 @@ import { config } from '../../config/config.js'; import mconfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('cat') - .setDescription('send random cat img') + data: new SlashCommandBuilder() + .setName('cat') + .setDescription('send random cat img') - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, - userPermissionsBitField: [], - bot: [], - run: async (client, interaction) => { - try { - const res = await axios.get( - 'https://api.thecatapi.com/v1/images/search' - ); - const imgurl = res.data[0]?.url; - if (!imgurl) { - throw new Error('Failed to get Cat Img .'); - } - const rembed = new EmbedBuilder() - .setColor(mconfig.embedColorSuccess) - .setDescription('Random cat img 😺') - .setImage(imgurl); - - await interaction.reply({ embeds: [rembed] }); - } catch (error) { - console.error('err while gitting cat img ', error); + userPermissionsBitField: [], + bot: [], + run: async (client, interaction) => { + try { + const res = await axios.get('https://api.thecatapi.com/v1/images/search'); + const imgurl = res.data[0]?.url; + if (!imgurl) { + throw new Error('Failed to get Cat Img .'); } - }, + const rembed = new EmbedBuilder() + .setColor(mconfig.embedColorSuccess) + .setDescription('Random cat img 😺') + .setImage(imgurl); + + await interaction.reply({ embeds: [rembed] }); + } catch (error) { + console.error('err while gitting cat img ', error); + } + }, }; diff --git a/src/commands/image/clown.js b/src/commands/image/clown.js index c90f000..e306e8b 100644 --- a/src/commands/image/clown.js +++ b/src/commands/image/clown.js @@ -4,51 +4,51 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('clown') - .setDescription("Generates an image of a user's avatar as a clown") - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want to turn into a clown') - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - - // Generate the Clown image - const img = await new DIG.Clown().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('clown.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating Clown image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('clown') + .setDescription("Generates an image of a user's avatar as a clown") + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want to turn into a clown') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + + // Generate the Clown image + const img = await new DIG.Clown().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('clown.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Clown image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/delete.js b/src/commands/image/delete.js index 3ee31fd..5b87484 100644 --- a/src/commands/image/delete.js +++ b/src/commands/image/delete.js @@ -4,51 +4,51 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('delete') - .setDescription("Generates an image of a user's avatar being deleted") - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want to delete') - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - - // Generate the Delete image - const img = await new DIG.Delete().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('delete.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating Delete image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('delete') + .setDescription("Generates an image of a user's avatar being deleted") + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want to delete') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + + // Generate the Delete image + const img = await new DIG.Delete().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('delete.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Delete image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/dog.js b/src/commands/image/dog.js index e134d7b..999d78a 100644 --- a/src/commands/image/dog.js +++ b/src/commands/image/dog.js @@ -7,36 +7,36 @@ import { config } from '../../config/config.js'; import mconfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('dog') - .setDescription('send random dog image') - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, + data: new SlashCommandBuilder() + .setName('dog') + .setDescription('send random dog image') + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, - userPermissionsBitField: [], - bot: [], - run: async (client, interaction) => { - try { - const res = await axios.get('https://dog.ceo/api/breeds/image/random'); - const imgurl = res.data.message; + userPermissionsBitField: [], + bot: [], + run: async (client, interaction) => { + try { + const res = await axios.get('https://dog.ceo/api/breeds/image/random'); + const imgurl = res.data.message; - if (!imgurl) { - throw new Error('Failed to get Dog Image .'); - } - const rembed = new EmbedBuilder() - .setColor(mconfig.embedColorSuccess) - .setDescription('Random Dog Image 🐕🐶') - .setImage(imgurl); - - await interaction.reply({ embeds: [rembed] }); - } catch (error) { - console.error('err while gitting dog Image ', error); + if (!imgurl) { + throw new Error('Failed to get Dog Image .'); } - }, + const rembed = new EmbedBuilder() + .setColor(mconfig.embedColorSuccess) + .setDescription('Random Dog Image 🐕🐶') + .setImage(imgurl); + + await interaction.reply({ embeds: [rembed] }); + } catch (error) { + console.error('err while gitting dog Image ', error); + } + }, }; diff --git a/src/commands/image/doublestonks.js b/src/commands/image/doublestonks.js index 3e32332..fd024cc 100644 --- a/src/commands/image/doublestonks.js +++ b/src/commands/image/doublestonks.js @@ -4,70 +4,63 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('doublestonks') - .setDescription( - "Applies a 'Double Stonks' effect using two users' avatars" - ) - .addUserOption((option) => - option - .setName('user1') - .setDescription('First user whose avatar you want to use') - .setRequired(true) - ) - .addUserOption((option) => - option - .setName('user2') - .setDescription('Second user whose avatar you want to use') - .setRequired(true) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, + data: new SlashCommandBuilder() + .setName('doublestonks') + .setDescription("Applies a 'Double Stonks' effect using two users' avatars") + .addUserOption((option) => + option + .setName('user1') + .setDescription('First user whose avatar you want to use') + .setRequired(true) + ) + .addUserOption((option) => + option + .setName('user2') + .setDescription('Second user whose avatar you want to use') + .setRequired(true) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, - userPermissionsBitField: [], - bot: [], + userPermissionsBitField: [], + bot: [], - run: async (client, interaction) => { - try { - await interaction.deferReply(); + run: async (client, interaction) => { + try { + await interaction.deferReply(); - const user1 = interaction.options.getUser('user1'); - const user2 = interaction.options.getUser('user2'); - const avatarUrl1 = user1.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - const avatarUrl2 = user2.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); + const user1 = interaction.options.getUser('user1'); + const user2 = interaction.options.getUser('user2'); + const avatarUrl1 = user1.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + const avatarUrl2 = user2.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); - // Generate the Double Stonks image - const img = await new DIG.DoubleStonk().getImage( - avatarUrl1, - avatarUrl2 - ); + // Generate the Double Stonks image + const img = await new DIG.DoubleStonk().getImage(avatarUrl1, avatarUrl2); - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName( - 'doublestonks.png' - ); + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('doublestonks.png'); - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating Double Stonks image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Double Stonks image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/hitler.js b/src/commands/image/hitler.js index eddb7d2..1db7806 100644 --- a/src/commands/image/hitler.js +++ b/src/commands/image/hitler.js @@ -4,51 +4,51 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('hitler') - .setDescription('Generates an image of a user with a Hitler mustache') - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want to add the effect to') - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - - // Generate the Hitler image - const img = await new DIG.Hitler().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('hitler.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating Hitler image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('hitler') + .setDescription('Generates an image of a user with a Hitler mustache') + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want to add the effect to') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + + // Generate the Hitler image + const img = await new DIG.Hitler().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('hitler.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Hitler image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/jail.js b/src/commands/image/jail.js index 64f36c8..d6968a5 100644 --- a/src/commands/image/jail.js +++ b/src/commands/image/jail.js @@ -4,51 +4,51 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('jail') - .setDescription('Generates an image of a user in jail') - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want to place in jail') - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - - // Generate the Jail image - const img = await new DIG.Jail().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('jail.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating Jail image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('jail') + .setDescription('Generates an image of a user in jail') + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want to place in jail') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + + // Generate the Jail image + const img = await new DIG.Jail().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('jail.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Jail image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/kiss.js b/src/commands/image/kiss.js index 042792e..ddf3599 100644 --- a/src/commands/image/kiss.js +++ b/src/commands/image/kiss.js @@ -4,63 +4,63 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('kiss') - .setDescription('Generates an image of one user kissing another') - .addUserOption((option) => - option - .setName('user1') - .setDescription('First user whose avatar you want to use') - .setRequired(true) - ) - .addUserOption((option) => - option - .setName('user2') - .setDescription('Second user whose avatar you want to use') - .setRequired(true) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, + data: new SlashCommandBuilder() + .setName('kiss') + .setDescription('Generates an image of one user kissing another') + .addUserOption((option) => + option + .setName('user1') + .setDescription('First user whose avatar you want to use') + .setRequired(true) + ) + .addUserOption((option) => + option + .setName('user2') + .setDescription('Second user whose avatar you want to use') + .setRequired(true) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, - userPermissionsBitField: [], - bot: [], + userPermissionsBitField: [], + bot: [], - run: async (client, interaction) => { - try { - await interaction.deferReply(); + run: async (client, interaction) => { + try { + await interaction.deferReply(); - const user1 = interaction.options.getUser('user1'); - const user2 = interaction.options.getUser('user2'); - const avatarUrl1 = user1.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - const avatarUrl2 = user2.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); + const user1 = interaction.options.getUser('user1'); + const user2 = interaction.options.getUser('user2'); + const avatarUrl1 = user1.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + const avatarUrl2 = user2.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); - // Generate the Kiss image - const img = await new DIG.Kiss().getImage(avatarUrl1, avatarUrl2); + // Generate the Kiss image + const img = await new DIG.Kiss().getImage(avatarUrl1, avatarUrl2); - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('kiss.png'); + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('kiss.png'); - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating Kiss image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Kiss image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/lisapresentation.js b/src/commands/image/lisapresentation.js index 11e395b..034f7f9 100644 --- a/src/commands/image/lisapresentation.js +++ b/src/commands/image/lisapresentation.js @@ -4,51 +4,48 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('lisapresentation') - .setDescription('Generates an image of Lisa presenting your custom text') - .addStringOption((option) => - option - .setName('text') - .setDescription('The text you want Lisa to present') - .setRequired(true) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const text = interaction.options.getString('text'); - - // Generate the Lisa Presentation image - const img = await new DIG.LisaPresentation().getImage(text); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName( - 'lisapresentation.png' - ); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error( - 'Error while generating Lisa Presentation image:', - error - ); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('lisapresentation') + .setDescription('Generates an image of Lisa presenting your custom text') + .addStringOption((option) => + option + .setName('text') + .setDescription('The text you want Lisa to present') + .setRequired(true) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const text = interaction.options.getString('text'); + + // Generate the Lisa Presentation image + const img = await new DIG.LisaPresentation().getImage(text); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName( + 'lisapresentation.png' + ); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Lisa Presentation image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/magik.js b/src/commands/image/magik.js index b9061c5..8499040 100644 --- a/src/commands/image/magik.js +++ b/src/commands/image/magik.js @@ -1,7 +1,7 @@ import { - EmbedBuilder, - SlashCommandBuilder, - ApplicationCommandType, + EmbedBuilder, + SlashCommandBuilder, + ApplicationCommandType, } from 'discord.js'; import axios from 'axios'; import mconfig from '../../config/messageConfig.js'; @@ -11,160 +11,160 @@ const TIMEOUT = 15000; const MAX_PAGES = 5; export default { - data: new SlashCommandBuilder() - .setName('magik') - .setDescription('Create multiple magik images') - .addUserOption((option) => - option - .setName('target') - .setDescription('User to magik') - .setRequired(false) - ) - .addIntegerOption((option) => - option - .setName('intensity') - .setDescription('Magik intensity (1-10)') - .setRequired(false) - .setMinValue(1) - .setMaxValue(10) - ) - .addIntegerOption((option) => - option - .setName('count') - .setDescription('Number of images to generate (1-5)') - .setRequired(false) - .setMinValue(1) - .setMaxValue(5) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - cooldown: 10, - nsfwMode: false, - testMode: false, - category: 'Image', - devOnly: false, - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { + data: new SlashCommandBuilder() + .setName('magik') + .setDescription('Create multiple magik images') + .addUserOption((option) => + option + .setName('target') + .setDescription('User to magik') + .setRequired(false) + ) + .addIntegerOption((option) => + option + .setName('intensity') + .setDescription('Magik intensity (1-10)') + .setRequired(false) + .setMinValue(1) + .setMaxValue(10) + ) + .addIntegerOption((option) => + option + .setName('count') + .setDescription('Number of images to generate (1-5)') + .setRequired(false) + .setMinValue(1) + .setMaxValue(5) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + cooldown: 10, + nsfwMode: false, + testMode: false, + category: 'Image', + devOnly: false, + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + await interaction.deferReply(); + + const targetUser = + interaction.options.getUser('target') || interaction.user; + const intensity = interaction.options.getInteger('intensity') || 5; + const count = interaction.options.getInteger('count') || 1; + + if (typeof intensity !== 'number' || typeof count !== 'number') { + return interaction.editReply('Intensity and count must be numbers.'); + } + + await generateAndSendMagik(interaction, targetUser, intensity, count); + }, + + contextMenu: { + name: 'Magik User Avatar', + type: ApplicationCommandType.User, + run: async (client, interaction) => { await interaction.deferReply(); - const targetUser = - interaction.options.getUser('target') || interaction.user; - const intensity = interaction.options.getInteger('intensity') || 5; - const count = interaction.options.getInteger('count') || 1; - - if (typeof intensity !== 'number' || typeof count !== 'number') { - return interaction.editReply('Intensity and count must be numbers.'); - } + const targetUser = interaction.targetUser; + const intensity = 5; // Default intensity for context menu + const count = 1; // Default count for context menu await generateAndSendMagik(interaction, targetUser, intensity, count); - }, - - contextMenu: { - name: 'Magik User Avatar', - type: ApplicationCommandType.User, - run: async (client, interaction) => { - await interaction.deferReply(); - - const targetUser = interaction.targetUser; - const intensity = 5; // Default intensity for context menu - const count = 1; // Default count for context menu - - await generateAndSendMagik(interaction, targetUser, intensity, count); - }, - }, + }, + }, }; async function generateAndSendMagik(interaction, targetUser, intensity, count) { - try { - const avatarURL = targetUser.displayAvatarURL({ - size: 512, - extension: 'png', - forceStatic: true, - }); - - const pages = []; - - for (let i = 0; i < count; i++) { - const response = await axios.get( - `https://nekobot.xyz/api/imagegen?type=magik&image=${encodeURIComponent( - avatarURL - )}&intensity=${intensity}`, - { timeout: TIMEOUT } - ); - - if (!response.data || !response.data.message) { - throw new Error('Failed to generate magik image.'); - } - - const magikImageURL = response.data.message; - const embed = createEmbed( - targetUser, - intensity, - magikImageURL, - interaction.user.username, - i + 1, - count - ); - pages.push(embed); + try { + const avatarURL = targetUser.displayAvatarURL({ + size: 512, + extension: 'png', + forceStatic: true, + }); + + const pages = []; + + for (let i = 0; i < count; i++) { + const response = await axios.get( + `https://nekobot.xyz/api/imagegen?type=magik&image=${encodeURIComponent( + avatarURL + )}&intensity=${intensity}`, + { timeout: TIMEOUT } + ); + + if (!response.data || !response.data.message) { + throw new Error('Failed to generate magik image.'); } - await pagination(interaction, pages, 60000); // Use 60 seconds for pagination timeout - } catch (error) { - handleError(interaction, error); - } + const magikImageURL = response.data.message; + const embed = createEmbed( + targetUser, + intensity, + magikImageURL, + interaction.user.username, + i + 1, + count + ); + pages.push(embed); + } + + await pagination(interaction, pages, 60000); // Use 60 seconds for pagination timeout + } catch (error) { + handleError(interaction, error); + } } function createEmbed( - targetUser, - intensity, - magikImageURL, - requester, - currentPage, - totalPages + targetUser, + intensity, + magikImageURL, + requester, + currentPage, + totalPages ) { - return new EmbedBuilder() - .setTitle('🎭 Magik') - .setColor(mconfig.embedColorDefault || 0x7289da) - .setImage(magikImageURL) - .setURL(magikImageURL) - .setDescription(`Magik'd image of ${targetUser.username}`) - .addFields( - { - name: '🧙‍♂️ Requested by', - value: requester, - inline: true, - }, - { - name: '🔮 Intensity', - value: `${intensity}/10`, - inline: true, - }, - { - name: '🖼️ Generated Image', - value: `[Open Image](${magikImageURL})`, - inline: true, - } - ) - .setFooter({ - text: `Powered by nekobot.xyz | Image ${currentPage}/${totalPages}`, - }) - .setTimestamp(); + return new EmbedBuilder() + .setTitle('🎭 Magik') + .setColor(mconfig.embedColorDefault || 0x7289da) + .setImage(magikImageURL) + .setURL(magikImageURL) + .setDescription(`Magik'd image of ${targetUser.username}`) + .addFields( + { + name: '🧙‍♂️ Requested by', + value: requester, + inline: true, + }, + { + name: '🔮 Intensity', + value: `${intensity}/10`, + inline: true, + }, + { + name: '🖼️ Generated Image', + value: `[Open Image](${magikImageURL})`, + inline: true, + } + ) + .setFooter({ + text: `Powered by nekobot.xyz | Image ${currentPage}/${totalPages}`, + }) + .setTimestamp(); } function handleError(interaction, error) { - console.error('Error generating magik image:', error); - const errorMessage = - error.response?.status === 524 - ? 'The image generation service is currently overloaded. Please try again later.' - : error.message || - 'Sorry, something went wrong while generating the magik image.'; - - interaction.editReply({ - content: errorMessage, - embeds: [], - components: [], - }); + console.error('Error generating magik image:', error); + const errorMessage = + error.response?.status === 524 + ? 'The image generation service is currently overloaded. Please try again later.' + : error.message || + 'Sorry, something went wrong while generating the magik image.'; + + interaction.editReply({ + content: errorMessage, + embeds: [], + components: [], + }); } diff --git a/src/commands/image/notstonk.js b/src/commands/image/notstonk.js index 71712f9..68e3745 100644 --- a/src/commands/image/notstonk.js +++ b/src/commands/image/notstonk.js @@ -4,51 +4,51 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('notstonk') - .setDescription("Applies a 'Not Stonk' effect to a user's avatar") - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want to apply the effect to') - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - - // Generate the Not Stonk image - const img = await new DIG.NotStonk().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('notstonk.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating Not Stonk image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('notstonk') + .setDescription("Applies a 'Not Stonk' effect to a user's avatar") + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want to apply the effect to') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + + // Generate the Not Stonk image + const img = await new DIG.NotStonk().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('notstonk.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Not Stonk image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/rainbow.js b/src/commands/image/rainbow.js index 022ae1e..e4769bd 100644 --- a/src/commands/image/rainbow.js +++ b/src/commands/image/rainbow.js @@ -4,53 +4,51 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('rainbow') - .setDescription("Applies a rainbow filter to a user's avatar") - .addUserOption((option) => - option - .setName('user') - .setDescription( - 'User whose avatar you want to apply the filter to:' - ) - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, // Ensures a static image (not animated) - size: 1024, // High resolution - }); - - // Generate the image with the rainbow effect - const img = await new DIG.Gay().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('rainbow.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while applying rainbow effect:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('rainbow') + .setDescription("Applies a rainbow filter to a user's avatar") + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want to apply the filter to:') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, // Ensures a static image (not animated) + size: 1024, // High resolution + }); + + // Generate the image with the rainbow effect + const img = await new DIG.Gay().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('rainbow.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while applying rainbow effect:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/rip.js b/src/commands/image/rip.js index dbec3de..7a47a7f 100644 --- a/src/commands/image/rip.js +++ b/src/commands/image/rip.js @@ -4,51 +4,51 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('rip') - .setDescription("Creates a RIP tombstone with a user's avatar") - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want on the tombstone') - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, // Ensures a static image (not animated) - size: 512, // Medium resolution to fit the tombstone - }); - - // Generate the RIP tombstone image - const img = await new DIG.Rip().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('rip.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating RIP image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('rip') + .setDescription("Creates a RIP tombstone with a user's avatar") + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want on the tombstone') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, // Ensures a static image (not animated) + size: 512, // Medium resolution to fit the tombstone + }); + + // Generate the RIP tombstone image + const img = await new DIG.Rip().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('rip.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating RIP image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/spank.js b/src/commands/image/spank.js index 32d3c3d..b10fde8 100644 --- a/src/commands/image/spank.js +++ b/src/commands/image/spank.js @@ -4,70 +4,70 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('spank') - .setDescription('Spank another user with this command!') - .addUserOption((option) => - option - .setName('target') - .setDescription('User you want to spank') - .setRequired(true) - ) - .addUserOption((option) => - option - .setName('initiator') - .setDescription('User who initiates the spanking (defaults to you)') - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, + data: new SlashCommandBuilder() + .setName('spank') + .setDescription('Spank another user with this command!') + .addUserOption((option) => + option + .setName('target') + .setDescription('User you want to spank') + .setRequired(true) + ) + .addUserOption((option) => + option + .setName('initiator') + .setDescription('User who initiates the spanking (defaults to you)') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, - userPermissionsBitField: [], - bot: [], + userPermissionsBitField: [], + bot: [], - run: async (client, interaction) => { - try { - await interaction.deferReply(); + run: async (client, interaction) => { + try { + await interaction.deferReply(); - // Get the users - const targetUser = interaction.options.getUser('target'); - const initiatorUser = - interaction.options.getUser('initiator') || interaction.user; + // Get the users + const targetUser = interaction.options.getUser('target'); + const initiatorUser = + interaction.options.getUser('initiator') || interaction.user; - // Get their avatars - const targetAvatarUrl = targetUser.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - const initiatorAvatarUrl = initiatorUser.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); + // Get their avatars + const targetAvatarUrl = targetUser.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + const initiatorAvatarUrl = initiatorUser.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); - // Generate the spank image - const img = await new DIG.Spank().getImage( - initiatorAvatarUrl, - targetAvatarUrl - ); + // Generate the spank image + const img = await new DIG.Spank().getImage( + initiatorAvatarUrl, + targetAvatarUrl + ); - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('spank.png'); + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('spank.png'); - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating spank image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating spank image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/stonk.js b/src/commands/image/stonk.js index a1ef329..603bdc3 100644 --- a/src/commands/image/stonk.js +++ b/src/commands/image/stonk.js @@ -4,51 +4,51 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('stonk') - .setDescription("Applies a 'Stonk' effect to a user's avatar") - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want to apply the effect to') - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 512, - }); - - // Generate the Stonk image - const img = await new DIG.Stonk().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('stonk.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while generating Stonk image:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('stonk') + .setDescription("Applies a 'Stonk' effect to a user's avatar") + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want to apply the effect to') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 512, + }); + + // Generate the Stonk image + const img = await new DIG.Stonk().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('stonk.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while generating Stonk image:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/trash.js b/src/commands/image/trash.js index c07ab89..84d48b2 100644 --- a/src/commands/image/trash.js +++ b/src/commands/image/trash.js @@ -4,53 +4,53 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('trash') - .setDescription("Applies a 'Trash' effect to a user's avatar") - .addUserOption((option) => - option - .setName('user') - .setDescription( - "User whose avatar you want to apply the 'Trash' effect to" - ) - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 1024, - }); - - // Generate the "Trash" effect image - const img = await new DIG.Trash().getImage(avatarUrl); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('trash.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while applying Trash effect:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('trash') + .setDescription("Applies a 'Trash' effect to a user's avatar") + .addUserOption((option) => + option + .setName('user') + .setDescription( + "User whose avatar you want to apply the 'Trash' effect to" + ) + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 1024, + }); + + // Generate the "Trash" effect image + const img = await new DIG.Trash().getImage(avatarUrl); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('trash.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while applying Trash effect:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/triggered.js b/src/commands/image/triggered.js index 122c4af..41be465 100644 --- a/src/commands/image/triggered.js +++ b/src/commands/image/triggered.js @@ -4,52 +4,50 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('triggered') - .setDescription("applies a triggered filter to a user's avatar") - .addUserOption((option) => - option - .setName('user') - .setDescription( - 'user whose avatar you want to apply the filter to:' - ) - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - userPermissionsBitField: [], - bot: [], + data: new SlashCommandBuilder() + .setName('triggered') + .setDescription("applies a triggered filter to a user's avatar") + .addUserOption((option) => + option + .setName('user') + .setDescription('user whose avatar you want to apply the filter to:') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + userPermissionsBitField: [], + bot: [], - run: async (client, interaction) => { - try { - await interaction.deferReply(); + run: async (client, interaction) => { + try { + await interaction.deferReply(); - const user = interaction.options.getUser('user') || interaction.user; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, // Ensures a static image (not animated) - size: 1024, // High resolution - }); + const user = interaction.options.getUser('user') || interaction.user; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, // Ensures a static image (not animated) + size: 1024, // High resolution + }); - // Generate the image with the rainbow effect - const img = await new DIG.Triggered().getImage(avatarUrl); + // Generate the image with the rainbow effect + const img = await new DIG.Triggered().getImage(avatarUrl); - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('rainbow.gif'); + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('rainbow.gif'); - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while applying rainbow effect:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while applying rainbow effect:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/image/wanted.js b/src/commands/image/wanted.js index 65771fa..7600fad 100644 --- a/src/commands/image/wanted.js +++ b/src/commands/image/wanted.js @@ -4,60 +4,58 @@ import { SlashCommandBuilder, AttachmentBuilder } from 'discord.js'; import DIG from 'discord-image-generation'; export default { - data: new SlashCommandBuilder() - .setName('wanted') - .setDescription("Creates a 'Wanted' poster with a user's avatar") - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want on the Wanted poster') - .setRequired(false) - ) - .addStringOption((option) => - option - .setName('currency') - .setDescription( - 'Currency symbol to use for the reward (e.g., $, €, ¥)' - ) - .setRequired(false) - ) - .setContexts([0, 1, 2]) - .setIntegrationTypes([0, 1]) - .toJSON(), - category: 'Image', - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - userPermissionsBitField: [], - bot: [], - - run: async (client, interaction) => { - try { - await interaction.deferReply(); - - const user = interaction.options.getUser('user') || interaction.user; - const currency = interaction.options.getString('currency') || '$'; - const avatarUrl = user.displayAvatarURL({ - extension: 'png', - forceStatic: true, - size: 1024, - }); - - // Generate the "Wanted" poster image - const img = await new DIG.Wanted().getImage(avatarUrl, currency); - - // Create an attachment using AttachmentBuilder - const attachment = new AttachmentBuilder(img).setName('wanted.png'); - - // Send the image as a reply - await interaction.editReply({ files: [attachment] }); - } catch (error) { - console.error('Error while creating Wanted poster:', error); - await interaction.editReply({ - content: 'Sorry, something went wrong while generating the image.', - }); - } - }, + data: new SlashCommandBuilder() + .setName('wanted') + .setDescription("Creates a 'Wanted' poster with a user's avatar") + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want on the Wanted poster') + .setRequired(false) + ) + .addStringOption((option) => + option + .setName('currency') + .setDescription('Currency symbol to use for the reward (e.g., $, €, ¥)') + .setRequired(false) + ) + .setContexts([0, 1, 2]) + .setIntegrationTypes([0, 1]) + .toJSON(), + category: 'Image', + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + userPermissionsBitField: [], + bot: [], + + run: async (client, interaction) => { + try { + await interaction.deferReply(); + + const user = interaction.options.getUser('user') || interaction.user; + const currency = interaction.options.getString('currency') || '$'; + const avatarUrl = user.displayAvatarURL({ + extension: 'png', + forceStatic: true, + size: 1024, + }); + + // Generate the "Wanted" poster image + const img = await new DIG.Wanted().getImage(avatarUrl, currency); + + // Create an attachment using AttachmentBuilder + const attachment = new AttachmentBuilder(img).setName('wanted.png'); + + // Send the image as a reply + await interaction.editReply({ files: [attachment] }); + } catch (error) { + console.error('Error while creating Wanted poster:', error); + await interaction.editReply({ + content: 'Sorry, something went wrong while generating the image.', + }); + } + }, }; diff --git a/src/commands/misc/avatar.js b/src/commands/misc/avatar.js index a526658..6b2f8f7 100644 --- a/src/commands/misc/avatar.js +++ b/src/commands/misc/avatar.js @@ -1,305 +1,302 @@ import { - EmbedBuilder, - SlashCommandBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - PermissionFlagsBits, + EmbedBuilder, + SlashCommandBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + PermissionFlagsBits, } from 'discord.js'; import { - Avatar, - AvatarRating, - AvatarChallenge, + Avatar, + AvatarRating, + AvatarChallenge, } from '../../schemas/AvatarSchema.js'; export default { - data: new SlashCommandBuilder() - .setName('avatar') - .setDescription('Show avatar of any user') - .addUserOption((option) => - option - .setName('user') - .setDescription('User whose avatar you want to see:') - .setRequired(false) - ), + data: new SlashCommandBuilder() + .setName('avatar') + .setDescription('Show avatar of any user') + .addUserOption((option) => + option + .setName('user') + .setDescription('User whose avatar you want to see:') + .setRequired(false) + ), - userPermissions: [], - botPermissions: [], - category: 'Misc', - cooldown: 5, - deleted: false, - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, + userPermissions: [], + botPermissions: [], + category: 'Misc', + cooldown: 5, + deleted: false, + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, - run: async (client, interaction) => { - try { - await interaction.deferReply(); + run: async (client, interaction) => { + try { + await interaction.deferReply(); - const user = interaction.options.getUser('user') || interaction.user; - const member = interaction.guild.members.cache.get(user.id); + const user = interaction.options.getUser('user') || interaction.user; + const member = interaction.guild.members.cache.get(user.id); - const getAvatarUrl = (userOrMember, size = 1024) => { - return userOrMember.displayAvatarURL({ - format: 'png', - dynamic: true, - size: size, - }); - }; + const getAvatarUrl = (userOrMember, size = 1024) => { + return userOrMember.displayAvatarURL({ + format: 'png', + dynamic: true, + size: size, + }); + }; - const userAvatar = getAvatarUrl(user); - const memberAvatar = member ? getAvatarUrl(member) : null; + const userAvatar = getAvatarUrl(user); + const memberAvatar = member ? getAvatarUrl(member) : null; - // Save the current avatar to the database only if it's different from the last one - let newAvatar = await Avatar.findOne({ - userId: user.id, - guildId: interaction.guild.id, - }).sort({ timestamp: -1 }); + // Save the current avatar to the database only if it's different from the last one + let newAvatar = await Avatar.findOne({ + userId: user.id, + guildId: interaction.guild.id, + }).sort({ timestamp: -1 }); - if (!newAvatar || newAvatar.avatarUrl !== userAvatar) { - newAvatar = new Avatar({ - userId: user.id, - guildId: interaction.guild.id, - avatarUrl: userAvatar, - isGlobal: true, - }); - await newAvatar.save(); - } + if (!newAvatar || newAvatar.avatarUrl !== userAvatar) { + newAvatar = new Avatar({ + userId: user.id, + guildId: interaction.guild.id, + avatarUrl: userAvatar, + isGlobal: true, + }); + await newAvatar.save(); + } - // Fetch avatar history - const avatarHistory = await Avatar.find({ - userId: user.id, - guildId: interaction.guild.id, - }) - .sort({ timestamp: -1 }) - .limit(5); + // Fetch avatar history + const avatarHistory = await Avatar.find({ + userId: user.id, + guildId: interaction.guild.id, + }) + .sort({ timestamp: -1 }) + .limit(5); - // Fetch avatar rating - const avatarRatings = await AvatarRating.find({ - avatarId: newAvatar._id, - }); - const averageRating = - avatarRatings.length > 0 - ? ( - avatarRatings.reduce( - (sum, rating) => sum + rating.rating, - 0 - ) / avatarRatings.length - ).toFixed(1) - : 'No ratings yet'; + // Fetch avatar rating + const avatarRatings = await AvatarRating.find({ + avatarId: newAvatar._id, + }); + const averageRating = + avatarRatings.length > 0 + ? ( + avatarRatings.reduce((sum, rating) => sum + rating.rating, 0) / + avatarRatings.length + ).toFixed(1) + : 'No ratings yet'; - // Fetch active avatar challenge - const activeChallenge = await AvatarChallenge.findOne({ - guildId: interaction.guild.id, - startDate: { $lte: new Date() }, - endDate: { $gte: new Date() }, - }); + // Fetch active avatar challenge + const activeChallenge = await AvatarChallenge.findOne({ + guildId: interaction.guild.id, + startDate: { $lte: new Date() }, + endDate: { $gte: new Date() }, + }); - const embed = new EmbedBuilder() - .setTitle(`${user.username}'s Avatar`) - .setDescription(`[Avatar URL](${userAvatar})`) - .setImage(userAvatar) - .setColor(member?.displayHexColor || '#eb3434') - .addFields( - { name: '🆔 User ID', value: user.id, inline: true }, - { - name: '📅 Account Created', - value: ``, - inline: true, - }, - { - name: '🎭 Activity Status', - value: member?.presence?.status || 'Offline', - inline: true, - }, - { - name: '📆 Server Join Date', - value: member - ? `` - : 'N/A', - inline: true, - }, - { - name: '👑 Roles', - value: member - ? member.roles.cache - .filter((r) => r.id !== interaction.guild.id) - .map((r) => `<@&${r.id}>`) - .join(', ') || 'None' - : 'N/A', - inline: false, - }, - { - name: '📜 Avatar History', - value: - avatarHistory - .map( - (a, index) => - `[${index === 0 ? 'Current' : `${index + 1}`}](${a.avatarUrl})` - ) - .join(' | ') || 'No history available', - inline: false, - }, - { - name: '⭐ Average Rating', - value: - averageRating + - (averageRating !== 'No ratings yet' ? '/5' : ''), - inline: true, - } - ) - .setFooter({ - text: `Requested by ${interaction.user.username}`, - iconURL: getAvatarUrl(interaction.user, 32), - }) - .setTimestamp(); + const embed = new EmbedBuilder() + .setTitle(`${user.username}'s Avatar`) + .setDescription(`[Avatar URL](${userAvatar})`) + .setImage(userAvatar) + .setColor(member?.displayHexColor || '#eb3434') + .addFields( + { name: '🆔 User ID', value: user.id, inline: true }, + { + name: '📅 Account Created', + value: ``, + inline: true, + }, + { + name: '🎭 Activity Status', + value: member?.presence?.status || 'Offline', + inline: true, + }, + { + name: '📆 Server Join Date', + value: member + ? `` + : 'N/A', + inline: true, + }, + { + name: '👑 Roles', + value: member + ? member.roles.cache + .filter((r) => r.id !== interaction.guild.id) + .map((r) => `<@&${r.id}>`) + .join(', ') || 'None' + : 'N/A', + inline: false, + }, + { + name: '📜 Avatar History', + value: + avatarHistory + .map( + (a, index) => + `[${index === 0 ? 'Current' : `${index + 1}`}](${a.avatarUrl})` + ) + .join(' | ') || 'No history available', + inline: false, + }, + { + name: '⭐ Average Rating', + value: + averageRating + (averageRating !== 'No ratings yet' ? '/5' : ''), + inline: true, + } + ) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: getAvatarUrl(interaction.user, 32), + }) + .setTimestamp(); - if (memberAvatar && memberAvatar !== userAvatar) { - embed.addFields({ - name: '🔗 Server Avatar', - value: `[View](${memberAvatar})`, - inline: true, - }); - } + if (memberAvatar && memberAvatar !== userAvatar) { + embed.addFields({ + name: '🔗 Server Avatar', + value: `[View](${memberAvatar})`, + inline: true, + }); + } - if (activeChallenge) { - embed.addFields({ - name: '🏆 Active Avatar Challenge', - value: `"${activeChallenge.title}" - Ends `, - inline: false, - }); - } + if (activeChallenge) { + embed.addFields({ + name: '🏆 Active Avatar Challenge', + value: `"${activeChallenge.title}" - Ends `, + inline: false, + }); + } - const row1 = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('🌐 View in Browser') - .setStyle(ButtonStyle.Link) - .setURL(userAvatar), - new ButtonBuilder() - .setCustomId('avatar_refresh') - .setLabel('🔄 Refresh') - .setStyle(ButtonStyle.Primary), - new ButtonBuilder() - .setCustomId('avatar_delete') - .setLabel('🗑️ Delete') - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setCustomId('avatar_compare') - .setLabel('🔍 Compare Avatars') - .setStyle(ButtonStyle.Secondary) - .setDisabled(!memberAvatar || memberAvatar === userAvatar) - ); + const row1 = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel('🌐 View in Browser') + .setStyle(ButtonStyle.Link) + .setURL(userAvatar), + new ButtonBuilder() + .setCustomId('avatar_refresh') + .setLabel('🔄 Refresh') + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId('avatar_delete') + .setLabel('🗑️ Delete') + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId('avatar_compare') + .setLabel('🔍 Compare Avatars') + .setStyle(ButtonStyle.Secondary) + .setDisabled(!memberAvatar || memberAvatar === userAvatar) + ); - const row2 = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('avatar_rate') - .setLabel('⭐ Rate Avatar') - .setStyle(ButtonStyle.Success) - ); + const row2 = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('avatar_rate') + .setLabel('⭐ Rate Avatar') + .setStyle(ButtonStyle.Success) + ); - const response = await interaction.editReply({ - embeds: [embed], - components: [row1, row2], - }); + const response = await interaction.editReply({ + embeds: [embed], + components: [row1, row2], + }); - const collector = response.createMessageComponentCollector({ - time: 60000, - }); + const collector = response.createMessageComponentCollector({ + time: 60000, + }); - collector.on('collect', async (i) => { - if (i.user.id !== interaction.user.id) { - return i.reply({ - content: 'You cannot use these buttons.', - ephemeral: true, - }); - } + collector.on('collect', async (i) => { + if (i.user.id !== interaction.user.id) { + return i.reply({ + content: 'You cannot use these buttons.', + ephemeral: true, + }); + } - switch (i.customId) { - case 'avatar_refresh': - const refreshedEmbed = EmbedBuilder.from(embed).setImage( - getAvatarUrl(user, 1024) + '?t=' + Date.now() - ); - await i.update({ embeds: [refreshedEmbed] }); - break; - case 'avatar_delete': - await i.message.delete(); - break; - case 'avatar_compare': - const compareEmbed = new EmbedBuilder() - .setTitle(`Avatar Comparison for ${user.username}`) - .setDescription('Global Avatar vs Server Avatar') - .setColor(member?.displayHexColor || '#eb3434') - .setImage(userAvatar) - .setThumbnail(memberAvatar); - await i.update({ embeds: [compareEmbed] }); - break; + switch (i.customId) { + case 'avatar_refresh': + const refreshedEmbed = EmbedBuilder.from(embed).setImage( + getAvatarUrl(user, 1024) + '?t=' + Date.now() + ); + await i.update({ embeds: [refreshedEmbed] }); + break; + case 'avatar_delete': + await i.message.delete(); + break; + case 'avatar_compare': + const compareEmbed = new EmbedBuilder() + .setTitle(`Avatar Comparison for ${user.username}`) + .setDescription('Global Avatar vs Server Avatar') + .setColor(member?.displayHexColor || '#eb3434') + .setImage(userAvatar) + .setThumbnail(memberAvatar); + await i.update({ embeds: [compareEmbed] }); + break; - case 'avatar_rate': - const existingRating = await AvatarRating.findOne({ - avatarId: newAvatar._id, - raterId: i.user.id, - }); + case 'avatar_rate': + const existingRating = await AvatarRating.findOne({ + avatarId: newAvatar._id, + raterId: i.user.id, + }); - if (existingRating) { - return i.reply({ - content: 'You have already rated this avatar.', - ephemeral: true, - }); - } + if (existingRating) { + return i.reply({ + content: 'You have already rated this avatar.', + ephemeral: true, + }); + } - const rating = Math.floor(Math.random() * 5) + 1; - const newRating = new AvatarRating({ - avatarId: newAvatar._id, - raterId: i.user.id, - rating: rating, - }); - await newRating.save(); + const rating = Math.floor(Math.random() * 5) + 1; + const newRating = new AvatarRating({ + avatarId: newAvatar._id, + raterId: i.user.id, + rating: rating, + }); + await newRating.save(); - // Update the average rating in the embed - const updatedRatings = await AvatarRating.find({ - avatarId: newAvatar._id, - }); - const newAverageRating = ( - updatedRatings.reduce((sum, r) => sum + r.rating, 0) / - updatedRatings.length - ).toFixed(1); - embed.spliceFields(-2, 1, { - name: '⭐ Average Rating', - value: `${newAverageRating}/5`, - inline: true, - }); + // Update the average rating in the embed + const updatedRatings = await AvatarRating.find({ + avatarId: newAvatar._id, + }); + const newAverageRating = ( + updatedRatings.reduce((sum, r) => sum + r.rating, 0) / + updatedRatings.length + ).toFixed(1); + embed.spliceFields(-2, 1, { + name: '⭐ Average Rating', + value: `${newAverageRating}/5`, + inline: true, + }); - await i.update({ - content: `You rated this avatar ${rating}/5 stars!`, - embeds: [embed], - }); - break; - } - }); + await i.update({ + content: `You rated this avatar ${rating}/5 stars!`, + embeds: [embed], + }); + break; + } + }); - collector.on('end', async () => { - const disabledRow1 = ActionRowBuilder.from(row1).setComponents( - row1.components.map((component) => - ButtonBuilder.from(component).setDisabled(true) - ) - ); - const disabledRow2 = ActionRowBuilder.from(row2).setComponents( - row2.components.map((component) => - ButtonBuilder.from(component).setDisabled(true) - ) - ); - await response - .edit({ components: [disabledRow1, disabledRow2] }) - .catch(() => {}); - }); - } catch (error) { - console.error('Error in avatar command:', error); - await interaction.editReply({ - content: - 'An error occurred while processing the command. Please try again later.', - ephemeral: true, - }); - } - }, + collector.on('end', async () => { + const disabledRow1 = ActionRowBuilder.from(row1).setComponents( + row1.components.map((component) => + ButtonBuilder.from(component).setDisabled(true) + ) + ); + const disabledRow2 = ActionRowBuilder.from(row2).setComponents( + row2.components.map((component) => + ButtonBuilder.from(component).setDisabled(true) + ) + ); + await response + .edit({ components: [disabledRow1, disabledRow2] }) + .catch(() => {}); + }); + } catch (error) { + console.error('Error in avatar command:', error); + await interaction.editReply({ + content: + 'An error occurred while processing the command. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/commands/misc/fact.js b/src/commands/misc/fact.js index 23d28b4..c928eb4 100644 --- a/src/commands/misc/fact.js +++ b/src/commands/misc/fact.js @@ -1,136 +1,191 @@ import { - SlashCommandBuilder, - EmbedBuilder, - ApplicationCommandType, - ContextMenuCommandBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, + SlashCommandBuilder, + EmbedBuilder, + ApplicationCommandType, + ContextMenuCommandBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, } from 'discord.js'; import axios from 'axios'; import mConfig from '../../config/messageConfig.js'; export default { - data: new SlashCommandBuilder() - .setName('fact') - .setDescription('Send a random fact') - .addStringOption((option) => - option - .setName('category') - .setDescription('Choose a fact category') - .setRequired(false) - .addChoices( - { name: 'Random', value: 'random' }, - { name: 'Today', value: 'today' }, - { name: 'Year', value: 'year' } - ) - ) - .toJSON(), - - contextMenu: new ContextMenuCommandBuilder() - .setName('Get Random Fact') - .setType(ApplicationCommandType.Message) - .toJSON(), - - userPermissionsBitField: [], - bot: [], - category: 'Misc', - cooldown: 19, - nsfwMode: false, - testMode: false, - devOnly: false, - prefix: true, - - run: async (client, interaction) => { - await interaction.deferReply(); - - try { - const category = interaction.options.getString('category') || 'random'; - const fact = await getFact(category); - - const embed = createFactEmbed(fact, category); - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('regenerate_fact') - .setLabel('Get New Fact') - .setStyle(ButtonStyle.Primary) - ); - - await interaction.editReply({ embeds: [embed], components: [row] }); - - const filter = (i) => - i.customId === 'regenerate_fact' && - i.user.id === interaction.user.id; - - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: 60000, - }); - - collector.on('collect', async (i) => { - if (i.customId === 'regenerate_fact') { - const newFact = await getFact(category); - const newEmbed = createFactEmbed(newFact, category); - await i.update({ embeds: [newEmbed], components: [row] }); - } - }); - - collector.on('end', async () => { - const disabledRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('regenerate_fact') - .setLabel('Get New Fact') - .setStyle(ButtonStyle.Primary) - .setDisabled(true) - ); - await interaction.editReply({ components: [disabledRow] }); - }); - } catch (error) { - console.error('Error in fact command:', error); - await interaction.editReply({ - content: - 'Sorry, there was an error fetching the fact. Please try again later.', - }); - } - }, - - handleContext: async (client, interaction) => { - await interaction.deferReply({ ephemeral: true }); - - try { - const fact = await getFact('random'); - const embed = createFactEmbed(fact, 'random'); - await interaction.editReply({ embeds: [embed], ephemeral: true }); - } catch (error) { - console.error('Error in fact context menu:', error); - await interaction.editReply({ - content: - 'Sorry, there was an error fetching the fact. Please try again later.', - ephemeral: true, - }); - } - }, + data: new SlashCommandBuilder() + .setName('fact') + .setDescription('Send a random fact') + .addStringOption((option) => + option + .setName('category') + .setDescription('Choose a fact category') + .setRequired(false) + .addChoices( + { name: 'Random', value: 'random' }, + { name: 'Today', value: 'today' }, + { name: 'Year', value: 'year' }, + { name: 'Science', value: 'science' }, + { name: 'History', value: 'history' }, + { name: 'Animal', value: 'animal' } + ) + ) + .toJSON(), + + contextMenu: new ContextMenuCommandBuilder() + .setName('Get Random Fact') + .setType(ApplicationCommandType.Message) + .toJSON(), + + userPermissionsBitField: [], + bot: [], + category: 'Misc', + cooldown: 15, + nsfwMode: false, + testMode: false, + devOnly: false, + prefix: true, + + run: async (client, interaction) => { + await interaction.deferReply(); + + try { + const category = interaction.options.getString('category') || 'random'; + const fact = await getFact(category); + + const embed = createFactEmbed(fact, category); + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('regenerate_fact') + .setLabel('Get New Fact') + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId('share_fact') + .setLabel('Share Fact') + .setStyle(ButtonStyle.Secondary) + ); + + await interaction.editReply({ embeds: [embed], components: [row] }); + + const filter = (i) => + (i.customId === 'regenerate_fact' || i.customId === 'share_fact') && + i.user.id === interaction.user.id; + + const collector = interaction.channel.createMessageComponentCollector({ + filter, + time: 120000, + }); + + collector.on('collect', async (i) => { + if (i.customId === 'regenerate_fact') { + const newFact = await getFact(category); + const newEmbed = createFactEmbed(newFact, category); + await i.update({ embeds: [newEmbed], components: [row] }); + } else if (i.customId === 'share_fact') { + await i.reply({ + content: `${interaction.user} shared a fact: ${fact}`, + allowedMentions: { parse: [] }, + }); + } + }); + + collector.on('end', async () => { + const disabledRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('regenerate_fact') + .setLabel('Get New Fact') + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setCustomId('share_fact') + .setLabel('Share Fact') + .setStyle(ButtonStyle.Secondary) + .setDisabled(true) + ); + await interaction.editReply({ components: [disabledRow] }); + }); + } catch (error) { + console.error('Error in fact command:', error); + await interaction.editReply({ + content: + 'Sorry, there was an error fetching the fact. Please try again later.', + }); + } + }, + + handleContext: async (client, interaction) => { + await interaction.deferReply({ ephemeral: true }); + + try { + const fact = await getFact('random'); + const embed = createFactEmbed(fact, 'random'); + await interaction.editReply({ embeds: [embed], ephemeral: true }); + } catch (error) { + console.error('Error in fact context menu:', error); + await interaction.editReply({ + content: + 'Sorry, there was an error fetching the fact. Please try again later.', + ephemeral: true, + }); + } + }, }; async function getFact(category) { - let url = 'https://uselessfacts.jsph.pl/random.json?language=en'; - - if (category === 'today') { + let url; + switch (category) { + case 'today': url = 'https://uselessfacts.jsph.pl/today.json?language=en'; - } else if (category === 'year') { + break; + case 'year': const currentYear = new Date().getFullYear(); url = `https://numbersapi.com/${currentYear}/year`; - } - - const response = await axios.get(url); - return category === 'year' ? response.data : response.data.text; + break; + case 'science': + url = + 'https://uselessfacts.jsph.pl/random.json?language=en&category=science'; + break; + case 'history': + url = + 'https://uselessfacts.jsph.pl/random.json?language=en&category=history'; + break; + case 'math': + url = 'https://numbersapi.com/random/math'; + break; + default: + url = 'https://uselessfacts.jsph.pl/random.json?language=en'; + } + + try { + const response = await axios.get(url, { timeout: 5000 }); + return category === 'animal' + ? response.data.fact + : category === 'year' || category === 'math' + ? response.data + : response.data.text; + } catch (error) { + console.error(`Error fetching fact from ${url}:`, error.message); + return 'Unable to fetch a fact at this time. Please try again later.'; + } } function createFactEmbed(fact, category) { - return new EmbedBuilder() - .setColor(mConfig.embedColorSuccess) - .setTitle(`${category.charAt(0).toUpperCase() + category.slice(1)} Fact`) - .setDescription(fact) - .setFooter({ text: 'Click the button to get a new fact' }) - .setTimestamp(); + const categoryIcons = { + random: '🎲', + today: '📅', + year: '🗓️', + science: '🔬', + history: '📜', + math: '🔢', + animal: '🐾', + }; + + return new EmbedBuilder() + .setColor(mConfig.embedColorSuccess) + .setTitle( + `${categoryIcons[category] || '❓'} ${category.charAt(0).toUpperCase() + category.slice(1)} Fact` + ) + .setDescription(fact) + .setFooter({ + text: 'Click the button to get a new fact or share this fact', + }) + .setTimestamp(); } diff --git a/src/commands/misc/guild.js b/src/commands/misc/guild.js index beed323..c300b96 100644 --- a/src/commands/misc/guild.js +++ b/src/commands/misc/guild.js @@ -1,86 +1,86 @@ import { - SlashCommandBuilder, - ModalBuilder, - TextInputBuilder, - TextInputStyle, - ActionRowBuilder, + SlashCommandBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ActionRowBuilder, } from 'discord.js'; import Guild from '../../schemas/guildSchema.js'; // Adjust the path if necessary export default { - data: new SlashCommandBuilder() - .setName('guild') - .setDescription('Guild commands') - .addSubcommand((subcommand) => - subcommand.setName('join').setDescription('Request to join the guild') - ) - .toJSON(), - nwfwMode: false, - testMode: false, - devOnly: false, - cooldown: 5, - userPermissionsBitField: [], - bot: [], - category: 'Misc', + data: new SlashCommandBuilder() + .setName('guild') + .setDescription('Guild commands') + .addSubcommand((subcommand) => + subcommand.setName('join').setDescription('Request to join the guild') + ) + .toJSON(), + nwfwMode: false, + testMode: false, + devOnly: false, + cooldown: 5, + userPermissionsBitField: [], + bot: [], + category: 'Misc', - run: async (client, interaction) => { - if (interaction.options.getSubcommand() === 'join') { - try { - const guild = await Guild.findOne(); + run: async (client, interaction) => { + if (interaction.options.getSubcommand() === 'join') { + try { + const guild = await Guild.findOne(); - if (!guild) { - return interaction.reply({ - content: 'No guild found.', - ephemeral: true, - }); - } + if (!guild) { + return interaction.reply({ + content: 'No guild found.', + ephemeral: true, + }); + } - if ( - guild.members.includes(interaction.user.id) || - guild.pendingMembers.some( - (member) => member.userId === interaction.user.id - ) - ) { - return interaction.reply({ - content: - 'You are already a member or have a pending request for the guild.', - ephemeral: true, - }); - } + if ( + guild.members.includes(interaction.user.id) || + guild.pendingMembers.some( + (member) => member.userId === interaction.user.id + ) + ) { + return interaction.reply({ + content: + 'You are already a member or have a pending request for the guild.', + ephemeral: true, + }); + } - const modal = new ModalBuilder() - .setCustomId('guild-join') - .setTitle('Guild Join Request'); + const modal = new ModalBuilder() + .setCustomId('guild-join') + .setTitle('Guild Join Request'); - const ignInput = new TextInputBuilder() - .setCustomId('ign') - .setLabel("What's your Hypixel IGN?") - .setStyle(TextInputStyle.Short) - .setRequired(true) - .setMinLength(1) - .setMaxLength(16); + const ignInput = new TextInputBuilder() + .setCustomId('ign') + .setLabel("What's your Hypixel IGN?") + .setStyle(TextInputStyle.Short) + .setRequired(true) + .setMinLength(1) + .setMaxLength(16); - const reasonInput = new TextInputBuilder() - .setCustomId('reason') - .setLabel('Why do you want to join this guild?') - .setStyle(TextInputStyle.Paragraph) - .setRequired(true) - .setMinLength(1) - .setMaxLength(200); + const reasonInput = new TextInputBuilder() + .setCustomId('reason') + .setLabel('Why do you want to join this guild?') + .setStyle(TextInputStyle.Paragraph) + .setRequired(true) + .setMinLength(1) + .setMaxLength(200); - modal.addComponents( - new ActionRowBuilder().addComponents(ignInput), - new ActionRowBuilder().addComponents(reasonInput) - ); + modal.addComponents( + new ActionRowBuilder().addComponents(ignInput), + new ActionRowBuilder().addComponents(reasonInput) + ); - await interaction.showModal(modal); - } catch (error) { - return interaction.reply({ - content: 'An error occurred while processing your request.', - ephemeral: true, - }); - throw error; - } + await interaction.showModal(modal); + } catch (error) { + return interaction.reply({ + content: 'An error occurred while processing your request.', + ephemeral: true, + }); + throw error; } - }, + } + }, }; diff --git a/src/commands/misc/help.js b/src/commands/misc/help.js index 1e4fa9e..1091c31 100644 --- a/src/commands/misc/help.js +++ b/src/commands/misc/help.js @@ -1,406 +1,414 @@ import { - EmbedBuilder, - ActionRowBuilder, - StringSelectMenuBuilder, - ButtonBuilder, - ButtonStyle, - ComponentType, - SlashCommandBuilder, + EmbedBuilder, + ActionRowBuilder, + StringSelectMenuBuilder, + ButtonBuilder, + ButtonStyle, + ComponentType, + SlashCommandBuilder, } from 'discord.js'; import getLocalCommands from '../../utils/getLocalCommands.js'; import mConfig from '../../config/messageConfig.js'; -const COMMANDS_PER_PAGE = 8; +const COMMANDS_PER_PAGE = 6; const INTERACTION_TIMEOUT = 300000; const categorizeCommands = (commands) => { - const categorized = commands.reduce((acc, cmd) => { - const category = cmd.category || 'Uncategorized'; - if (!acc[category]) acc[category] = []; - acc[category].push(cmd); - return acc; - }, {}); - - // Add a separate category for prefix commands - categorized['Prefix Commands'] = commands.filter((cmd) => cmd.prefix); - - return categorized; + const categorized = commands.reduce((acc, cmd) => { + const category = cmd.category || 'Uncategorized'; + if (!acc[category]) acc[category] = []; + acc[category].push(cmd); + return acc; + }, {}); + + categorized['Prefix Commands'] = commands.filter((cmd) => cmd.prefix); + return categorized; }; export default { - data: new SlashCommandBuilder() - .setName('help') - .setDescription('Displays information about commands') - .addStringOption((option) => - option - .setName('command') - .setDescription('Specific command to get info about') - .setRequired(false) - .setAutocomplete(true) - ) - .addStringOption((option) => - option - .setName('category') - .setDescription('View commands by category') - .setRequired(false) - .setAutocomplete(true) - ), - run: async (client, interaction) => { - try { - const localCommands = await getLocalCommands(); - const embedColor = mConfig.embedColorDefault ?? '#0099ff'; - const prefix = '!'; - - const commandOption = interaction.options.getString('command'); - const categoryOption = interaction.options.getString('category'); - - if (commandOption) { - const command = localCommands.find( - (cmd) => cmd.name.toLowerCase() === commandOption.toLowerCase() - ); - if (command) { - return await showCommandDetails( - interaction, - command, - embedColor, - prefix - ); - } else { - return await interaction.reply({ - content: `Command "${commandOption}" not found.`, - ephemeral: true, - }); - } - } - - if (categoryOption) { - const categorizedCommands = categorizeCommands(localCommands); - const category = Object.keys(categorizedCommands).find( - (cat) => cat.toLowerCase() === categoryOption.toLowerCase() - ); - if (category) { - return await showCategoryCommands( - interaction, - localCommands, - category, - embedColor, - prefix - ); - } else { - return await interaction.reply({ - content: `Category "${categoryOption}" not found.`, - }); - } - } - - return await showCommandOverview( - interaction, - localCommands, - embedColor, - prefix - ); - } catch (error) { - console.error('Error in help command:', error); - return interaction.reply({ - content: 'An error occurred while fetching help information.', - ephemeral: true, - }); + data: new SlashCommandBuilder() + .setName('help') + .setDescription('Displays information about commands') + .addStringOption((option) => + option + .setName('command') + .setDescription('Specific command to get info about') + .setRequired(false) + .setAutocomplete(true) + ) + .addStringOption((option) => + option + .setName('category') + .setDescription('View commands by category') + .setRequired(false) + .setAutocomplete(true) + ), + run: async (client, interaction) => { + try { + const localCommands = await getLocalCommands(); + const embedColor = mConfig.embedColorDefault ?? '#0099ff'; + const prefix = '!'; + + const commandOption = interaction.options.getString('command'); + const categoryOption = interaction.options.getString('category'); + + if (commandOption) { + return await showCommandDetails( + interaction, + localCommands, + commandOption, + embedColor, + prefix + ); } - }, -}; - -async function showCommandDetails(interaction, command, embedColor, prefix) { - console.log(command); - const embed = new EmbedBuilder() - .setTitle(`📖 Command: ${command.name}`) - .setDescription(command.description ?? 'No description available.') - .setColor(embedColor) - .addFields( - { - name: '🏷️ Category', - value: command.category ?? 'Uncategorized', - inline: true, - }, - { - name: '⏳ Cooldown', - value: `${command.cooldown ?? 0}s`, - inline: true, - }, - { - name: '🔒 Permissions', - value: command.userPermissions?.join(', ') ?? 'None', - inline: true, - }, - { - name: '🔧 Prefix Command', - value: command.prefix ? 'Yes' : 'No', - inline: true, - } - ); - if (command.aliases?.length > 0) { - embed.addFields({ - name: '🔀 Aliases', - value: command.aliases.join(', '), - inline: true, - }); - } + if (categoryOption) { + return await showCategoryCommands( + interaction, + localCommands, + categoryOption, + embedColor, + prefix + ); + } - if (command.usage) { - embed.addFields({ - name: '💡 Usage', - value: `\`${command.prefix ? prefix : '/'}${command.usage}\``, - inline: false, + return await showCommandOverview( + interaction, + localCommands, + embedColor, + prefix + ); + } catch (error) { + console.error('Error in help command:', error); + return interaction.reply({ + content: 'An error occurred while fetching help information.', + ephemeral: true, }); - } - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('return_to_overview') - .setLabel('Return to Overview') - .setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId('show_examples') - .setLabel('Show Examples') - .setStyle(ButtonStyle.Primary) - ); - - const response = await interaction.reply({ - embeds: [embed], - components: [row], - }); - - const collector = response.createMessageComponentCollector({ - componentType: ComponentType.Button, - time: INTERACTION_TIMEOUT, - }); - - collector.on('collect', async (i) => { - if (i.user.id !== interaction.user.id) { - return i.reply({ - content: "You can't use this button.", - ephemeral: true, - }); - } + } + }, +}; - if (i.customId === 'return_to_overview') { - const localCommands = await getLocalCommands(); - await showCommandOverview(i, localCommands, embedColor, prefix); - } else if (i.customId === 'show_examples') { - const examplesEmbed = new EmbedBuilder() - .setTitle(`📝 Examples for ${command.name}`) - .setColor(embedColor) - .setDescription( - command.examples?.join('\n') ?? 'No examples available.' - ); - await i.reply({ embeds: [examplesEmbed], ephemeral: true }); +async function showCommandDetails( + interaction, + localCommands, + commandName, + embedColor, + prefix +) { + const command = localCommands.find( + (cmd) => cmd.name.toLowerCase() === commandName.toLowerCase() + ); + + if (!command) { + return interaction.reply({ + content: `Command "${commandName}" not found.`, + ephemeral: true, + }); + } + + const embed = new EmbedBuilder() + .setTitle(`📖 Command: ${command.name}`) + .setDescription(command.description ?? 'No description available.') + .setColor(embedColor) + .addFields( + { + name: '🏷️ Category', + value: command.category ?? 'Uncategorized', + inline: true, + }, + { name: '⏳ Cooldown', value: `${command.cooldown ?? 0}s`, inline: true }, + { + name: '🔒 Permissions', + value: command.userPermissions?.join(', ') ?? 'None', + inline: true, + }, + { + name: '🔧 Prefix Command', + value: command.prefix ? 'Yes' : 'No', + inline: true, } - }); - - collector.on('end', () => { - row.components.forEach((component) => component.setDisabled(true)); - interaction.editReply({ components: [row] }); - }); + ); + + if (command.aliases?.length > 0) { + embed.addFields({ + name: '🔀 Aliases', + value: command.aliases.join(', '), + inline: true, + }); + } + + if (command.usage) { + embed.addFields({ + name: '💡 Usage', + value: `\`${command.prefix ? prefix : '/'}${command.usage}\``, + inline: false, + }); + } + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('return_to_overview') + .setLabel('Return to Overview') + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId('show_examples') + .setLabel('Show Examples') + .setStyle(ButtonStyle.Primary) + ); + + const response = await interaction.reply({ + embeds: [embed], + components: [row], + }); + + const collector = response.createMessageComponentCollector({ + componentType: ComponentType.Button, + time: INTERACTION_TIMEOUT, + }); + + collector.on('collect', async (i) => { + if (i.user.id !== interaction.user.id) { + return i.reply({ + content: "You can't use this button.", + ephemeral: true, + }); + } + + if (i.customId === 'return_to_overview') { + await showCommandOverview(i, localCommands, embedColor, prefix); + } else if (i.customId === 'show_examples') { + const examplesEmbed = new EmbedBuilder() + .setTitle(`📝 Examples for ${command.name}`) + .setColor(embedColor) + .setDescription( + command.examples?.join('\n') ?? 'No examples available.' + ); + await i.reply({ embeds: [examplesEmbed], ephemeral: true }); + } + }); + + collector.on('end', () => { + row.components.forEach((component) => component.setDisabled(true)); + interaction.editReply({ components: [row] }); + }); } async function showCategoryCommands( - interaction, - localCommands, - category, - embedColor, - prefix + interaction, + localCommands, + category, + embedColor, + prefix ) { - const categorizedCommands = categorizeCommands(localCommands); - const categoryCommands = categorizedCommands[category] ?? []; - - if (categoryCommands.length === 0) { - return interaction.reply({ - content: `No commands found in the "${category}" category.`, - ephemeral: true, + const categorizedCommands = categorizeCommands(localCommands); + const matchedCategory = Object.keys(categorizedCommands).find( + (cat) => cat.toLowerCase() === category.toLowerCase() + ); + + if (!matchedCategory) { + return interaction.reply({ + content: `Category "${category}" not found.`, + ephemeral: true, + }); + } + + const categoryCommands = categorizedCommands[matchedCategory]; + + if (categoryCommands.length === 0) { + return interaction.reply({ + content: `No commands found in the "${matchedCategory}" category.`, + ephemeral: true, + }); + } + + const pages = createCommandPages( + categoryCommands, + matchedCategory, + embedColor, + prefix + ); + let currentPage = 0; + + const row = createPaginationRow(pages.length); + + const response = await interaction.reply({ + embeds: [pages[currentPage]], + components: [row], + }); + + const collector = response.createMessageComponentCollector({ + componentType: ComponentType.Button, + time: INTERACTION_TIMEOUT, + }); + + collector.on('collect', async (i) => { + if (i.user.id !== interaction.user.id) { + return i.reply({ + content: "You can't use this button.", + ephemeral: true, }); - } - - const pages = createCommandPages( - categoryCommands, - category, - embedColor, - prefix - ); - - let currentPage = 0; - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('prev_page') - .setLabel('Previous') - .setStyle(ButtonStyle.Primary) - .setDisabled(true), - new ButtonBuilder() - .setCustomId('next_page') - .setLabel('Next') - .setStyle(ButtonStyle.Primary) - .setDisabled(pages.length <= 1), - new ButtonBuilder() - .setCustomId('return_to_overview') - .setLabel('Return to Overview') - .setStyle(ButtonStyle.Secondary) - ); - - const response = await interaction.reply({ - embeds: [pages[currentPage]], - components: [row], - }); - - const collector = response.createMessageComponentCollector({ - componentType: ComponentType.Button, - time: INTERACTION_TIMEOUT, - }); - - collector.on('collect', async (i) => { - if (i.user.id !== interaction.user.id) { - return i.reply({ - content: "You can't use this button.", - ephemeral: true, - }); - } - - if (i.customId === 'prev_page') { - currentPage = Math.max(0, currentPage - 1); - } else if (i.customId === 'next_page') { - currentPage = Math.min(pages.length - 1, currentPage + 1); - } else if (i.customId === 'return_to_overview') { - await showCommandOverview(i, localCommands, embedColor, prefix); - return; - } - - row.components[0].setDisabled(currentPage === 0); - row.components[1].setDisabled(currentPage === pages.length - 1); - - await i.update({ embeds: [pages[currentPage]], components: [row] }); - }); - - collector.on('end', () => { - row.components.forEach((component) => component.setDisabled(true)); - interaction.editReply({ components: [row] }); - }); + } + + switch (i.customId) { + case 'prev_page': + currentPage = Math.max(0, currentPage - 1); + break; + case 'next_page': + currentPage = Math.min(pages.length - 1, currentPage + 1); + break; + case 'return_to_overview': + await showCommandOverview(i, localCommands, embedColor, prefix); + return; + } + + updatePaginationRow(row, currentPage, pages.length); + await i.update({ embeds: [pages[currentPage]], components: [row] }); + }); + + collector.on('end', () => { + row.components.forEach((component) => component.setDisabled(true)); + interaction.editReply({ components: [row] }); + }); } async function showCommandOverview( - interaction, - localCommands, - embedColor, - prefix + interaction, + localCommands, + embedColor, + prefix ) { - const categorizedCommands = categorizeCommands(localCommands); - const categories = Object.keys(categorizedCommands); - - const overviewEmbed = createOverviewEmbed( - categorizedCommands, - embedColor, - prefix - ); - - const categorySelect = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('category_select') - .setPlaceholder('Select a category') - .addOptions( - categories.map((cat) => ({ - label: cat, - value: cat, - emoji: getCategoryEmoji(cat), - })) - ) - ); - - const response = await interaction.reply({ - embeds: [overviewEmbed], - components: [categorySelect], - }); - - const collector = response.createMessageComponentCollector({ - time: INTERACTION_TIMEOUT, - }); - - collector.on('collect', async (i) => { - if (i.user.id !== interaction.user.id) { - return i.reply({ - content: "You can't use this menu.", - ephemeral: true, - }); - } - - if (i.customId === 'category_select') { - await showCategoryCommands( - i, - localCommands, - i.values[0], - embedColor, - prefix - ); - } - }); - - collector.on('end', () => { - categorySelect.components.forEach((component) => - component.setDisabled(true) + const categorizedCommands = categorizeCommands(localCommands); + const categories = Object.keys(categorizedCommands); + + const overviewEmbed = createOverviewEmbed( + categorizedCommands, + embedColor, + prefix + ); + + const categorySelect = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId('category_select') + .setPlaceholder('Select a category') + .addOptions( + categories.map((cat) => ({ + label: cat, + value: cat, + emoji: getCategoryEmoji(cat), + })) + ) + ); + + const response = await interaction.reply({ + embeds: [overviewEmbed], + components: [categorySelect], + }); + + const collector = response.createMessageComponentCollector({ + time: INTERACTION_TIMEOUT, + }); + + collector.on('collect', async (i) => { + if (i.user.id !== interaction.user.id) { + return i.reply({ content: "You can't use this menu.", ephemeral: true }); + } + + if (i.customId === 'category_select') { + await showCategoryCommands( + i, + localCommands, + i.values[0], + embedColor, + prefix ); - interaction.editReply({ components: [categorySelect] }); - }); + } + }); + + collector.on('end', () => { + categorySelect.components.forEach((component) => + component.setDisabled(true) + ); + interaction.editReply({ components: [categorySelect] }); + }); } function createOverviewEmbed(categorizedCommands, embedColor, prefix) { - const overviewEmbed = new EmbedBuilder() - .setTitle('📜 Command Categories') - .setDescription( - 'Use the menu below to select a category or use `/help ` for specific command details.' - ) + return new EmbedBuilder() + .setTitle('📜 Command Categories') + .setDescription( + 'Use the menu below to select a category or use `/help ` for specific command details.' + ) + .setColor(embedColor) + .addFields( + Object.entries(categorizedCommands).map(([category, commands]) => ({ + name: `${getCategoryEmoji(category)} ${category}`, + value: `${commands.length} command${commands.length !== 1 ? 's' : ''}`, + inline: true, + })) + ) + .setFooter({ text: `Prefix for legacy commands: ${prefix}` }); +} + +function createCommandPages(categoryCommands, category, embedColor, prefix) { + const pages = []; + + for (let i = 0; i < categoryCommands.length; i += COMMANDS_PER_PAGE) { + const commandsSlice = categoryCommands.slice(i, i + COMMANDS_PER_PAGE); + const embed = new EmbedBuilder() + .setTitle(`${getCategoryEmoji(category)} ${category} Commands`) .setColor(embedColor) + .setDescription('Here are the available commands:') .addFields( - Object.keys(categorizedCommands).map((category) => ({ - name: `${getCategoryEmoji(category)} ${category}`, - value: `${categorizedCommands[category].length} commands`, - inline: true, - })) + commandsSlice.map((cmd) => ({ + name: `\`${cmd.prefix ? prefix : '/'}${cmd.name}\``, + value: cmd.description ?? 'No description available.', + inline: false, + })) ) - .setFooter({ text: `Prefix: ${prefix}` }); + .setFooter({ + text: `Page ${pages.length + 1}/${Math.ceil(categoryCommands.length / COMMANDS_PER_PAGE)}`, + }); + + pages.push(embed); + } - return overviewEmbed; + return pages; } -function createCommandPages(categoryCommands, category, embedColor, prefix) { - const pages = []; - - for (let i = 0; i < categoryCommands.length; i += COMMANDS_PER_PAGE) { - const commandsSlice = categoryCommands.slice(i, i + COMMANDS_PER_PAGE); - const embed = new EmbedBuilder() - .setTitle(`📜 ${category} Commands`) - .setColor(embedColor) - .setDescription('Here are the available commands:') - .addFields( - commandsSlice.map((cmd) => ({ - name: `\`${cmd.prefix ? prefix : '/'}${cmd.data.name}\``, - value: cmd.data.description ?? 'No description available.', - inline: true, - })) - ); - - pages.push(embed); - } - - return pages; +function createPaginationRow(pageCount) { + return new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('prev_page') + .setLabel('◀️ Previous') + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setCustomId('next_page') + .setLabel('Next ▶️') + .setStyle(ButtonStyle.Primary) + .setDisabled(pageCount <= 1), + new ButtonBuilder() + .setCustomId('return_to_overview') + .setLabel('🔍 Overview') + .setStyle(ButtonStyle.Secondary) + ); +} + +function updatePaginationRow(row, currentPage, pageCount) { + row.components[0].setDisabled(currentPage === 0); + row.components[1].setDisabled(currentPage === pageCount - 1); } function getCategoryEmoji(category) { - const emojis = { - General: '🌐', - Moderation: '🔨', - Fun: '🎉', - Utility: '⚙️', - Music: '🎵', - 'Prefix Commands': '🔧', - Uncategorized: '❓', - }; - - return emojis[category] || '📁'; + const emojis = { + General: '🌐', + Moderation: '🔨', + Fun: '🎉', + Utility: '⚙️', + Music: '🎵', + 'Prefix Commands': '🔧', + Uncategorized: '❓', + }; + + return emojis[category] || '📁'; } diff --git a/src/commands/misc/ping.js b/src/commands/misc/ping.js index 79772f0..d191d2d 100644 --- a/src/commands/misc/ping.js +++ b/src/commands/misc/ping.js @@ -1,105 +1,118 @@ import { - EmbedBuilder, - SlashCommandBuilder, - version as discordJsVersion, + EmbedBuilder, + SlashCommandBuilder, + version as discordJsVersion, } from 'discord.js'; import { formatDistanceToNow } from 'date-fns'; import os from 'os'; +import { performance } from 'perf_hooks'; export default { - data: new SlashCommandBuilder() - .setName('ping') - .setDescription('Shows bot latency and other stats'), + data: new SlashCommandBuilder() + .setName('ping') + .setDescription('Shows bot latency and other detailed system stats'), - userPermissions: [], - botPermissions: [], - category: 'Misc', - cooldown: 5, - nwfwMode: false, - testMode: false, - devOnly: false, - prefix: true, + userPermissions: [], + botPermissions: [], + category: 'Misc', + cooldown: 5, + nwfwMode: false, + testMode: false, + devOnly: false, + prefix: true, - run: async (client, interaction) => { - try { - const start = Date.now(); - await interaction.deferReply(); - const latency = Date.now() - start; - const apiPing = Math.round(client.ws.ping); + run: async (client, interaction) => { + try { + const start = performance.now(); + await interaction.deferReply(); + const latency = Math.round(performance.now() - start); + const apiPing = Math.round(client.ws.ping); - const getPingColor = (ping) => - ping < 150 ? '#00ff00' : ping < 250 ? '#ffff00' : '#ff0000'; + const getPingColor = (ping) => + ping < 100 ? '#00ff00' : ping < 200 ? '#ffff00' : '#ff0000'; - client.commandStats ??= { pingCount: 0, totalPing: 0, totalCommands: 0 }; - client.commandStats.pingCount++; - client.commandStats.totalPing += apiPing; - client.commandStats.totalCommands++; + client.commandStats ??= { + pingCount: 0, + totalPing: 0, + totalCommands: 0, + }; + client.commandStats.pingCount++; + client.commandStats.totalPing += apiPing; + client.commandStats.totalCommands++; - const averagePing = ( - client.commandStats.totalPing / client.commandStats.pingCount - ).toFixed(2); - const uptime = client.readyAt - ? formatDistanceToNow(client.readyAt, { addSuffix: true }) - : 'Bot not ready'; + const averagePing = ( + client.commandStats.totalPing / client.commandStats.pingCount + ).toFixed(2); + const uptime = client.readyAt + ? formatDistanceToNow(client.readyAt, { addSuffix: true }) + : 'Bot not ready'; - const { heapUsed, rss } = process.memoryUsage(); - const totalMem = os.totalmem() / 1024 / 1024; - const freeMem = os.freemem() / 1024 / 1024; - const systemUptime = os.uptime(); + const { heapUsed, rss } = process.memoryUsage(); + const totalMem = os.totalmem() / 1024 / 1024; + const freeMem = os.freemem() / 1024 / 1024; + const systemUptime = os.uptime(); + const cpuUsage = os.loadavg()[0].toFixed(2); - const stats = [ - { name: '🏓 **Bot Latency**', value: `\`${latency}ms\`` }, - { name: '🌐 **API Latency**', value: `\`${apiPing}ms\`` }, - { name: '📊 **Average Ping**', value: `\`${averagePing}ms\`` }, - { name: '⏳ **Uptime**', value: `\`${uptime}\`` }, - { - name: '💾 **Memory Usage**', - value: `\`${(heapUsed / 1024 / 1024).toFixed(2)} MB / ${(rss / 1024 / 1024).toFixed(2)} MB\``, - }, - { - name: '🧠 **System Memory**', - value: `\`Total: ${totalMem.toFixed(2)} MB, Free: ${freeMem.toFixed(2)} MB\``, - }, - { name: '📚 **Discord.js Version**', value: `\`${discordJsVersion}\`` }, - { name: '🛠️ **Node.js Version**', value: `\`${process.version}\`` }, - { - name: '⚙️ **System Uptime**', - value: `\`${formatDistanceToNow(Date.now() - systemUptime * 1000, { - addSuffix: true, - })}\``, - }, - { - name: '💻 **OS Info**', - value: `\`${os.type()} ${os.release()}\``, - }, - { - name: '🖥️ **CPU Info**', - value: `\`${os.cpus()[0].model} (${os.cpus().length} cores)\``, - }, - { - name: '🔢 **Command Usage**', - value: `\`Total Executed: ${client.commandStats.totalCommands}\``, - }, - ]; + const stats = [ + { name: '🏓 Bot Latency', value: `${latency}ms`, inline: true }, + { name: '🌐 API Latency', value: `${apiPing}ms`, inline: true }, + { name: '📊 Average Ping', value: `${averagePing}ms`, inline: true }, + { name: '⏳ Uptime', value: uptime, inline: true }, + { + name: '💾 Memory Usage', + value: `${(heapUsed / 1024 / 1024).toFixed(2)} MB / ${(rss / 1024 / 1024).toFixed(2)} MB`, + inline: true, + }, + { + name: '🧠 System Memory', + value: `${(((totalMem - freeMem) / totalMem) * 100).toFixed(2)}% used`, + inline: true, + }, + { name: '📚 Discord.js', value: discordJsVersion, inline: true }, + { name: '🛠️ Node.js', value: process.version, inline: true }, + { + name: '⚙️ System Uptime', + value: formatDistanceToNow(Date.now() - systemUptime * 1000, { + addSuffix: true, + }), + inline: true, + }, + { name: '💻 OS', value: `${os.type()} ${os.release()}`, inline: true }, + { + name: '🖥️ CPU', + value: `${os.cpus()[0].model.split(' ')[0]} (${os.cpus().length} cores)`, + inline: true, + }, + { name: '📈 CPU Usage', value: `${cpuUsage}%`, inline: true }, + { + name: '🔢 Commands Run', + value: client.commandStats.totalCommands.toString(), + inline: true, + }, + ]; - const pongEmbed = new EmbedBuilder() - .setColor(getPingColor(apiPing)) - .setTitle('🏓 **Pong!**') - .addFields(stats.map((stat) => ({ ...stat, inline: true }))) - .setFooter({ - text: `Requested by ${interaction.user.username}`, - iconURL: interaction.user.displayAvatarURL({ dynamic: true }), - }) - .setTimestamp(); + const pongEmbed = new EmbedBuilder() + .setColor(getPingColor(apiPing)) + .setTitle('🚀 Bot Status') + .setDescription( + "Here's a detailed overview of the bot's current performance and system statistics." + ) + .addFields(stats) + .setFooter({ + text: `Requested by ${interaction.user.username}`, + iconURL: interaction.user.displayAvatarURL({ dynamic: true }), + }) + .setTimestamp(); - await interaction.editReply({ embeds: [pongEmbed] }); - } catch (error) { - console.error('Error in ping command:', error); + await interaction.editReply({ embeds: [pongEmbed] }); + } catch (error) { + console.error('Error in ping command:', error); - - await interaction.editReply( - '❌ An error occurred while processing the command. Please try again later.' - ); - } - }, + await interaction.editReply({ + content: + '❌ An error occurred while processing the command. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/commands/misc/prefix.js b/src/commands/misc/prefix.js index da4fb7a..1e403ce 100644 --- a/src/commands/misc/prefix.js +++ b/src/commands/misc/prefix.js @@ -2,97 +2,97 @@ import { UserPrefix } from '../../schemas/prefix.js'; import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; const DISALLOWED_PREFIXES = [ - '/', - '\\', - '@', - '#', - '$', - '&', - '(', - ')', - '{', - '}', - '[', - ']', + '/', + '\\', + '@', + '#', + '$', + '&', + '(', + ')', + '{', + '}', + '[', + ']', ]; export default { - data: new SlashCommandBuilder() - .setName('prefix') - .setDescription('Shows or sets your prefix') - .addStringOption((option) => - option - .setName('prefix') - .setDescription('The new prefix to set') - .setRequired(false) - ), - userPermissions: [], - botPermissions: [], - category: 'Misc', - cooldown: 5, - nsfwMode: false, - testMode: false, - devOnly: false, + data: new SlashCommandBuilder() + .setName('prefix') + .setDescription('Shows or sets your prefix') + .addStringOption((option) => + option + .setName('prefix') + .setDescription('The new prefix to set') + .setRequired(false) + ), + userPermissions: [], + botPermissions: [], + category: 'Misc', + cooldown: 5, + nsfwMode: false, + testMode: false, + devOnly: false, - run: async (client, interaction) => { - try { - const newPrefix = interaction.options.getString('prefix'); + run: async (client, interaction) => { + try { + const newPrefix = interaction.options.getString('prefix'); - if (newPrefix !== null) { - await updatePrefix(interaction, newPrefix); - } else { - await showCurrentPrefix(interaction); - } - } catch (error) { - console.error('Error in prefix command:', error); - await interaction.reply({ - content: - 'An error occurred while processing the command. Please try again later.', - ephemeral: true, - }); + if (newPrefix !== null) { + await updatePrefix(interaction, newPrefix); + } else { + await showCurrentPrefix(interaction); } - }, + } catch (error) { + console.error('Error in prefix command:', error); + await interaction.reply({ + content: + 'An error occurred while processing the command. Please try again later.', + ephemeral: true, + }); + } + }, }; async function updatePrefix(interaction, newPrefix) { - if (DISALLOWED_PREFIXES.includes(newPrefix)) { - return interaction.reply({ - content: `The prefix "${newPrefix}" is not allowed as it may conflict with Discord or bot functionality.`, - ephemeral: true, - }); - } + if (DISALLOWED_PREFIXES.includes(newPrefix)) { + return interaction.reply({ + content: `The prefix "${newPrefix}" is not allowed as it may conflict with Discord or bot functionality.`, + ephemeral: true, + }); + } - const finalPrefix = newPrefix === 'noprefix' ? '' : newPrefix; + const finalPrefix = newPrefix === 'noprefix' ? '' : newPrefix; - await UserPrefix.findOneAndUpdate( - { userId: interaction.user.id }, - { prefix: finalPrefix }, - { upsert: true } - ); + await UserPrefix.findOneAndUpdate( + { userId: interaction.user.id }, + { prefix: finalPrefix }, + { upsert: true } + ); - await interaction.reply({ - content: `Your prefix has been updated to \`${finalPrefix || 'no prefix'}\`.`, - ephemeral: true, - }); + await interaction.reply({ + content: `Your prefix has been updated to \`${finalPrefix || 'no prefix'}\`.`, + ephemeral: true, + }); } async function showCurrentPrefix(interaction) { - const userPrefixData = await UserPrefix.findOne({ - userId: interaction.user.id, - }); - const userPrefix = userPrefixData ? userPrefixData.prefix : '!'; + const userPrefixData = await UserPrefix.findOne({ + userId: interaction.user.id, + }); + const userPrefix = userPrefixData ? userPrefixData.prefix : '!'; - const embed = new EmbedBuilder() - .setTitle('Your Prefix') - .setDescription( - `Your current prefix is \`${userPrefix || 'no prefix'}\`. You can set a new prefix by using \`/prefix [newPrefix]\`.` - ) - .setColor('#00FF00') - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.avatarURL(), - }) - .setTimestamp(); + const embed = new EmbedBuilder() + .setTitle('Your Prefix') + .setDescription( + `Your current prefix is \`${userPrefix || 'no prefix'}\`. You can set a new prefix by using \`/prefix [newPrefix]\`.` + ) + .setColor('#00FF00') + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.avatarURL(), + }) + .setTimestamp(); - await interaction.reply({ embeds: [embed] }); + await interaction.reply({ embeds: [embed] }); } diff --git a/src/commands/misc/social.js b/src/commands/misc/social.js index 981e773..e8953ec 100644 --- a/src/commands/misc/social.js +++ b/src/commands/misc/social.js @@ -3,76 +3,78 @@ import { SlashCommandBuilder, EmbedBuilder } from 'discord.js'; export default { - data: new SlashCommandBuilder() - .setName('social') - .setDescription('All the links to my socials.') - .toJSON(), - userPermissions: [], - botPermissions: [], - category: 'Misc', - cooldown: 5, - nwfwMode: false, - testMode: false, - devOnly: false, + data: new SlashCommandBuilder() + .setName('social') + .setDescription('All the links to my socials.') + .toJSON(), + userPermissions: [], + botPermissions: [], + category: 'Misc', + cooldown: 5, + nwfwMode: false, + testMode: false, + devOnly: false, - run: async (client, interaction) => { - const guild = interaction.guild; - const guildIconURL = guild.iconURL({ dynamic: true, size: 256 }) || ''; + run: async (client, interaction) => { + const guild = interaction.guild; + const guildIconURL = guild.iconURL({ dynamic: true, size: 256 }) || ''; - const embed = new EmbedBuilder() - .setColor('#00acee') - .setTitle('My Socials') - .setDescription('Follow me on these platforms!') - .addFields( - { - name: 'Discord Server', - value: '[Join my Discord Server](https://clienterr.com/discord)', - inline: true, - }, - { - name: 'Website', - value: '[Check out my website](https://clienterr.com/)', - inline: true, - }, - { - name: 'YouTube', - value: '[Subscribe to my YouTube Channel](https://clienterr.com/youtube)', - inline: true, - }, - { - name: 'Discord Username', - value: '.clienterr', - inline: true, - }, - { - name: 'Telegram', - value: '[Contact me on Telegram](https://clienterr.com/telegram)', - inline: true, - }, - { - name: 'GitHub', - value: '[Check out my GitHub](https://clienterr.com/github)', - inline: true, - }, - { - name: 'GitHub Organisation', - value: '[Check out my GitHub Organisation](https://clienterr.com/clienterrverse)', - inline: true, - } - ) - .setTimestamp(); + const embed = new EmbedBuilder() + .setColor('#00acee') + .setTitle('My Socials') + .setDescription('Follow me on these platforms!') + .addFields( + { + name: 'Discord Server', + value: '[Join my Discord Server](https://clienterr.com/discord)', + inline: true, + }, + { + name: 'Website', + value: '[Check out my website](https://clienterr.com/)', + inline: true, + }, + { + name: 'YouTube', + value: + '[Subscribe to my YouTube Channel](https://clienterr.com/youtube)', + inline: true, + }, + { + name: 'Discord Username', + value: '.clienterr', + inline: true, + }, + { + name: 'Telegram', + value: '[Contact me on Telegram](https://clienterr.com/telegram)', + inline: true, + }, + { + name: 'GitHub', + value: '[Check out my GitHub](https://clienterr.com/github)', + inline: true, + }, + { + name: 'GitHub Organisation', + value: + '[Check out my GitHub Organisation](https://clienterr.com/clienterrverse)', + inline: true, + } + ) + .setTimestamp(); - // Set the thumbnail only if the guild icon URL is available - if (guildIconURL) { - embed.setThumbnail(guildIconURL); - } + // Set the thumbnail only if the guild icon URL is available + if (guildIconURL) { + embed.setThumbnail(guildIconURL); + } - embed.setFooter({ - text: 'Thank you for your support!', - iconURL: - 'https://cdn.discordapp.com/avatars/1242892502768549969/d4eca7b8bfd004c2d1e016be39b66934.webp?size=1024&format=webp&width=0&height=240', - }); + embed.setFooter({ + text: 'Thank you for your support!', + iconURL: + 'https://cdn.discordapp.com/avatars/1242892502768549969/d4eca7b8bfd004c2d1e016be39b66934.webp?size=1024&format=webp&width=0&height=240', + }); - await interaction.reply({ embeds: [embed] }); - }, + await interaction.reply({ embeds: [embed] }); + }, }; diff --git a/src/commands/ticket/close-all-tickets.js b/src/commands/ticket/close-all-tickets.js index 0100fe3..f1d8fce 100644 --- a/src/commands/ticket/close-all-tickets.js +++ b/src/commands/ticket/close-all-tickets.js @@ -3,88 +3,88 @@ import { closeTicket } from '../../utils/ticket/ticketClose.js'; import ticketSchema from '../../schemas/ticketSchema.js'; export default { - data: new SlashCommandBuilder() - .setName('close-all-tickets') - .setDescription('Close all open tickets in the guild') - .addStringOption((option) => - option - .setName('reason') - .setDescription('The reason for closing all tickets') - .setRequired(false) - ), - userPermissions: [PermissionFlagsBits.ManageChannels], - botPermissions: [PermissionFlagsBits.ManageChannels], - category: 'ticket', - run: async (client, interaction) => { - await interaction.deferReply({ ephemeral: true }); + data: new SlashCommandBuilder() + .setName('close-all-tickets') + .setDescription('Close all open tickets in the guild') + .addStringOption((option) => + option + .setName('reason') + .setDescription('The reason for closing all tickets') + .setRequired(false) + ), + userPermissions: [PermissionFlagsBits.ManageChannels], + botPermissions: [PermissionFlagsBits.ManageChannels], + category: 'ticket', + run: async (client, interaction) => { + await interaction.deferReply({ ephemeral: true }); - const reason = - interaction.options.getString('reason') || 'Mass ticket closure'; + const reason = + interaction.options.getString('reason') || 'Mass ticket closure'; - try { - const guild = interaction.guild; - const openTickets = await ticketSchema.find({ - guildID: guild.id, - closed: false, - }); + try { + const guild = interaction.guild; + const openTickets = await ticketSchema.find({ + guildID: guild.id, + closed: false, + }); - if (openTickets.length === 0) { - return interaction.editReply( - 'There are no open tickets in this guild.' - ); - } - - let closedCount = 0; - let failedCount = 0; + if (openTickets.length === 0) { + return interaction.editReply( + 'There are no open tickets in this guild.' + ); + } - for (const ticket of openTickets) { - const channel = guild.channels.cache.get(ticket.ticketChannelID); - if (channel) { - const result = await closeTicket( - client, - guild, - channel, - interaction.member, - reason - ); - if (result.success) { - closedCount++; - } else { - failedCount++; - console.error( - `Failed to close ticket ${ticket.ticketChannelID}: ${result.message}` - ); - } - } else { - // Update the ticket in the database even if the channel is not found - await ticketSchema.findOneAndUpdate( - { - guildID: guild.id, - ticketChannelID: ticket.ticketChannelID, - }, - { - closed: true, - closeReason: reason, - status: 'closed', - closedBy: interaction.member.id, - }, - { new: true } - ); - closedCount++; - console.log( - `Channel not found for ticket ${ticket.ticketChannelID}, but database updated` - ); - } - } + let closedCount = 0; + let failedCount = 0; - await interaction.editReply( - `Operation completed. Successfully closed ${closedCount} ticket(s). Failed to close ${failedCount} ticket(s).` - ); - } catch (error) { - console.error('Error closing tickets:', error); - await interaction.editReply( - 'An error occurred while closing tickets. Please check the console for more details.' - ); + for (const ticket of openTickets) { + const channel = guild.channels.cache.get(ticket.ticketChannelID); + if (channel) { + const result = await closeTicket( + client, + guild, + channel, + interaction.member, + reason + ); + if (result.success) { + closedCount++; + } else { + failedCount++; + console.error( + `Failed to close ticket ${ticket.ticketChannelID}: ${result.message}` + ); + } + } else { + // Update the ticket in the database even if the channel is not found + await ticketSchema.findOneAndUpdate( + { + guildID: guild.id, + ticketChannelID: ticket.ticketChannelID, + }, + { + closed: true, + closeReason: reason, + status: 'closed', + closedBy: interaction.member.id, + }, + { new: true } + ); + closedCount++; + console.log( + `Channel not found for ticket ${ticket.ticketChannelID}, but database updated` + ); + } } - }, + + await interaction.editReply( + `Operation completed. Successfully closed ${closedCount} ticket(s). Failed to close ${failedCount} ticket(s).` + ); + } catch (error) { + console.error('Error closing tickets:', error); + await interaction.editReply( + 'An error occurred while closing tickets. Please check the console for more details.' + ); + } + }, }; diff --git a/src/commands/ticket/ticket-close.js b/src/commands/ticket/ticket-close.js index 2a7124a..b4a5689 100644 --- a/src/commands/ticket/ticket-close.js +++ b/src/commands/ticket/ticket-close.js @@ -2,45 +2,45 @@ import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; import { closeTicket } from '../../utils/ticket/ticketClose.js'; export default { - data: new SlashCommandBuilder() - .setName('ticket-close') - .setDescription('Close the current ticket') - .addStringOption((option) => - option - .setName('reason') - .setDescription('The reason for closing the ticket') - .setRequired(false) - ), + data: new SlashCommandBuilder() + .setName('ticket-close') + .setDescription('Close the current ticket') + .addStringOption((option) => + option + .setName('reason') + .setDescription('The reason for closing the ticket') + .setRequired(false) + ), - userPermissions: [PermissionFlagsBits.ManageChannels], - botPermissions: [PermissionFlagsBits.ManageChannels], - category: 'ticket', + userPermissions: [PermissionFlagsBits.ManageChannels], + botPermissions: [PermissionFlagsBits.ManageChannels], + category: 'ticket', - run: async (client, interaction) => { - await interaction.deferReply({ ephemeral: true }); + run: async (client, interaction) => { + await interaction.deferReply({ ephemeral: true }); - const reason = - interaction.options.getString('reason') || 'No reason provided'; + const reason = + interaction.options.getString('reason') || 'No reason provided'; - // Check if the channel is a ticket channel - const result = await closeTicket( - client, - interaction.guild, - interaction.channel, - interaction.member, - reason - ); + // Check if the channel is a ticket channel + const result = await closeTicket( + client, + interaction.guild, + interaction.channel, + interaction.member, + reason + ); - if (result.success) { - await interaction.editReply({ - content: result.message, - ephemeral: true, - }); - } else { - await interaction.editReply({ - content: `Error: ${result.message}`, - ephemeral: true, - }); - } - }, + if (result.success) { + await interaction.editReply({ + content: result.message, + ephemeral: true, + }); + } else { + await interaction.editReply({ + content: `Error: ${result.message}`, + ephemeral: true, + }); + } + }, }; diff --git a/src/commands/ticket/ticketAddMember.js b/src/commands/ticket/ticketAddMember.js index cd8b9cf..3ba2b28 100644 --- a/src/commands/ticket/ticketAddMember.js +++ b/src/commands/ticket/ticketAddMember.js @@ -2,77 +2,77 @@ import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; import Ticket from '../../schemas/ticketSchema.js'; export default { - data: new SlashCommandBuilder() - .setName('ticket-add-member') - .setDescription('Add a member to a ticket.') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) - .addUserOption((option) => - option - .setName('member') - .setDescription('The member to add to the ticket.') - .setRequired(true) - ), + data: new SlashCommandBuilder() + .setName('ticket-add-member') + .setDescription('Add a member to a ticket.') + .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) + .addUserOption((option) => + option + .setName('member') + .setDescription('The member to add to the ticket.') + .setRequired(true) + ), - userPermissions: [PermissionFlagsBits.ManageChannels], - botPermissions: [PermissionFlagsBits.ManageChannels], - category: 'ticket', + userPermissions: [PermissionFlagsBits.ManageChannels], + botPermissions: [PermissionFlagsBits.ManageChannels], + category: 'ticket', - run: async (client, interaction) => { - await interaction.deferReply({ ephemeral: true }); + run: async (client, interaction) => { + await interaction.deferReply({ ephemeral: true }); - try { - const { channel, guild } = interaction; - const memberToAdd = interaction.options.getMember('member'); + try { + const { channel, guild } = interaction; + const memberToAdd = interaction.options.getMember('member'); - if (!memberToAdd) { - return await interaction.editReply({ - content: 'The specified member was not found in the server.', - ephemeral: true, - }); - } + if (!memberToAdd) { + return await interaction.editReply({ + content: 'The specified member was not found in the server.', + ephemeral: true, + }); + } - const ticket = await Ticket.findOne({ - guildID: guild.id, - ticketChannelID: channel.id, - closed: false, - }); + const ticket = await Ticket.findOne({ + guildID: guild.id, + ticketChannelID: channel.id, + closed: false, + }); - if (!ticket) { - return await interaction.editReply({ - content: 'This channel is not an active ticket channel.', - ephemeral: true, - }); - } + if (!ticket) { + return await interaction.editReply({ + content: 'This channel is not an active ticket channel.', + ephemeral: true, + }); + } - if (channel.permissionOverwrites.cache.has(memberToAdd.id)) { - return await interaction.editReply({ - content: 'This member is already in the ticket.', - ephemeral: true, - }); - } + if (channel.permissionOverwrites.cache.has(memberToAdd.id)) { + return await interaction.editReply({ + content: 'This member is already in the ticket.', + ephemeral: true, + }); + } - // Add member to ticket - await Ticket.findOneAndUpdate( - { _id: ticket._id }, - { $addToSet: { membersAdded: memberToAdd.id } } - ); + // Add member to ticket + await Ticket.findOneAndUpdate( + { _id: ticket._id }, + { $addToSet: { membersAdded: memberToAdd.id } } + ); - await channel.permissionOverwrites.create(memberToAdd, { - ViewChannel: true, - SendMessages: true, - }); + await channel.permissionOverwrites.create(memberToAdd, { + ViewChannel: true, + SendMessages: true, + }); - return await interaction.editReply({ - content: `Successfully added ${memberToAdd.user.tag} to the ticket.`, - ephemeral: true, - }); - } catch (err) { - console.error('Error adding member to ticket:', err); - return await interaction.editReply({ - content: - 'An error occurred while adding the member to the ticket. Please try again later.', - ephemeral: true, - }); - } - }, + return await interaction.editReply({ + content: `Successfully added ${memberToAdd.user.tag} to the ticket.`, + ephemeral: true, + }); + } catch (err) { + console.error('Error adding member to ticket:', err); + return await interaction.editReply({ + content: + 'An error occurred while adding the member to the ticket. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/commands/ticket/ticketRemoveMenber.js b/src/commands/ticket/ticketRemoveMenber.js index 087e3c8..16a713f 100644 --- a/src/commands/ticket/ticketRemoveMenber.js +++ b/src/commands/ticket/ticketRemoveMenber.js @@ -2,80 +2,80 @@ import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; import Ticket from '../../schemas/ticketSchema.js'; export default { - data: new SlashCommandBuilder() - .setName('ticket-remove-member') - .setDescription('Remove a member from a ticket.') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageThreads) - .addUserOption((option) => - option - .setName('member') - .setDescription('The member to remove from the ticket.') - .setRequired(true) - ), + data: new SlashCommandBuilder() + .setName('ticket-remove-member') + .setDescription('Remove a member from a ticket.') + .setDefaultMemberPermissions(PermissionFlagsBits.ManageThreads) + .addUserOption((option) => + option + .setName('member') + .setDescription('The member to remove from the ticket.') + .setRequired(true) + ), - userPermissions: [PermissionFlagsBits.ManageChannels], - botPermissions: [PermissionFlagsBits.ManageChannels], - category: 'ticket', + userPermissions: [PermissionFlagsBits.ManageChannels], + botPermissions: [PermissionFlagsBits.ManageChannels], + category: 'ticket', - run: async (client, interaction) => { - await interaction.deferReply({ ephemeral: true }); + run: async (client, interaction) => { + await interaction.deferReply({ ephemeral: true }); - try { - const { channel, guild } = interaction; - const memberToRemove = interaction.options.getMember('member'); + try { + const { channel, guild } = interaction; + const memberToRemove = interaction.options.getMember('member'); - if (!memberToRemove) { - return await interaction.editReply({ - content: 'The specified member was not found in the server.', - ephemeral: true, - }); - } + if (!memberToRemove) { + return await interaction.editReply({ + content: 'The specified member was not found in the server.', + ephemeral: true, + }); + } - const ticket = await Ticket.findOne({ - guildID: guild.id, - ticketChannelID: channel.id, - closed: false, - }); + const ticket = await Ticket.findOne({ + guildID: guild.id, + ticketChannelID: channel.id, + closed: false, + }); - if (!ticket) { - return await interaction.editReply({ - content: 'This channel is not an active ticket channel.', - ephemeral: true, - }); - } + if (!ticket) { + return await interaction.editReply({ + content: 'This channel is not an active ticket channel.', + ephemeral: true, + }); + } - if (!channel.permissionOverwrites.cache.has(memberToRemove.id)) { - return await interaction.editReply({ - content: 'This member is not in the ticket.', - ephemeral: true, - }); - } + if (!channel.permissionOverwrites.cache.has(memberToRemove.id)) { + return await interaction.editReply({ + content: 'This member is not in the ticket.', + ephemeral: true, + }); + } - // Remove member from ticket - await Ticket.findOneAndUpdate( - { - guildID: guild.id, - ticketChannelID: channel.id, - closed: false, - }, - { - $pull: { membersAdded: memberToRemove.id }, - } - ); + // Remove member from ticket + await Ticket.findOneAndUpdate( + { + guildID: guild.id, + ticketChannelID: channel.id, + closed: false, + }, + { + $pull: { membersAdded: memberToRemove.id }, + } + ); - await channel.permissionOverwrites.delete(memberToRemove); + await channel.permissionOverwrites.delete(memberToRemove); - return await interaction.editReply({ - content: `Successfully removed ${memberToRemove.user.tag} from the ticket.`, - ephemeral: true, - }); - } catch (err) { - console.error('Error removing member from ticket:', err); - return await interaction.editReply({ - content: - 'An error occurred while removing the member from the ticket. Please try again later.', - ephemeral: true, - }); - } - }, + return await interaction.editReply({ + content: `Successfully removed ${memberToRemove.user.tag} from the ticket.`, + ephemeral: true, + }); + } catch (err) { + console.error('Error removing member from ticket:', err); + return await interaction.editReply({ + content: + 'An error occurred while removing the member from the ticket. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/commands/ticket/ticketSetup.js b/src/commands/ticket/ticketSetup.js index de6a045..2a5cf64 100644 --- a/src/commands/ticket/ticketSetup.js +++ b/src/commands/ticket/ticketSetup.js @@ -1,674 +1,673 @@ import { - SlashCommandBuilder, - ChannelType, - PermissionFlagsBits, - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - StringSelectMenuBuilder, + SlashCommandBuilder, + ChannelType, + PermissionFlagsBits, + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + StringSelectMenuBuilder, } from 'discord.js'; import ticketSetupSchema from '../../schemas/ticketSetupSchema.js'; export default { - data: new SlashCommandBuilder() - .setName('ticket') - .setDescription('Manage the ticket system in your server.') - .addSubcommand((subcommand) => - subcommand - .setName('setup') - .setDescription('Setup or update the ticket system in your server.') - .addChannelOption((option) => - option - .setName('ticket-channel') - .setDescription('The channel where tickets will be sent to') - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) + data: new SlashCommandBuilder() + .setName('ticket') + .setDescription('Manage the ticket system in your server.') + .addSubcommand((subcommand) => + subcommand + .setName('setup') + .setDescription('Setup or update the ticket system in your server.') + .addChannelOption((option) => + option + .setName('ticket-channel') + .setDescription('The channel where tickets will be sent to') + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ) + .addChannelOption((option) => + option + .setName('category') + .setDescription('The category where tickets will be created') + .setRequired(true) + .addChannelTypes(ChannelType.GuildCategory) + ) + .addRoleOption((option) => + option + .setName('staff-role') + .setDescription('The role that will be able to see tickets.') + .setRequired(true) + ) + .addChannelOption((option) => + option + .setName('log-channel') + .setDescription('The channel where ticket logs will be sent') + .setRequired(true) + .addChannelTypes(ChannelType.GuildText) + ) + .addStringOption((option) => + option + .setName('ticket-type') + .setDescription('How tickets will be created') + .addChoices( + { name: 'Modal', value: 'modal' }, + { name: 'Select', value: 'select' } ) - .addChannelOption((option) => - option - .setName('category') - .setDescription('The category where tickets will be created') - .setRequired(true) - .addChannelTypes(ChannelType.GuildCategory) - ) - .addRoleOption((option) => - option - .setName('staff-role') - .setDescription('The role that will be able to see tickets.') - .setRequired(true) - ) - .addChannelOption((option) => - option - .setName('log-channel') - .setDescription('The channel where ticket logs will be sent') - .setRequired(true) - .addChannelTypes(ChannelType.GuildText) - ) - .addStringOption((option) => - option - .setName('ticket-type') - .setDescription('How tickets will be created') - .addChoices( - { name: 'Modal', value: 'modal' }, - { name: 'Select', value: 'select' } - ) - .setRequired(true) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('update') - .setDescription('Update the ticket system message.') - ) - .addSubcommand((subcommand) => - subcommand - .setName('remove') - .setDescription('Remove the ticket system setup for the guild.') - ) - .addSubcommand((subcommand) => - subcommand - .setName('status') - .setDescription('Check the current ticket system setup.') - ) - .addSubcommand((subcommand) => - subcommand - .setName('add-option') - .setDescription('Add a new ticket option.') - .addStringOption((option) => - option - .setName('label') - .setDescription('The label for the new ticket option') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('value') - .setDescription('The value for the new ticket option') - .setRequired(true) - ) - .addStringOption((option) => - option - .setName('description') - .setDescription('The description for the new ticket option') - .setRequired(true) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName('remove-option') - .setDescription('Remove a ticket option.') - .addStringOption((option) => - option - .setName('value') - .setDescription('The value of the ticket option to remove') - .setRequired(true) - ) - ) - .toJSON(), - userPermissions: [PermissionFlagsBits.Administrator], - botPermissions: [ - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.ManageRoles, - ], - category: 'ticket', - - run: async (client, interaction) => { - const subcommand = interaction.options.getSubcommand(); - - try { - switch (subcommand) { - case 'setup': - await handleSetup(interaction); - break; - case 'update': - await handleUpdate(interaction); - break; - case 'remove': - await handleRemove(interaction); - break; - case 'status': - await handleStatus(interaction); - break; - case 'add-option': - await handleAddOption(interaction); - break; - case 'remove-option': - await handleRemoveOption(interaction); - break; - default: - await interaction.reply({ - content: 'Invalid subcommand.', - ephemeral: true, - }); - } - } catch (error) { - console.error(`Error in ticket command (${subcommand}):`, error); - await interaction.reply({ - content: 'An error occurred while processing your command.', + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('update') + .setDescription('Update the ticket system message.') + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Remove the ticket system setup for the guild.') + ) + .addSubcommand((subcommand) => + subcommand + .setName('status') + .setDescription('Check the current ticket system setup.') + ) + .addSubcommand((subcommand) => + subcommand + .setName('add-option') + .setDescription('Add a new ticket option.') + .addStringOption((option) => + option + .setName('label') + .setDescription('The label for the new ticket option') + .setRequired(true) + ) + .addStringOption((option) => + option + .setName('value') + .setDescription('The value for the new ticket option') + .setRequired(true) + ) + .addStringOption((option) => + option + .setName('description') + .setDescription('The description for the new ticket option') + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove-option') + .setDescription('Remove a ticket option.') + .addStringOption((option) => + option + .setName('value') + .setDescription('The value of the ticket option to remove') + .setRequired(true) + ) + ) + .toJSON(), + userPermissions: [PermissionFlagsBits.Administrator], + botPermissions: [ + PermissionFlagsBits.ManageChannels, + PermissionFlagsBits.ManageRoles, + ], + category: 'ticket', + + run: async (client, interaction) => { + const subcommand = interaction.options.getSubcommand(); + + try { + switch (subcommand) { + case 'setup': + await handleSetup(interaction); + break; + case 'update': + await handleUpdate(interaction); + break; + case 'remove': + await handleRemove(interaction); + break; + case 'status': + await handleStatus(interaction); + break; + case 'add-option': + await handleAddOption(interaction); + break; + case 'remove-option': + await handleRemoveOption(interaction); + break; + default: + await interaction.reply({ + content: 'Invalid subcommand.', ephemeral: true, - }); + }); } - }, + } catch (error) { + console.error(`Error in ticket command (${subcommand}):`, error); + await interaction.reply({ + content: 'An error occurred while processing your command.', + ephemeral: true, + }); + } + }, }; async function handleSetup(interaction) { - const { guild, options } = interaction; - - await interaction.deferReply({ ephemeral: true }); - - try { - const staffRole = options.getRole('staff-role'); - const ticketChannel = options.getChannel('ticket-channel'); - const category = options.getChannel('category'); - const logChannel = options.getChannel('log-channel'); - const ticketType = options.getString('ticket-type'); - - // Validate permissions - const requiredPermissions = [ - { - channel: ticketChannel, - permission: 'SendMessages', - name: 'ticket channel', - }, - { channel: category, permission: 'ManageChannels', name: 'category' }, - { - channel: logChannel, - permission: 'SendMessages', - name: 'log channel', - }, - ]; - - for (const { channel, permission, name } of requiredPermissions) { - if ( - !channel - .permissionsFor(guild.members.me) - .has(PermissionFlagsBits[permission]) - ) { - return await interaction.editReply( - `I don't have permission to ${permission} in the specified ${name}.` - ); - } - } - - const ticketCreateEmbed = new EmbedBuilder() - .setTitle('Support Ticket System') - .setDescription('Create a support ticket') - .setColor('Blue') - .setFooter({ text: 'Support Tickets' }) - .setTimestamp(); - - let setupTicket = await ticketSetupSchema.findOne({ guildID: guild.id }); - - if (!setupTicket) { - setupTicket = new ticketSetupSchema({ - guildID: guild.id, - ticketChannelID: ticketChannel.id, - staffRoleID: staffRole.id, - categoryID: category.id, - logChannelID: logChannel.id, - ticketType: ticketType, - customOptions: [ - { - label: 'General Support', - value: 'general_support', - description: 'Get help with general issues', - }, - { - label: 'Technical Support', - value: 'technical_support', - description: 'Get help with technical problems', - }, - ], - }); - } else { - setupTicket.ticketChannelID = ticketChannel.id; - setupTicket.staffRoleID = staffRole.id; - setupTicket.categoryID = category.id; - setupTicket.logChannelID = logChannel.id; - setupTicket.ticketType = ticketType; + const { guild, options } = interaction; + + await interaction.deferReply({ ephemeral: true }); + + try { + const staffRole = options.getRole('staff-role'); + const ticketChannel = options.getChannel('ticket-channel'); + const category = options.getChannel('category'); + const logChannel = options.getChannel('log-channel'); + const ticketType = options.getString('ticket-type'); + + // Validate permissions + const requiredPermissions = [ + { + channel: ticketChannel, + permission: 'SendMessages', + name: 'ticket channel', + }, + { channel: category, permission: 'ManageChannels', name: 'category' }, + { + channel: logChannel, + permission: 'SendMessages', + name: 'log channel', + }, + ]; + + for (const { channel, permission, name } of requiredPermissions) { + if ( + !channel + .permissionsFor(guild.members.me) + .has(PermissionFlagsBits[permission]) + ) { + return await interaction.editReply( + `I don't have permission to ${permission} in the specified ${name}.` + ); } - - let component; - - if (ticketType === 'modal') { - component = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('createTicket') - .setLabel('Open a ticket') - .setStyle(ButtonStyle.Primary) - .setEmoji('🎫') - ); - } else if (ticketType === 'select') { - component = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('createTicket') - .setPlaceholder('Select the type of support you need') - .addOptions( - setupTicket.customOptions.map((option) => ({ - label: option.label, - value: option.value, - description: option.description, - })) - ) - ); - } else { - return interaction.reply('Please select the correct'); - } - - const message = await ticketChannel.send({ - embeds: [ticketCreateEmbed], - components: [component], + } + + const ticketCreateEmbed = new EmbedBuilder() + .setTitle('Support Ticket System') + .setDescription('Create a support ticket') + .setColor('Blue') + .setFooter({ text: 'Support Tickets' }) + .setTimestamp(); + + let setupTicket = await ticketSetupSchema.findOne({ guildID: guild.id }); + + if (!setupTicket) { + setupTicket = new ticketSetupSchema({ + guildID: guild.id, + ticketChannelID: ticketChannel.id, + staffRoleID: staffRole.id, + categoryID: category.id, + logChannelID: logChannel.id, + ticketType: ticketType, + customOptions: [ + { + label: 'General Support', + value: 'general_support', + description: 'Get help with general issues', + }, + { + label: 'Technical Support', + value: 'technical_support', + description: 'Get help with technical problems', + }, + ], }); - - setupTicket.messageID = message.id; - await setupTicket.save(); - - const ticketSetupEmbed = new EmbedBuilder() - .setTitle('Ticket System Setup') - .setColor('Green') - .setDescription( - 'Ticket system setup complete with the following settings:' - ) - .addFields( - { name: 'Ticket Channel', value: `${ticketChannel}`, inline: true }, - { name: 'Category', value: `${category}`, inline: true }, - { name: 'Log Channel', value: `${logChannel}`, inline: true }, - { name: 'Staff Role', value: `${staffRole}`, inline: true }, - { - name: 'Ticket Type', - value: ticketType.charAt(0).toUpperCase() + ticketType.slice(1), - inline: true, - }, - { name: 'Message ID', value: message.id, inline: true } - ) - .setTimestamp(); - - await interaction.editReply({ - content: 'Ticket system setup successful!', - embeds: [ticketSetupEmbed], - }); - } catch (error) { - console.error('Error during ticket setup:', error); - await interaction.editReply( - 'There was an error during the ticket setup. Please check my permissions and try again.' + } else { + setupTicket.ticketChannelID = ticketChannel.id; + setupTicket.staffRoleID = staffRole.id; + setupTicket.categoryID = category.id; + setupTicket.logChannelID = logChannel.id; + setupTicket.ticketType = ticketType; + } + + let component; + + if (ticketType === 'modal') { + component = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('createTicket') + .setLabel('Open a ticket') + .setStyle(ButtonStyle.Primary) + .setEmoji('🎫') + ); + } else if (ticketType === 'select') { + component = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId('createTicket') + .setPlaceholder('Select the type of support you need') + .addOptions( + setupTicket.customOptions.map((option) => ({ + label: option.label, + value: option.value, + description: option.description, + })) + ) ); - } + } else { + return interaction.reply('Please select the correct'); + } + + const message = await ticketChannel.send({ + embeds: [ticketCreateEmbed], + components: [component], + }); + + setupTicket.messageID = message.id; + await setupTicket.save(); + + const ticketSetupEmbed = new EmbedBuilder() + .setTitle('Ticket System Setup') + .setColor('Green') + .setDescription( + 'Ticket system setup complete with the following settings:' + ) + .addFields( + { name: 'Ticket Channel', value: `${ticketChannel}`, inline: true }, + { name: 'Category', value: `${category}`, inline: true }, + { name: 'Log Channel', value: `${logChannel}`, inline: true }, + { name: 'Staff Role', value: `${staffRole}`, inline: true }, + { + name: 'Ticket Type', + value: ticketType.charAt(0).toUpperCase() + ticketType.slice(1), + inline: true, + }, + { name: 'Message ID', value: message.id, inline: true } + ) + .setTimestamp(); + + await interaction.editReply({ + content: 'Ticket system setup successful!', + embeds: [ticketSetupEmbed], + }); + } catch (error) { + console.error('Error during ticket setup:', error); + await interaction.editReply( + 'There was an error during the ticket setup. Please check my permissions and try again.' + ); + } } async function handleUpdate(interaction) { - const { guild } = interaction; - - await interaction.deferReply({ ephemeral: true }); + const { guild } = interaction; - try { - const setupTicket = await ticketSetupSchema.findOne({ - guildID: guild.id, - }); - if (!setupTicket) { - return await interaction.editReply( - 'No ticket setup found for this guild. Please set up the ticket system first.' - ); - } + await interaction.deferReply({ ephemeral: true }); - const ticketChannel = await guild.channels.cache.get( - setupTicket.ticketChannelID + try { + const setupTicket = await ticketSetupSchema.findOne({ + guildID: guild.id, + }); + if (!setupTicket) { + return await interaction.editReply( + 'No ticket setup found for this guild. Please set up the ticket system first.' ); - if (!ticketChannel) { - return await interaction.editReply( - 'The ticket channel no longer exists. Please set up the ticket system again.' - ); - } - - let ticketMessage; - try { - ticketMessage = await ticketChannel.messages.cache.get( - setupTicket.messageID - ); - } catch (error) { - return await interaction.editReply( - 'Unable to find the ticket system message. It may have been deleted. Please set up the ticket system again.' - ); - } - - const ticketCreateEmbed = new EmbedBuilder() - .setTitle('Support Ticket System') - .setDescription('Create a support ticket') - .setColor('Blue') - .setFooter({ text: 'Support Tickets' }) - .setTimestamp(); - - let component; - - if (ticketType === 'modal') { - component = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('createTicket') - .setLabel('Open a ticket') - .setStyle(ButtonStyle.Primary) - .setEmoji('🎫') - ); - } else if (ticketType === 'select') { - component = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('createTicket') - .setPlaceholder('Select the type of support you need') - .addOptions( - setupTicket.customOptions.map((option) => ({ - label: option.label, - value: option.value, - description: option.description, - })) - ) - ); - } else { - return interaction.reply('Please select the correct'); - } - - await ticketMessage.edit({ - embeds: [ticketCreateEmbed], - components: [component], - }); + } + + const ticketChannel = await guild.channels.cache.get( + setupTicket.ticketChannelID + ); + if (!ticketChannel) { + return await interaction.editReply( + 'The ticket channel no longer exists. Please set up the ticket system again.' + ); + } - const updateEmbed = new EmbedBuilder() - .setTitle('Ticket System Updated') - .setColor('Green') - .setDescription( - 'The ticket system message has been updated with the current settings.' - ) - .addFields( - { name: 'Message ID', value: setupTicket.messageID, inline: true }, - { - name: 'Ticket Channel', - value: `<#${setupTicket.ticketChannelID}>`, - inline: true, - }, - { - name: 'Ticket Type', - value: - setupTicket.ticketType.charAt(0).toUpperCase() + - setupTicket.ticketType.slice(1), - inline: true, - } - ) - .setTimestamp(); - - await interaction.editReply({ - content: 'Ticket system message updated successfully!', - embeds: [updateEmbed], - }); - } catch (error) { - console.error('Error during ticket update:', error); - await interaction.editReply( - 'There was an error during the ticket system update. Please try again.' + let ticketMessage; + try { + ticketMessage = await ticketChannel.messages.cache.get( + setupTicket.messageID + ); + } catch (error) { + return await interaction.editReply( + 'Unable to find the ticket system message. It may have been deleted. Please set up the ticket system again.' + ); + } + + const ticketCreateEmbed = new EmbedBuilder() + .setTitle('Support Ticket System') + .setDescription('Create a support ticket') + .setColor('Blue') + .setFooter({ text: 'Support Tickets' }) + .setTimestamp(); + + let component; + + if (ticketType === 'modal') { + component = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('createTicket') + .setLabel('Open a ticket') + .setStyle(ButtonStyle.Primary) + .setEmoji('🎫') ); - } + } else if (ticketType === 'select') { + component = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId('createTicket') + .setPlaceholder('Select the type of support you need') + .addOptions( + setupTicket.customOptions.map((option) => ({ + label: option.label, + value: option.value, + description: option.description, + })) + ) + ); + } else { + return interaction.reply('Please select the correct'); + } + + await ticketMessage.edit({ + embeds: [ticketCreateEmbed], + components: [component], + }); + + const updateEmbed = new EmbedBuilder() + .setTitle('Ticket System Updated') + .setColor('Green') + .setDescription( + 'The ticket system message has been updated with the current settings.' + ) + .addFields( + { name: 'Message ID', value: setupTicket.messageID, inline: true }, + { + name: 'Ticket Channel', + value: `<#${setupTicket.ticketChannelID}>`, + inline: true, + }, + { + name: 'Ticket Type', + value: + setupTicket.ticketType.charAt(0).toUpperCase() + + setupTicket.ticketType.slice(1), + inline: true, + } + ) + .setTimestamp(); + + await interaction.editReply({ + content: 'Ticket system message updated successfully!', + embeds: [updateEmbed], + }); + } catch (error) { + console.error('Error during ticket update:', error); + await interaction.editReply( + 'There was an error during the ticket system update. Please try again.' + ); + } } async function handleRemove(interaction) { - const { guild } = interaction; + const { guild } = interaction; - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); - try { - const setupTicket = await ticketSetupSchema.findOneAndDelete({ - guildID: guild.id, - }); - - if (!setupTicket) { - return await interaction.editReply( - 'No ticket setup found for this guild.' - ); - } + try { + const setupTicket = await ticketSetupSchema.findOneAndDelete({ + guildID: guild.id, + }); - const ticketChannel = await guild.channels.cache.get( - setupTicket.ticketChannelID + if (!setupTicket) { + return await interaction.editReply( + 'No ticket setup found for this guild.' ); - if (ticketChannel) { - try { - const ticketMessage = await ticketChannel.messages.cache.get( - setupTicket.messageID - ); - if (ticketMessage) { - await ticketMessage.delete(); - } - } catch (error) { - console.error('Error deleting ticket message:', error); - } - } + } - await interaction.editReply( - 'Ticket system setup has been removed for the guild and the ticket message has been deleted.' - ); - } catch (error) { - console.error('Error during ticket removal:', error); - await interaction.editReply( - 'There was an error during the removal of the ticket setup. Please try again later.' - ); - } + const ticketChannel = await guild.channels.cache.get( + setupTicket.ticketChannelID + ); + if (ticketChannel) { + try { + const ticketMessage = await ticketChannel.messages.cache.get( + setupTicket.messageID + ); + if (ticketMessage) { + await ticketMessage.delete(); + } + } catch (error) { + console.error('Error deleting ticket message:', error); + } + } + + await interaction.editReply( + 'Ticket system setup has been removed for the guild and the ticket message has been deleted.' + ); + } catch (error) { + console.error('Error during ticket removal:', error); + await interaction.editReply( + 'There was an error during the removal of the ticket setup. Please try again later.' + ); + } } async function handleStatus(interaction) { - const { guild } = interaction; - - await interaction.deferReply({ ephemeral: true }); + const { guild } = interaction; - try { - const setupTicket = await ticketSetupSchema.findOne({ - guildID: guild.id, - }); + await interaction.deferReply({ ephemeral: true }); - if (!setupTicket) { - return await interaction.editReply( - 'No ticket setup found for this guild.' - ); - } + try { + const setupTicket = await ticketSetupSchema.findOne({ + guildID: guild.id, + }); - const statusEmbed = new EmbedBuilder() - .setTitle('Ticket System Status') - .setColor('Blue') - .setDescription('Current ticket system configuration:') - .addFields( - { - name: 'Ticket Channel', - value: `<#${setupTicket.ticketChannelID}>`, - inline: true, - }, - { - name: 'Category', - value: `<#${setupTicket.categoryID}>`, - inline: true, - }, - { - name: 'Log Channel', - value: `<#${setupTicket.logChannelID}>`, - inline: true, - }, - { - name: 'Staff Role', - value: `<@&${setupTicket.staffRoleID}>`, - inline: true, - }, - { - name: 'Ticket Type', - value: - setupTicket.ticketType.charAt(0).toUpperCase() + - setupTicket.ticketType.slice(1), - inline: true, - }, - { name: 'Message ID', value: setupTicket.messageID, inline: true } - ) - .addField( - 'Custom Options', - setupTicket.customOptions - .map((option) => `${option.label} (${option.value})`) - .join('\n') - ) - .setTimestamp(); - - await interaction.editReply({ embeds: [statusEmbed] }); - } catch (error) { - console.error('Error fetching ticket status:', error); - await interaction.editReply( - 'There was an error fetching the ticket system status. Please try again later.' + if (!setupTicket) { + return await interaction.editReply( + 'No ticket setup found for this guild.' ); - } + } + + const statusEmbed = new EmbedBuilder() + .setTitle('Ticket System Status') + .setColor('Blue') + .setDescription('Current ticket system configuration:') + .addFields( + { + name: 'Ticket Channel', + value: `<#${setupTicket.ticketChannelID}>`, + inline: true, + }, + { + name: 'Category', + value: `<#${setupTicket.categoryID}>`, + inline: true, + }, + { + name: 'Log Channel', + value: `<#${setupTicket.logChannelID}>`, + inline: true, + }, + { + name: 'Staff Role', + value: `<@&${setupTicket.staffRoleID}>`, + inline: true, + }, + { + name: 'Ticket Type', + value: + setupTicket.ticketType.charAt(0).toUpperCase() + + setupTicket.ticketType.slice(1), + inline: true, + }, + { name: 'Message ID', value: setupTicket.messageID, inline: true } + ) + .addField( + 'Custom Options', + setupTicket.customOptions + .map((option) => `${option.label} (${option.value})`) + .join('\n') + ) + .setTimestamp(); + + await interaction.editReply({ embeds: [statusEmbed] }); + } catch (error) { + console.error('Error fetching ticket status:', error); + await interaction.editReply( + 'There was an error fetching the ticket system status. Please try again later.' + ); + } } async function handleAddOption(interaction) { - const { guild, options } = interaction; + const { guild, options } = interaction; - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); - try { - const label = options.getString('label'); - const value = options.getString('value'); - const description = options.getString('description'); + try { + const label = options.getString('label'); + const value = options.getString('value'); + const description = options.getString('description'); - const setupTicket = await ticketSetupSchema.findOne({ - guildID: guild.id, - }); - if (!setupTicket) { - return await interaction.editReply( - 'No ticket setup found for this guild. Please set up the ticket system first.' - ); - } - - if (setupTicket.customOptions.some((option) => option.value === value)) { - return await interaction.editReply( - 'An option with this value already exists.' - ); - } + const setupTicket = await ticketSetupSchema.findOne({ + guildID: guild.id, + }); + if (!setupTicket) { + return await interaction.editReply( + 'No ticket setup found for this guild. Please set up the ticket system first.' + ); + } - const existingLabel = setupTicket.customOptions.find( - (opt) => opt.label === label + if (setupTicket.customOptions.some((option) => option.value === value)) { + return await interaction.editReply( + 'An option with this value already exists.' ); - if (existingLabel) { - return await interaction.editReply({ - content: `The label "${label}" already exists.`, - ephemeral: true, - }); - } + } + + const existingLabel = setupTicket.customOptions.find( + (opt) => opt.label === label + ); + if (existingLabel) { + return await interaction.editReply({ + content: `The label "${label}" already exists.`, + ephemeral: true, + }); + } - setupTicket.customOptions.push({ label, value, description }); + setupTicket.customOptions.push({ label, value, description }); - // Update the ticket channel message - const ticketChannel = await guild.channels.cache.get( - setupTicket.ticketChannelID + // Update the ticket channel message + const ticketChannel = await guild.channels.cache.get( + setupTicket.ticketChannelID + ); + if (ticketChannel) { + const ticketMessage = await ticketChannel.messages.cache.get( + setupTicket.messageID ); - if (ticketChannel) { - const ticketMessage = await ticketChannel.messages.cache.get( - setupTicket.messageID - ); - if (ticketMessage) { - const component = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('createTicket') - .setPlaceholder('Select the type of support you need') - .addOptions( - setupTicket.customOptions.map((option) => ({ - label: option.label, - value: option.value, - description: option.description, - })) - ) - ); - await ticketMessage.edit({ - components: [component], - }); - } + if (ticketMessage) { + const component = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId('createTicket') + .setPlaceholder('Select the type of support you need') + .addOptions( + setupTicket.customOptions.map((option) => ({ + label: option.label, + value: option.value, + description: option.description, + })) + ) + ); + await ticketMessage.edit({ + components: [component], + }); } + } - await setupTicket.save(); - - const successEmbed = new EmbedBuilder() - .setTitle('Label Added') - .setDescription( - `The label "${label}" (${value}) has been added successfully.` - ) - .setColor('Green') - .setTimestamp(); - - await interaction.editReply({ embeds: [successEmbed] }); - } catch (error) { - console.error('Error adding label:', error); - await interaction.editReply({ - content: - 'There was an error adding the label. Please try again later.', - ephemeral: true, - }); - } + await setupTicket.save(); + + const successEmbed = new EmbedBuilder() + .setTitle('Label Added') + .setDescription( + `The label "${label}" (${value}) has been added successfully.` + ) + .setColor('Green') + .setTimestamp(); + + await interaction.editReply({ embeds: [successEmbed] }); + } catch (error) { + console.error('Error adding label:', error); + await interaction.editReply({ + content: 'There was an error adding the label. Please try again later.', + ephemeral: true, + }); + } } async function handleRemoveOption(interaction) { - const { guild, options } = interaction; + const { guild, options } = interaction; - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); - try { - const value = options.getString('value'); + try { + const value = options.getString('value'); - const setupTicket = await ticketSetupSchema.findOne({ - guildID: guild.id, - }); - if (!setupTicket) { - return await interaction.editReply( - 'No ticket setup found for this guild. Please set up the ticket system first.' - ); - } - - const optionIndex = setupTicket.customOptions.findIndex( - (option) => option.value === value + const setupTicket = await ticketSetupSchema.findOne({ + guildID: guild.id, + }); + if (!setupTicket) { + return await interaction.editReply( + 'No ticket setup found for this guild. Please set up the ticket system first.' ); - if (optionIndex === -1) { - return await interaction.editReply( - 'No option with the provided value exists.' - ); - } + } + + const optionIndex = setupTicket.customOptions.findIndex( + (option) => option.value === value + ); + if (optionIndex === -1) { + return await interaction.editReply( + 'No option with the provided value exists.' + ); + } - const removedOption = setupTicket.customOptions.splice(optionIndex, 1)[0]; + const removedOption = setupTicket.customOptions.splice(optionIndex, 1)[0]; - // Update the ticket channel message - const ticketChannel = await guild.channels.cache.get( - setupTicket.ticketChannelID + // Update the ticket channel message + const ticketChannel = await guild.channels.cache.get( + setupTicket.ticketChannelID + ); + if (ticketChannel) { + const ticketMessage = await ticketChannel.messages.cache.get( + setupTicket.messageID ); - if (ticketChannel) { - const ticketMessage = await ticketChannel.messages.cache.get( - setupTicket.messageID - ); - if (ticketMessage) { - const component = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId('createTicket') - .setPlaceholder('Select the type of support you need') - .addOptions( - setupTicket.customOptions.map((option) => ({ - label: option.label, - value: option.value, - description: option.description, - })) - ) - ); - await ticketMessage.edit({ - components: [component], - }); - } + if (ticketMessage) { + const component = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId('createTicket') + .setPlaceholder('Select the type of support you need') + .addOptions( + setupTicket.customOptions.map((option) => ({ + label: option.label, + value: option.value, + description: option.description, + })) + ) + ); + await ticketMessage.edit({ + components: [component], + }); } + } - await setupTicket.save(); - - const successEmbed = new EmbedBuilder() - .setTitle('Option Removed') - .setDescription( - `The option "${removedOption.label}" (${removedOption.value}) has been removed.` - ) - .setColor('Green') - .setTimestamp(); - - await interaction.editReply({ embeds: [successEmbed] }); - } catch (error) { - console.error('Error removing option:', error); - await interaction.editReply({ - content: - 'There was an error removing the option. Please try again later.', - ephemeral: true, - }); - } + await setupTicket.save(); + + const successEmbed = new EmbedBuilder() + .setTitle('Option Removed') + .setDescription( + `The option "${removedOption.label}" (${removedOption.value}) has been removed.` + ) + .setColor('Green') + .setTimestamp(); + + await interaction.editReply({ embeds: [successEmbed] }); + } catch (error) { + console.error('Error removing option:', error); + await interaction.editReply({ + content: + 'There was an error removing the option. Please try again later.', + ephemeral: true, + }); + } } diff --git a/src/config/config.js b/src/config/config.js index c186fa0..601c12c 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -1,8 +1,36 @@ +/** + * The configuration object for the bot. + * @type {Object} + */ export const config = { + /** + * The ID of the test server. + * @type {string} + */ testServerId: "1207374906296246282", + /** + * The ID of the log channel. + * @type {string} + */ logChannel: "1244852688106295316", + /** + * The IDs of the developers. + * @type {string[]} + */ developersId: ["660117771421614085", "598554287244574731", "1215648186643906643"], + /** + * The ID of the other log channel. + * @type {string} + */ otherLogChannel: "1243878147057782804", + /** + * The duration to cache data in seconds. + * @type {number} + */ cacheDuration: 300, + /** + * Whether the bot is in maintenance mode. + * @type {boolean} + */ maintenance: false, }; diff --git a/src/config/emoji.js b/src/config/emoji.js index 81cfffe..12476f5 100644 --- a/src/config/emoji.js +++ b/src/config/emoji.js @@ -1,6 +1,6 @@ const Emoji = { - slotlodin: '', - coin: '' || '💰', + slotlodin: '', + coin: '' || '💰', }; export default Emoji; diff --git a/src/config/messageConfig.js b/src/config/messageConfig.js index a776a6d..014607a 100644 --- a/src/config/messageConfig.js +++ b/src/config/messageConfig.js @@ -1,25 +1,25 @@ const messageConfig = { - embedColorSuccess: '00D26A', - embedColorWarning: 'FFCC4D', - embedColorError: 'FB2F61', - embedErrorMessage: '`❌` An error occurred!', - commandDevOnly: '`❌` This command can only be executed by developers.', - commandTestMode: - '`❌` This is in development and cannot be executed in this server.', - nsfw: '`❌` This command can only be used in NSFW channels.', - commandCooldown: - 'Please wait {time} seconds before using this command again.', - commandPremiumOnly: - '`❌` This command can only be executed by premium users.', - userNoPermissions: - '`❌` You do not have enough permissions to execute this command.', - botNoPermissions: - '`❌` I do not have enough permissions to execute this command.', - hasHigherRolePosition: - '`❌` This person has a higher role than you have or has the same role as you have.', - unableToInteractWithYourself: '`❌` You cannot do that to yourself.', - cannotUseButton: '`❌` You cannot use this button.', - cannotUseSelect: '`❌` You cannot use this Select.', + embedColorSuccess: '00D26A', + embedColorWarning: 'FFCC4D', + embedColorError: 'FB2F61', + embedErrorMessage: '`❌` An error occurred!', + commandDevOnly: '`❌` This command can only be executed by developers.', + commandTestMode: + '`❌` This is in development and cannot be executed in this server.', + nsfw: '`❌` This command can only be used in NSFW channels.', + commandCooldown: + 'Please wait {time} seconds before using this command again.', + commandPremiumOnly: + '`❌` This command can only be executed by premium users.', + userNoPermissions: + '`❌` You do not have enough permissions to execute this command.', + botNoPermissions: + '`❌` I do not have enough permissions to execute this command.', + hasHigherRolePosition: + '`❌` This person has a higher role than you have or has the same role as you have.', + unableToInteractWithYourself: '`❌` You cannot do that to yourself.', + cannotUseButton: '`❌` You cannot use this button.', + cannotUseSelect: '`❌` You cannot use this Select.', }; export default messageConfig; diff --git a/src/contextmenus/img/avatarCTM.js b/src/contextmenus/img/avatarCTM.js index 0161d8a..1d9bbd4 100644 --- a/src/contextmenus/img/avatarCTM.js +++ b/src/contextmenus/img/avatarCTM.js @@ -1,46 +1,69 @@ import { - ContextMenuCommandBuilder, - ApplicationCommandType, - EmbedBuilder, + ContextMenuCommandBuilder, + ApplicationCommandType, + EmbedBuilder, } from 'discord.js'; +/** + * Represents the User Avatar context menu command. + * This command displays the avatar of a targeted user in a Discord server. + */ export default { - data: new ContextMenuCommandBuilder() - .setName('User Avatar') - .setType(ApplicationCommandType.User), - userPermissions: [], - botPermissions: [], + /** + * The data for the context menu command. + * @type {ContextMenuCommandBuilder} + */ + data: new ContextMenuCommandBuilder() + .setName('User Avatar') // Sets the name of the command to "User Avatar" + .setType(ApplicationCommandType.User), // Specifies the type of the command as User - run: async (client, interaction) => { - try { - const user = interaction.targetUser; + /** + * The permissions required by the user to use this command. + * @type {string[]} + */ + userPermissions: [], - // Get the avatar URL of the targeted user - const avatar = user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }); + /** + * The permissions required by the bot to use this command. + * @type {string[]} + */ + botPermissions: [], - // Construct embed to display the targeted user's avatar - const embed = new EmbedBuilder() - .setTitle(`${user.username}'s Avatar`) // Set the title as the username followed by "Avatar" - .setURL(avatar) // Set the URL of the embed to the avatar URL - .setImage(avatar) // Set the image of the embed to the avatar URL - .setFooter({ - text: `Requested by ${interaction.user.username}`, // Set the footer text as the username of the requester - iconURL: interaction.user.displayAvatarURL({ - format: 'png', - dynamic: true, - size: 1024, - }), // Set the footer icon as the requester's avatar - }) - .setColor('#eb3434'); // Set the embed color + /** + * Executes the command. + * @param {Client} client - The Discord client instance. + * @param {Interaction} interaction - The interaction that triggered the command. + */ + run: async (client, interaction) => { + try { + const user = interaction.targetUser; // Retrieves the targeted user - // Send the embed containing the targeted user's avatar as a reply - await interaction.reply({ embeds: [embed] }); - } catch (error) { - console.log(error); - } - }, + // Retrieves the avatar URL of the targeted user + const avatar = user.displayAvatarURL({ + format: 'png', // Sets the format of the avatar to PNG + dynamic: true, // Allows the avatar to be dynamic + size: 1024, // Sets the size of the avatar to 1024x1024 + }); + + // Constructs an embed to display the targeted user's avatar + const embed = new EmbedBuilder() + .setTitle(`${user.username}'s Avatar`) // Sets the title of the embed to the username followed by "Avatar" + .setURL(avatar) // Sets the URL of the embed to the avatar URL + .setImage(avatar) // Sets the image of the embed to the avatar URL + .setFooter({ + text: `Requested by ${interaction.user.username}`, // Sets the footer text to the username of the requester + iconURL: interaction.user.displayAvatarURL({ + format: 'png', // Sets the format of the footer icon to PNG + dynamic: true, // Allows the footer icon to be dynamic + size: 1024, // Sets the size of the footer icon to 1024x1024 + }), // Sets the footer icon to the requester's avatar + }) + .setColor('#eb3434'); // Sets the color of the embed + + // Sends the embed containing the targeted user's avatar as a reply + await interaction.reply({ embeds: [embed] }); + } catch (error) { + console.log(error); // Logs any errors that occur during the execution of the command + } + }, }; diff --git a/src/events/guildMemberAdd/welcom.js b/src/events/guildMemberAdd/welcom.js index cacd98d..562d51c 100644 --- a/src/events/guildMemberAdd/welcom.js +++ b/src/events/guildMemberAdd/welcom.js @@ -7,119 +7,187 @@ import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const BACKGROUND_PATH = path.join(__dirname, '..', '..', 'assets', 'welcome.png'); +const BACKGROUND_PATH = path.join( + __dirname, + '..', + '..', + 'assets', + 'welcome.png' +); const DEFAULT_WELCOME_MESSAGE = 'Welcome to our server, {user}!'; +/** + * Handles the welcome event for a new guild member. + * + * This function is responsible for sending a welcome message to a new member + * in a specified channel. It also assigns an auto-role if configured. + * + * @param {Client} client - The Discord client instance. + * @param {ErrorHandler} errorHandler - The error handler instance. + * @param {GuildMember} member - The new guild member. + */ export default async (client, errorHandler, member) => { - try { - const welcomeSettings = await getWelcomeSettings(member.guild.id); - if (!welcomeSettings?.enabled) return; - - const { welcomeChannel, autoRole } = await getGuildResources(member.guild, welcomeSettings); - if (!welcomeChannel) return; - - const welcomeImage = await generateWelcomeImage(member); - const embed = createWelcomeEmbed(member, welcomeSettings, welcomeImage); - - await sendWelcomeMessage(welcomeChannel, member, embed, welcomeImage); - if (autoRole) await assignAutoRole(member, autoRole); - } catch (error) { - handleError(errorHandler, error, 'Welcome Event', member); - } + try { + const welcomeSettings = await getWelcomeSettings(member.guild.id); + if (!welcomeSettings?.enabled) return; + + const { welcomeChannel, autoRole } = await getGuildResources( + member.guild, + welcomeSettings + ); + if (!welcomeChannel) return; + + const welcomeImage = await generateWelcomeImage(member); + const embed = createWelcomeEmbed(member, welcomeSettings, welcomeImage); + + await sendWelcomeMessage(welcomeChannel, member, embed, welcomeImage); + if (autoRole) await assignAutoRole(member, autoRole); + } catch (error) { + handleError(errorHandler, error, 'Welcome Event', member); + } }; -// Fetch welcome settings from the database +/** + * Fetches welcome settings from the database for a given guild. + * + * @param {string} guildId - The ID of the guild. + * @returns {Promise} - A promise that resolves to the welcome settings. + */ async function getWelcomeSettings(guildId) { - try { - return await Welcome.findOne({ guildId }); - } catch (error) { - console.error('Error fetching welcome settings:', error); - return null; - } + try { + return await Welcome.findOne({ guildId }); + } catch (error) { + console.error('Error fetching welcome settings:', error); + return null; + } } -// Get the welcome channel and auto-role based on settings +/** + * Retrieves the welcome channel and auto-role based on the welcome settings. + * + * @param {Guild} guild - The guild instance. + * @param {WelcomeSettings} welcomeSettings - The welcome settings. + * @returns {Promise<{welcomeChannel: Channel, autoRole: Role}>} - A promise that resolves to an object containing the welcome channel and auto-role. + */ async function getGuildResources(guild, welcomeSettings) { - const welcomeChannel = guild.channels.cache.get(welcomeSettings.channelId); - const autoRole = guild.roles.cache.get(welcomeSettings.roleId); - - if (!welcomeChannel) { - console.warn(`Welcome channel with ID ${welcomeSettings.channelId} not found in guild ${guild.id}.`); - } - - if (!autoRole) { - console.warn(`Auto role with ID ${welcomeSettings.roleId} not found in guild ${guild.id}.`); - } - - return { welcomeChannel, autoRole }; + const welcomeChannel = guild.channels.cache.get(welcomeSettings.channelId); + const autoRole = guild.roles.cache.get(welcomeSettings.roleId); + + if (!welcomeChannel) { + console.warn( + `Welcome channel with ID ${welcomeSettings.channelId} not found in guild ${guild.id}.` + ); + } + + if (!autoRole) { + console.warn( + `Auto role with ID ${welcomeSettings.roleId} not found in guild ${guild.id}.` + ); + } + + return { welcomeChannel, autoRole }; } -// Generate the welcome image using the profileImage function +/** + * Generates a welcome image for the new member using the profileImage function. + * + * @param {GuildMember} member - The new guild member. + * @returns {Promise} - A promise that resolves to the welcome image buffer. + */ async function generateWelcomeImage(member) { - try { - return await profileImage(member.user.id, { - customTag: `Member #${member.guild.memberCount}`, - customBackground: BACKGROUND_PATH, - }); - } catch (error) { - console.error('Error generating welcome image:', error); - return null; - } + try { + return await profileImage(member.user.id, { + customTag: `Member #${member.guild.memberCount}`, + customBackground: BACKGROUND_PATH, + }); + } catch (error) { + console.error('Error generating welcome image:', error); + return null; + } } -// Create the welcome embed with the specified settings +/** + * Creates a welcome embed with the specified settings. + * + * @param {GuildMember} member - The new guild member. + * @param {WelcomeSettings} welcomeSettings - The welcome settings. + * @param {Buffer} welcomeImage - The welcome image buffer. + * @returns {EmbedBuilder} - The welcome embed. + */ function createWelcomeEmbed(member, welcomeSettings, welcomeImage) { - const welcomeMessage = (welcomeSettings.message || DEFAULT_WELCOME_MESSAGE) - .replace('{user}', `<@${member.id}>`); - - const embed = new EmbedBuilder() - .setColor('#0099ff') - .setTitle(`Welcome to ${member.guild.name}!`) - .setDescription(welcomeMessage) - .setTimestamp() - .setFooter({ text: `Joined: ${member.joinedAt.toUTCString()}` }); - - if (welcomeImage) { - embed.setImage('attachment://welcome-image.png'); - } - - return embed; + const welcomeMessage = ( + welcomeSettings.message || DEFAULT_WELCOME_MESSAGE + ).replace('{user}', `<@${member.id}>`); + + const embed = new EmbedBuilder() + .setColor('#0099ff') + .setTitle(`Welcome to ${member.guild.name}!`) + .setDescription(welcomeMessage) + .setTimestamp() + .setFooter({ text: `Joined: ${member.joinedAt.toUTCString()}` }); + + if (welcomeImage) { + embed.setImage('attachment://welcome-image.png'); + } + + return embed; } -// Send the welcome message to the specified channel +/** + * Sends the welcome message to the specified channel. + * + * @param {Channel} channel - The channel to send the welcome message to. + * @param {GuildMember} member - The new guild member. + * @param {EmbedBuilder} embed - The welcome embed. + * @param {Buffer} welcomeImage - The welcome image buffer. + */ async function sendWelcomeMessage(channel, member, embed, welcomeImage) { - const messageOptions = { - content: `Hey everyone, please welcome <@${member.id}>!`, - embeds: [embed], - }; - - if (welcomeImage) { - const attachment = new AttachmentBuilder(welcomeImage, { name: 'welcome-image.png' }); - messageOptions.files = [attachment]; - } - - try { - await channel.send(messageOptions); - } catch (error) { - console.error('Error sending welcome message:', error); - } + const messageOptions = { + content: `Hey everyone, please welcome <@${member.id}>!`, + embeds: [embed], + }; + + if (welcomeImage) { + const attachment = new AttachmentBuilder(welcomeImage, { + name: 'welcome-image.png', + }); + messageOptions.files = [attachment]; + } + + try { + await channel.send(messageOptions); + } catch (error) { + console.error('Error sending welcome message:', error); + } } -// Assign the auto-role to the new member +/** + * Assigns the auto-role to the new member. + * + * @param {GuildMember} member - The new guild member. + * @param {Role} role - The auto-role to assign. + */ async function assignAutoRole(member, role) { - try { - await member.roles.add(role); - } catch (error) { - console.error(`Error assigning auto role to member ${member.id}:`, error); - } + try { + await member.roles.add(role); + } catch (error) { + console.error(`Error assigning auto role to member ${member.id}:`, error); + } } -// Handle errors and log them appropriately +/** + * Handles errors and logs them appropriately. + * + * @param {ErrorHandler} errorHandler - The error handler instance. + * @param {Error} error - The error to handle. + * @param {string} eventType - The type of event that triggered the error. + * @param {GuildMember} member - The guild member related to the error. + */ function handleError(errorHandler, error, eventType, member) { - console.error(`Error in ${eventType} for member ${member.id}:`, error); - errorHandler.handleError(error, { - type: eventType, - memberId: member.id, - guildId: member.guild.id, - }); + console.error(`Error in ${eventType} for member ${member.id}:`, error); + errorHandler.handleError(error, { + type: eventType, + memberId: member.id, + guildId: member.guild.id, + }); } diff --git a/src/events/messageCreate/chat.js b/src/events/messageCreate/chat.js index 49713f4..4a9a9ec 100644 --- a/src/events/messageCreate/chat.js +++ b/src/events/messageCreate/chat.js @@ -1,67 +1,141 @@ +/** + * This module handles chat events related to clienterr actions. + * It defines a set of emojis and GIFs as constants, and a list of actions + * that can be triggered by specific keywords in chat messages. When an action + * is triggered, the corresponding emoji or GIF is sent as a reply to the message. + */ + // Define emoji and GIF URLs as constants const EMOJIS = { - whip: '<:whip:1223554028794024018>', + /** + * The whip emoji. + */ + whip: '<:whip:1223554028794024018>', }; const GIFS = { - smashClienterr: - 'https://tenor.com/view/rule-1-fakepixel-rule-clienterr-clienterr-smash-fakepixel-gif-11287819569727263061', - promoteClienterr: - 'https://tenor.com/view/promote-clienterr-fakepixel-koban-gif-10325616020765903574', - clienterrverseOnTop: - 'https://tenor.com/view/clienterrverse-clienterr-fakepixel-kobe-gif-3978810040281653181', - tameNory: - 'https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExeTF4eGs3Y2U4bHY0b2FkeGpkaXgwZjdxcGE4d2xnNjlvM2o0cnVpZiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/HOTfzC3IjaJxK/giphy.webp', - demoteclienterr: - 'https://tenor.com/view/clienterr-fakepixel-demo-pls-gif-10228665671878098589', + /** + * The GIF for smashing clienterr. + */ + smashClienterr: + 'https://tenor.com/view/rule-1-fakepixel-rule-clienterr-clienterr-smash-fakepixel-gif-11287819569727263061', + /** + * The GIF for promoting clienterr. + */ + promoteClienterr: + 'https://tenor.com/view/promote-clienterr-fakepixel-koban-gif-10325616020765903574', + /** + * The GIF for clienterrverse on top. + */ + clienterrverseOnTop: + 'https://tenor.com/view/clienterrverse-clienterr-fakepixel-kobe-gif-3978810040281653181', + /** + * The GIF for taming Nory. + */ + tameNory: + 'https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExeTF4eGs3Y2U4bHY0b2FkeGpkaXgwZjdxcGE4d2xnNjlvM2o0cnVpZiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/HOTfzC3IjaJxK/giphy.webp', + /** + * The GIF for demoting clienterr. + */ + demoteclienterr: + 'https://tenor.com/view/clienterr-fakepixel-demo-pls-gif-10228665671878098589', }; // Define actions in a more structured way const ACTIONS = [ - { - regex: /how\s*to\s*tame\s*(koban|ChronoUK|regasky|darkgladiator)/i, - response: EMOJIS.whip, - }, - { - regex: /smash\s*clienterr/i, - response: GIFS.smashClienterr, - }, - { - regex: /promote\s*clienterr/i, - response: GIFS.promoteClienterr, - }, - { - regex: /clienterrverse\s*on\s*top/i, - response: GIFS.clienterrverseOnTop, - }, - { - regex: /how\s*to\s*tame\s*(nory|norysight)/i, - response: GIFS.tameNory, - }, - { - regex: /demote\s*clienterr/i, - response: GIFS.demoteclienterr, - }, + { + /** + * Regular expression to match messages asking how to tame specific entities. + */ + regex: /how\s*to\s*tame\s*(koban|ChronoUK|regasky|darkgladiator)/i, + /** + * The response to send when the above regex matches. + */ + response: EMOJIS.whip, + }, + { + /** + * Regular expression to match messages asking to smash clienterr. + */ + regex: /smash\s*clienterr/i, + /** + * The response to send when the above regex matches. + */ + response: GIFS.smashClienterr, + }, + { + /** + * Regular expression to match messages asking to promote clienterr. + */ + regex: /promote\s*clienterr/i, + /** + * The response to send when the above regex matches. + */ + response: GIFS.promoteClienterr, + }, + { + /** + * Regular expression to match messages asking about clienterrverse on top. + */ + regex: /clienterrverse\s*on\s*top/i, + /** + * The response to send when the above regex matches. + */ + response: GIFS.clienterrverseOnTop, + }, + { + /** + * Regular expression to match messages asking how to tame Nory. + */ + regex: /how\s*to\s*tame\s*(nory|norysight)/i, + /** + * The response to send when the above regex matches. + */ + response: GIFS.tameNory, + }, + { + /** + * Regular expression to match messages asking to demote clienterr. + */ + regex: /demote\s*clienterr/i, + /** + * The response to send when the above regex matches. + */ + response: GIFS.demoteclienterr, + }, ]; +/** + * Handles sending a response to a message based on the action triggered. + * + * @param {Message} message - The message that triggered the action. + * @param {String} response - The response to send. + */ const handleAction = async (message, response) => { - try { - await message.reply(response); - } catch (error) { - console.error('Error sending message:', error); - await message.channel.send( - 'Oops! Something went wrong while processing your request.' - ); - } + try { + await message.reply(response); + } catch (error) { + console.error('Error sending message:', error); + await message.channel.send( + 'Oops! Something went wrong while processing your request.' + ); + } }; +/** + * The main function that processes chat messages and triggers actions. + * + * @param {Client} client - The Discord client. + * @param {Function} errorHandler - The error handler function. + * @param {Message} message - The message to process. + */ export default async (client, errorHandler, message) => { - try { - const action = ACTIONS.find(({ regex }) => regex.test(message.content)); - if (action) { - await handleAction(message, action.response); - } - } catch (error) { - errorHandler.handleError(error, { type: 'chatrega' }); - } + try { + const action = ACTIONS.find(({ regex }) => regex.test(message.content)); + if (action) { + await handleAction(message, action.response); + } + } catch (error) { + errorHandler.handleError(error, { type: 'chatrega' }); + } }; diff --git a/src/events/messageCreate/command.js b/src/events/messageCreate/command.js index 8903211..06e046c 100644 --- a/src/events/messageCreate/command.js +++ b/src/events/messageCreate/command.js @@ -11,217 +11,209 @@ const commandMap = new Map(); let prefixCommandsLoaded = false; export default async (client, errorHandler, message) => { - try { - const { prefix, isExempt } = await getUserPrefixInfo(message.author.id); + try { + const { prefix, isExempt } = await getUserPrefixInfo(message.author.id); - if (!isExempt && !message.content.startsWith(prefix)) return; + if (!isExempt && !message.content.startsWith(prefix)) return; - const commandContent = isExempt - ? message.content - : message.content.slice(prefix.length); + const commandContent = isExempt + ? message.content + : message.content.slice(prefix.length); - await loadPrefixCommandsIfNeeded(client); + await loadPrefixCommandsIfNeeded(client); - const { commandName, args } = parseCommand(commandContent); - const command = findCommand(commandName); + const { commandName, args } = parseCommand(commandContent); + const command = findCommand(commandName); - if (!command) return; + if (!command) return; - await executeCommand(client, message, command, args, errorHandler); - } catch (error) { - console.error('Error in command handler:', error); - await sendEmbedReply( - message, - mConfig.embedColorError, - 'An unexpected error occurred. Please try again later.' - ); - } + await executeCommand(client, message, command, args, errorHandler); + } catch (error) { + console.error('Error in command handler:', error); + await sendEmbedReply( + message, + mConfig.embedColorError, + 'An unexpected error occurred. Please try again later.' + ); + } }; async function getUserPrefixInfo(userId) { - try { - const userPrefixData = await UserPrefix.findOne({ userId }); - return { - prefix: userPrefixData?.prefix || '!', - isExempt: userPrefixData?.exemptFromPrefix || false, - }; - } catch (error) { - console.error('Error fetching user prefix:', error); - return { prefix: '!', isExempt: false }; - } + try { + const userPrefixData = await UserPrefix.findOne({ userId }); + return { + prefix: userPrefixData?.prefix || '!', + isExempt: userPrefixData?.exemptFromPrefix || false, + }; + } catch (error) { + console.error('Error fetching user prefix:', error); + return { prefix: '!', isExempt: false }; + } } async function loadPrefixCommandsIfNeeded(client) { - if (prefixCommandsLoaded) return; - - try { - client.prefixCommands = await convertSlashCommandsToPrefix(); - client.prefixCommands.forEach((cmd) => commandMap.set(cmd.name, cmd)); - prefixCommandsLoaded = true; - } catch (error) { - console.error('Error loading prefix commands:', error); - throw new Error('Failed to load prefix commands'); - } + if (prefixCommandsLoaded) return; + + try { + client.prefixCommands = await convertSlashCommandsToPrefix(); + client.prefixCommands.forEach((cmd) => commandMap.set(cmd.name, cmd)); + prefixCommandsLoaded = true; + } catch (error) { + console.error('Error loading prefix commands:', error); + throw new Error('Failed to load prefix commands'); + } } function parseCommand(content) { - const args = content.trim().split(/ +/); - const commandName = args.shift().toLowerCase(); - return { commandName, args }; + const args = content.trim().split(/ +/); + const commandName = args.shift().toLowerCase(); + return { commandName, args }; } function findCommand(commandName) { - return ( - commandMap.get(commandName) || - Array.from(commandMap.values()).find( - (cmd) => cmd.aliases && cmd.aliases.includes(commandName) - ) - ); + return ( + commandMap.get(commandName) || + Array.from(commandMap.values()).find( + (cmd) => cmd.aliases && cmd.aliases.includes(commandName) + ) + ); } async function executeCommand(client, message, command, args, errorHandler) { - try { - if (!checkCommandPrerequisites(message, command)) return; - - const cooldown = applyCooldown( - message.author.id, - command.name, - (command.cooldown || 3) * 1000 + try { + if (!checkCommandPrerequisites(message, command)) return; + + const cooldown = applyCooldown( + message.author.id, + command.name, + (command.cooldown || 3) * 1000 + ); + if (cooldown.active) { + return sendEmbedReply( + message, + mConfig.embedColorError, + mConfig.commandCooldown.replace('{time}', cooldown.timeLeft) ); - if (cooldown.active) { - return sendEmbedReply( - message, - mConfig.embedColorError, - mConfig.commandCooldown.replace('{time}', cooldown.timeLeft) - ); - } - - await command.run(client, message, args); - console.log(`Command executed: ${command.name} by ${message.author.tag}`); - } catch (err) { - await handleCommandError(err, command, message, errorHandler); - } + } + + await command.run(client, message, args); + console.log(`Command executed: ${command.name} by ${message.author.tag}`); + } catch (err) { + await handleCommandError(err, command, message, errorHandler); + } } function checkCommandPrerequisites(message, command) { - if (maintenance && !developersId.includes(message.author.id)) { - sendEmbedReply( - message, - mConfig.embedColorError, - 'Bot is currently in maintenance mode. Please try again later.' - ); - return false; - } - - if (command.devOnly && !developersId.includes(message.author.id)) { - sendEmbedReply(message, mConfig.embedColorError, mConfig.commandDevOnly); - return false; - } - - if (command.testMode && message.guild?.id !== testServerId) { - sendEmbedReply(message, mConfig.embedColorError, mConfig.commandTestMode); - return false; - } - - if (command.nsfwMode && !message.channel.nsfw) { - sendEmbedReply(message, mConfig.embedColorError, mConfig.nsfw); - return false; - } - - if ( - command.userPermissions?.length && - !checkPermissions(message, command.userPermissions, 'user') - ) { - sendEmbedReply( - message, - mConfig.embedColorError, - mConfig.userNoPermissions - ); - return false; - } - - if ( - command.botPermissions?.length && - !checkPermissions(message, command.botPermissions, 'bot') - ) { - sendEmbedReply( - message, - mConfig.embedColorError, - mConfig.botNoPermissions - ); - return false; - } - - return true; + if (maintenance && !developersId.includes(message.author.id)) { + sendEmbedReply( + message, + mConfig.embedColorError, + 'Bot is currently in maintenance mode. Please try again later.' + ); + return false; + } + + if (command.devOnly && !developersId.includes(message.author.id)) { + sendEmbedReply(message, mConfig.embedColorError, mConfig.commandDevOnly); + return false; + } + + if (command.testMode && message.guild?.id !== testServerId) { + sendEmbedReply(message, mConfig.embedColorError, mConfig.commandTestMode); + return false; + } + + if (command.nsfwMode && !message.channel.nsfw) { + sendEmbedReply(message, mConfig.embedColorError, mConfig.nsfw); + return false; + } + + if ( + command.userPermissions?.length && + !checkPermissions(message, command.userPermissions, 'user') + ) { + sendEmbedReply(message, mConfig.embedColorError, mConfig.userNoPermissions); + return false; + } + + if ( + command.botPermissions?.length && + !checkPermissions(message, command.botPermissions, 'bot') + ) { + sendEmbedReply(message, mConfig.embedColorError, mConfig.botNoPermissions); + return false; + } + + return true; } async function handleCommandError(err, command, message, errorHandler) { - await errorHandler.handleError(err, { - type: 'prefixcommandError', - commandName: command.name, - userId: message.author.id, - guildId: message.guild?.id, - }); - - sendEmbedReply( - message, - mConfig.embedColorError, - 'An error occurred while executing the command.' - ); + await errorHandler.handleError(err, { + type: 'prefixcommandError', + commandName: command.name, + userId: message.author.id, + guildId: message.guild?.id, + }); + + sendEmbedReply( + message, + mConfig.embedColorError, + 'An error occurred while executing the command.' + ); } const sendEmbedReply = async (message, color, description) => { - try { - const embed = new EmbedBuilder() - .setColor(color) - .setDescription(description) - .setAuthor({ - name: message.author.username, - iconURL: message.author.displayAvatarURL({ dynamic: true }), - }) - .setTimestamp(); - - if (message.guild) { - await message.channel.send({ embeds: [embed] }); - } else { - await message.author.send({ embeds: [embed] }); - } - } catch (err) { - console.error('Error sending embed reply:', err); - } + try { + const embed = new EmbedBuilder() + .setColor(color) + .setDescription(description) + .setAuthor({ + name: message.author.username, + iconURL: message.author.displayAvatarURL({ dynamic: true }), + }) + .setTimestamp(); + + if (message.guild) { + await message.channel.send({ embeds: [embed] }); + } else { + await message.author.send({ embeds: [embed] }); + } + } catch (err) { + console.error('Error sending embed reply:', err); + } }; function checkPermissions(message, requiredPermissions, type) { - if (!message.guild) return true; - - const member = type === 'user' ? message.member : message.guild.members.me; - return requiredPermissions.every( - (permission) => - PermissionsBitField.Flags[permission] !== undefined && - member.permissions.has(PermissionsBitField.Flags[permission]) - ); + if (!message.guild) return true; + + const member = type === 'user' ? message.member : message.guild.members.me; + return requiredPermissions.every( + (permission) => + PermissionsBitField.Flags[permission] !== undefined && + member.permissions.has(PermissionsBitField.Flags[permission]) + ); } function applyCooldown(userId, commandName, cooldownTime) { - if (!cooldowns.has(commandName)) { - cooldowns.set(commandName, new Collection()); - } + if (!cooldowns.has(commandName)) { + cooldowns.set(commandName, new Collection()); + } - const now = Date.now(); - const timestamps = cooldowns.get(commandName); - const cooldownAmount = cooldownTime; + const now = Date.now(); + const timestamps = cooldowns.get(commandName); + const cooldownAmount = cooldownTime; - if (timestamps.has(userId)) { - const expirationTime = timestamps.get(userId) + cooldownAmount; + if (timestamps.has(userId)) { + const expirationTime = timestamps.get(userId) + cooldownAmount; - if (now < expirationTime) { - const timeLeft = (expirationTime - now) / 1000; - return { active: true, timeLeft: timeLeft.toFixed(1) }; - } - } + if (now < expirationTime) { + const timeLeft = (expirationTime - now) / 1000; + return { active: true, timeLeft: timeLeft.toFixed(1) }; + } + } - timestamps.set(userId, now); - setTimeout(() => timestamps.delete(userId), cooldownAmount); + timestamps.set(userId, now); + setTimeout(() => timestamps.delete(userId), cooldownAmount); - return { active: false }; + return { active: false }; } diff --git a/src/events/messageCreate/reactMention.js b/src/events/messageCreate/reactMention.js index 01bef18..ebd9695 100644 --- a/src/events/messageCreate/reactMention.js +++ b/src/events/messageCreate/reactMention.js @@ -1,44 +1,78 @@ +/** + * The emoji to react with when mentioned. + * @type {string} + */ const REACTION_EMOJI = '👋'; + +/** + * The chance to reply with a message when mentioned. + * @type {number} + */ const REPLY_CHANCE = 0.3; // 10% chance to reply with a message +/** + * The list of random replies to use when replying. + * @type {string[]} + */ const RANDOM_REPLIES = [ - 'Hello there!', - 'How can I help you today?', - 'Nice to see you!', - "What's on your mind?", - "I'm all ears!", + 'Hello there!', + 'How can I help you today?', + 'Nice to see you!', + "What's on your mind?", + "I'm all ears!", ]; +/** + * Checks if the message is a direct mention to the bot. + * @param {Message} message - The message to check. + * @param {Client} client - The client instance. + * @returns {boolean} - Whether the message is a direct mention to the bot. + */ const isDirectMention = (message, client) => - message.mentions.has(client.user) && - message.author.id !== client.user.id && - !message.content.match(/@(everyone|here)/gi); + message.mentions.has(client.user) && + message.author.id !== client.user.id && + !message.content.match(/@(everyone|here)/gi); +/** + * Gets a random reply from the list of random replies. + * @returns {string} - A random reply. + */ const getRandomReply = () => - RANDOM_REPLIES[Math.floor(Math.random() * RANDOM_REPLIES.length)]; + RANDOM_REPLIES[Math.floor(Math.random() * RANDOM_REPLIES.length)]; +/** + * Handles the message when the bot is mentioned. + * @param {Message} message - The message to handle. + * @param {Client} client - The client instance. + */ const handleMessage = async (message, client) => { - if ( - message.author.bot || - message.reference || - !isDirectMention(message, client) - ) { - return; - } - - try { - await message.react(REACTION_EMOJI); - - if (Math.random() < REPLY_CHANCE) { - const reply = getRandomReply(); - await message.reply(reply); - } - } catch (error) { - console.error('Error processing message:', error); - // errorHandler.handleError(error, { type: 'messageProcessing', messageId: message.id }); - } + if ( + message.author.bot || + message.reference || + !isDirectMention(message, client) + ) { + return; + } + + try { + await message.react(REACTION_EMOJI); + + if (Math.random() < REPLY_CHANCE) { + const reply = getRandomReply(); + await message.reply(reply); + } + } catch (error) { + console.error('Error processing message:', error); + // errorHandler.handleError(error, { type: 'messageProcessing', messageId: message.id }); + } }; +/** + * The default export function for the reactMention event. + * @param {Client} client - The client instance. + * @param {ErrorHandler} errorHandler - The error handler instance. + * @param {Message} message - The message to handle. + */ export default async (client, errorHandler, message) => { - await handleMessage(message, client); + await handleMessage(message, client); }; diff --git a/src/events/messageDelete/log.js b/src/events/messageDelete/log.js index bd91aa2..274850a 100644 --- a/src/events/messageDelete/log.js +++ b/src/events/messageDelete/log.js @@ -1,153 +1,186 @@ +/** + * Import necessary modules and define constants for emojis used in logging. + */ import { config } from '../../config/config.js'; import { EmbedBuilder, AttachmentBuilder } from 'discord.js'; +/** + * Define a constant object for emojis used in logging messages. + * @type {{LOG: string, ERROR: string, IMAGE: string, REACTION: string, DELETE: string, EDIT: string, CHANNEL: string, CONTENT: string, TIME: string, ID: string, SERVER: string}} + */ const EMOJIS = { - LOG: '📝', - ERROR: '❌', - IMAGE: '🖼️', - REACTION: '👍', - DELETE: '🗑️', - EDIT: '✏️', - CHANNEL: '💬', - CONTENT: '📄', - TIME: '🕒', - ID: '🔢', - SERVER: '🏠', + LOG: '📝', + ERROR: '❌', + IMAGE: '🖼️', + REACTION: '👍', + DELETE: '🗑️', + EDIT: '✏️', + CHANNEL: '💬', + CONTENT: '📄', + TIME: '🕒', + ID: '🔢', + SERVER: '🏠', }; +/** + * Creates an embed for logging messages. + * @param {Object} message - The message object from Discord. + * @param {Object} author - The author of the message. + * @param {string} content - The content of the message. + * @param {string} time - The time the message was sent. + * @param {string} [imageURL] - The URL of an image attachment, if any. + * @returns {EmbedBuilder} The embed for logging the message. + */ const createMessageEmbed = (message, author, content, time, imageURL) => { - const embed = new EmbedBuilder() - .setColor('#0099ff') - .setTitle(`${EMOJIS.LOG} Message Log`) - .setThumbnail(author.displayAvatarURL()) - .addFields( - { - name: `${EMOJIS.REACTION} Author`, - value: `${author.tag} (ID: ${author.id})`, - inline: true, - }, - { - name: `${EMOJIS.CHANNEL} Channel`, - value: `${message.channel.name} (ID: ${message.channel.id})`, - inline: true, - }, - { name: `${EMOJIS.CONTENT} Content`, value: content }, - { name: `${EMOJIS.TIME} Time`, value: time, inline: true }, - { name: `${EMOJIS.ID} Message ID`, value: message.id, inline: true }, - { - name: `${EMOJIS.SERVER} Server`, - value: message.guild.name, - inline: true, - } - ) - .setFooter({ - text: `Message Logger | ${message.client.user.tag}`, - iconURL: message.client.user.displayAvatarURL(), - }) - .setTimestamp(); - - if (imageURL) { - embed.setImage(imageURL); - } - - return embed; + const embed = new EmbedBuilder() + .setColor('#0099ff') + .setTitle(`${EMOJIS.LOG} Message Log`) + .setThumbnail(author.displayAvatarURL()) + .addFields( + { + name: `${EMOJIS.REACTION} Author`, + value: `${author.tag} (ID: ${author.id})`, + inline: true, + }, + { + name: `${EMOJIS.CHANNEL} Channel`, + value: `${message.channel.name} (ID: ${message.channel.id})`, + inline: true, + }, + { name: `${EMOJIS.CONTENT} Content`, value: content }, + { name: `${EMOJIS.TIME} Time`, value: time, inline: true }, + { name: `${EMOJIS.ID} Message ID`, value: message.id, inline: true }, + { + name: `${EMOJIS.SERVER} Server`, + value: message.guild.name, + inline: true, + } + ) + .setFooter({ + text: `Message Logger | ${message.client.user.tag}`, + iconURL: message.client.user.displayAvatarURL(), + }) + .setTimestamp(); + + if (imageURL) { + embed.setImage(imageURL); + } + + return embed; }; +/** + * Creates an embed for error logging. + * @param {Error} error - The error object. + * @param {Object} client - The Discord client. + * @returns {EmbedBuilder} The embed for error logging. + */ const createErrorEmbed = (error, client) => { - return new EmbedBuilder() - .setColor('#FF0000') - .setTitle(`${EMOJIS.ERROR} Error Logging Message`) - .setDescription( - `An error occurred while attempting to log a message: ${error.message}` - ) - .setFooter({ - text: `Message Logger | ${client.user.tag}`, - iconURL: client.user.displayAvatarURL(), - }) - .setTimestamp(); + return new EmbedBuilder() + .setColor('#FF0000') + .setTitle(`${EMOJIS.ERROR} Error Logging Message`) + .setDescription( + `An error occurred while attempting to log a message: ${error.message}` + ) + .setFooter({ + text: `Message Logger | ${client.user.tag}`, + iconURL: client.user.displayAvatarURL(), + }) + .setTimestamp(); }; +/** + * Extracts attachments from a message. + * @param {Object} message - The message object from Discord. + * @returns {Array} An array of attachments with their details. + */ const getAttachments = (message) => { - return message.attachments.map((attachment) => { - return { - name: attachment.name, - url: attachment.url, - contentType: attachment.contentType, - }; - }); + return message.attachments.map((attachment) => { + return { + name: attachment.name, + url: attachment.url, + contentType: attachment.contentType, + }; + }); }; +/** + * Logs a message to a specified channel. + * @param {Object} logChannel - The channel to log the message in. + * @param {EmbedBuilder} embed - The embed to log. + * @param {Array} attachments - An array of attachments to log. + */ const logMessage = async (logChannel, embed, attachments) => { - const files = attachments.map( - (att) => new AttachmentBuilder(att.url, { name: att.name }) - ); - await logChannel.send({ embeds: [embed], files }); + const files = attachments.map( + (att) => new AttachmentBuilder(att.url, { name: att.name }) + ); + await logChannel.send({ embeds: [embed], files }); }; +/** + * The main function for logging messages. + * @param {Object} client - The Discord client. + * @param {Function} errorHandler - A function to handle errors. + * @param {Object} message - The message object from Discord. + */ export default async (client, errorHandler, message) => { - if (message.author.bot || message.guild.id !== config.testServerId) return; - - const logChannel = client.channels.cache.get(config.logChannel); - if (!logChannel) { - console.error(`Log channel with ID ${config.logChannel} not found.`); - return; - } - - try { - const author = message.author; - const content = message.content || '*Message content not available*'; - const time = message.createdAt.toLocaleString(); - const attachments = getAttachments(message); - const imageURL = attachments.find((att) => - att.contentType?.startsWith('image/') - )?.url; - - const embed = createMessageEmbed( - message, - author, - content, - time, - imageURL - ); - - await logMessage(logChannel, embed, attachments); - - // Log message edits - const collector = message.channel.createMessageCollector({ - filter: (m) => m.id === message.id, - time: 600000, // 10 minutes - }); - - collector.on('collect', async (newMessage) => { - if (newMessage.editedAt) { - const editEmbed = new EmbedBuilder() - .setColor('#FFA500') - .setTitle(`${EMOJIS.EDIT} Message Edited`) - .setDescription(`[Jump to Message](${newMessage.url})`) - .addFields( - { name: 'Before', value: content }, - { name: 'After', value: newMessage.content } - ) - .setFooter({ - text: `Edited at ${newMessage.editedAt.toLocaleString()}`, - iconURL: author.displayAvatarURL(), - }); - - await logChannel.send({ embeds: [editEmbed] }); - } - }); - } catch (error) { - console.error('Error logging message:', error); - errorHandler.handleError(error, { - type: 'messageLogging', - messageId: message.id, - }); - - try { - const errorEmbed = createErrorEmbed(error, client); - await logChannel.send({ embeds: [errorEmbed] }); - } catch (innerError) { - console.error('Error logging the error:', innerError); + if (message.author.bot || message.guild.id !== config.testServerId) return; + + const logChannel = client.channels.cache.get(config.logChannel); + if (!logChannel) { + console.error(`Log channel with ID ${config.logChannel} not found.`); + return; + } + + try { + const author = message.author; + const content = message.content || '*Message content not available*'; + const time = message.createdAt.toLocaleString(); + const attachments = getAttachments(message); + const imageURL = attachments.find((att) => + att.contentType?.startsWith('image/') + )?.url; + + const embed = createMessageEmbed(message, author, content, time, imageURL); + + await logMessage(logChannel, embed, attachments); + + // Log message edits + const collector = message.channel.createMessageCollector({ + filter: (m) => m.id === message.id, + time: 600000, // 10 minutes + }); + + collector.on('collect', async (newMessage) => { + if (newMessage.editedAt) { + const editEmbed = new EmbedBuilder() + .setColor('#FFA500') + .setTitle(`${EMOJIS.EDIT} Message Edited`) + .setDescription(`[Jump to Message](${newMessage.url})`) + .addFields( + { name: 'Before', value: content }, + { name: 'After', value: newMessage.content } + ) + .setFooter({ + text: `Edited at ${newMessage.editedAt.toLocaleString()}`, + iconURL: author.displayAvatarURL(), + }); + + await logChannel.send({ embeds: [editEmbed] }); } - } + }); + } catch (error) { + console.error('Error logging message:', error); + errorHandler.handleError(error, { + type: 'messageLogging', + messageId: message.id, + }); + + try { + const errorEmbed = createErrorEmbed(error, client); + await logChannel.send({ embeds: [errorEmbed] }); + } catch (innerError) { + console.error('Error logging the error:', innerError); + } + } }; diff --git a/src/events/messageUpdate/logMessageEdit.js b/src/events/messageUpdate/logMessageEdit.js index c5a31ea..aa5d0a9 100644 --- a/src/events/messageUpdate/logMessageEdit.js +++ b/src/events/messageUpdate/logMessageEdit.js @@ -1,158 +1,194 @@ import { config } from '../../config/config.js'; import { EmbedBuilder } from 'discord.js'; +/** + * Emojis used in the message edit log. + */ const EMOJIS = { - EDIT: '✏️', - AUTHOR: '👤', - CHANNEL: '💬', - TIME: '🕒', - ID: '🔢', - SERVER: '🏠', - OLD: '⬅️', - NEW: '➡️', - EMBED: '🖼️', - ERROR: '❌', - ATTACHMENT: '📎', + EDIT: '✏️', + AUTHOR: '👤', + CHANNEL: '💬', + TIME: '🕒', + ID: '🔢', + SERVER: '🏠', + OLD: '⬅️', + NEW: '➡️', + EMBED: '🖼️', + ERROR: '❌', + ATTACHMENT: '📎', }; +/** + * Creates an embed for the message edit log. + * @param {Message} oldMessage - The old message. + * @param {Message} newMessage - The new message. + * @param {User} author - The author of the new message. + * @param {string} time - The time of the edit. + * @returns {MessageEmbed} - The created embed. + */ const createMessageEmbed = (oldMessage, newMessage, author, time) => { - const oldContent = oldMessage.content || '*Message content not available*'; - const newContent = newMessage.content || '*Message content not available*'; + const oldContent = oldMessage.content || '*Message content not available*'; + const newContent = newMessage.content || '*Message content not available*'; - const embed = new EmbedBuilder() - .setColor('#FFA500') - .setTitle(`${EMOJIS.EDIT} Message Edited`) - .setThumbnail(author.displayAvatarURL()) - .addFields( - { - name: `${EMOJIS.AUTHOR} Author`, - value: `${author.tag} (ID: ${author.id})`, - inline: true, - }, - { - name: `${EMOJIS.CHANNEL} Channel`, - value: `${newMessage.channel.name} (ID: ${newMessage.channel.id})`, - inline: true, - }, - { name: `${EMOJIS.OLD} Old Content`, value: oldContent }, - { name: `${EMOJIS.NEW} New Content`, value: newContent }, - { name: `${EMOJIS.TIME} Time`, value: time, inline: true }, - { - name: `${EMOJIS.ID} Message ID`, - value: newMessage.id, - inline: true, - }, - { - name: `${EMOJIS.SERVER} Server`, - value: newMessage.guild.name, - inline: true, - } - ) - .setFooter({ - text: `Message Logger | ${newMessage.client.user.tag}`, - iconURL: newMessage.client.user.displayAvatarURL(), - }) - .setTimestamp(); + const embed = new EmbedBuilder() + .setColor('#FFA500') + .setTitle(`${EMOJIS.EDIT} Message Edited`) + .setThumbnail(author.displayAvatarURL()) + .addFields( + { + name: `${EMOJIS.AUTHOR} Author`, + value: `${author.tag} (ID: ${author.id})`, + inline: true, + }, + { + name: `${EMOJIS.CHANNEL} Channel`, + value: `${newMessage.channel.name} (ID: ${newMessage.channel.id})`, + inline: true, + }, + { name: `${EMOJIS.OLD} Old Content`, value: oldContent }, + { name: `${EMOJIS.NEW} New Content`, value: newContent }, + { name: `${EMOJIS.TIME} Time`, value: time, inline: true }, + { + name: `${EMOJIS.ID} Message ID`, + value: newMessage.id, + inline: true, + }, + { + name: `${EMOJIS.SERVER} Server`, + value: newMessage.guild.name, + inline: true, + } + ) + .setFooter({ + text: `Message Logger | ${newMessage.client.user.tag}`, + iconURL: newMessage.client.user.displayAvatarURL(), + }) + .setTimestamp(); - return embed; + return embed; }; +/** + * Adds embed fields to the message edit log embed. + * @param {MessageEmbed} embed - The message edit log embed. + * @param {Message} oldMessage - The old message. + * @param {Message} newMessage - The new message. + */ const addEmbedFields = (embed, oldMessage, newMessage) => { - const oldEmbeds = - oldMessage.embeds.length > 0 - ? oldMessage.embeds.map((e) => e.toJSON()) - : null; - const newEmbeds = - newMessage.embeds.length > 0 - ? newMessage.embeds.map((e) => e.toJSON()) - : null; + const oldEmbeds = + oldMessage.embeds.length > 0 + ? oldMessage.embeds.map((e) => e.toJSON()) + : null; + const newEmbeds = + newMessage.embeds.length > 0 + ? newMessage.embeds.map((e) => e.toJSON()) + : null; - if (oldEmbeds) { - embed.addFields({ - name: `${EMOJIS.EMBED} Old Embeds`, - value: JSON.stringify(oldEmbeds, null, 2).slice(0, 1024), - }); - } - if (newEmbeds) { - embed.addFields({ - name: `${EMOJIS.EMBED} New Embeds`, - value: JSON.stringify(newEmbeds, null, 2).slice(0, 1024), - }); - } + if (oldEmbeds) { + embed.addFields({ + name: `${EMOJIS.EMBED} Old Embeds`, + value: JSON.stringify(oldEmbeds, null, 2).slice(0, 1024), + }); + } + if (newEmbeds) { + embed.addFields({ + name: `${EMOJIS.EMBED} New Embeds`, + value: JSON.stringify(newEmbeds, null, 2).slice(0, 1024), + }); + } }; +/** + * Adds attachment fields to the message edit log embed. + * @param {MessageEmbed} embed - The message edit log embed. + * @param {Message} oldMessage - The old message. + * @param {Message} newMessage - The new message. + */ const addAttachmentFields = (embed, oldMessage, newMessage) => { - const oldAttachments = - oldMessage.attachments.size > 0 - ? oldMessage.attachments.map((a) => a.url).join('\n') - : 'None'; - const newAttachments = - newMessage.attachments.size > 0 - ? newMessage.attachments.map((a) => a.url).join('\n') - : 'None'; + const oldAttachments = + oldMessage.attachments.size > 0 + ? oldMessage.attachments.map((a) => a.url).join('\n') + : 'None'; + const newAttachments = + newMessage.attachments.size > 0 + ? newMessage.attachments.map((a) => a.url).join('\n') + : 'None'; - embed.addFields( - { name: `${EMOJIS.ATTACHMENT} Old Attachments`, value: oldAttachments }, - { name: `${EMOJIS.ATTACHMENT} New Attachments`, value: newAttachments } - ); + embed.addFields( + { name: `${EMOJIS.ATTACHMENT} Old Attachments`, value: oldAttachments }, + { name: `${EMOJIS.ATTACHMENT} New Attachments`, value: newAttachments } + ); }; +/** + * Creates an error embed for the message edit log. + * @param {Error} error - The error that occurred. + * @param {Client} client - The Discord client. + * @returns {MessageEmbed} - The created error embed. + */ const createErrorEmbed = (error, client) => { - return new EmbedBuilder() - .setColor('#FF0000') - .setTitle(`${EMOJIS.ERROR} Error Logging Edited Message`) - .setDescription( - `An error occurred while attempting to log an edited message: ${error.message}` - ) - .setFooter({ - text: `Message Logger | ${client.user.tag}`, - iconURL: client.user.displayAvatarURL(), - }) - .setTimestamp(); + return new EmbedBuilder() + .setColor('#FF0000') + .setTitle(`${EMOJIS.ERROR} Error Logging Edited Message`) + .setDescription( + `An error occurred while attempting to log an edited message: ${error.message}` + ) + .setFooter({ + text: `Message Logger | ${client.user.tag}`, + iconURL: client.user.displayAvatarURL(), + }) + .setTimestamp(); }; +/** + * Logs the message edit. + * @param {Client} client - The Discord client. + * @param {ErrorHandler} errorHandler - The error handler. + * @param {Message} oldMessage - The old message. + * @param {Message} newMessage - The new message. + */ export default async (client, errorHandler, oldMessage, newMessage) => { - if (newMessage.author.bot || newMessage.guild.id !== config.testServerId) - return; - if ( - oldMessage.content === newMessage.content && - oldMessage.embeds.length === newMessage.embeds.length && - oldMessage.attachments.size === newMessage.attachments.size - ) - return; + if (newMessage.author.bot || newMessage.guild.id !== config.testServerId) + return; + if ( + oldMessage.content === newMessage.content && + oldMessage.embeds.length === newMessage.embeds.length && + oldMessage.attachments.size === newMessage.attachments.size + ) + return; - const logChannel = client.channels.cache.get(config.logChannel); - if (!logChannel) { - console.error(`Log channel with ID ${config.logChannel} not found.`); - return; - } + const logChannel = client.channels.cache.get(config.logChannel); + if (!logChannel) { + console.error(`Log channel with ID ${config.logChannel} not found.`); + return; + } - try { - const time = newMessage.editedAt - ? newMessage.editedAt.toLocaleString() - : new Date().toLocaleString(); - const embed = createMessageEmbed( - oldMessage, - newMessage, - newMessage.author, - time - ); - addEmbedFields(embed, oldMessage, newMessage); - addAttachmentFields(embed, oldMessage, newMessage); + try { + const time = newMessage.editedAt + ? newMessage.editedAt.toLocaleString() + : new Date().toLocaleString(); + const embed = createMessageEmbed( + oldMessage, + newMessage, + newMessage.author, + time + ); + addEmbedFields(embed, oldMessage, newMessage); + addAttachmentFields(embed, oldMessage, newMessage); - await logChannel.send({ embeds: [embed] }); - } catch (error) { - console.error('Error logging edited message:', error); - errorHandler.handleError(error, { - type: 'messageEditLogging', - messageId: newMessage.id, - }); + await logChannel.send({ embeds: [embed] }); + } catch (error) { + console.error('Error logging edited message:', error); + errorHandler.handleError(error, { + type: 'messageEditLogging', + messageId: newMessage.id, + }); - try { - const errorEmbed = createErrorEmbed(error, client); - await logChannel.send({ embeds: [errorEmbed] }); - } catch (innerError) { - console.error('Error logging the error:', innerError); - } - } + try { + const errorEmbed = createErrorEmbed(error, client); + await logChannel.send({ embeds: [errorEmbed] }); + } catch (innerError) { + console.error('Error logging the error:', innerError); + } + } }; diff --git a/src/events/ready/consoleLog.js b/src/events/ready/consoleLog.js index 05d1a29..1649a43 100644 --- a/src/events/ready/consoleLog.js +++ b/src/events/ready/consoleLog.js @@ -1,3 +1,9 @@ +/** + * Handles the ready event for the Discord client, sets the client's presence, and connects to the MongoDB database. + * + * @param {Discord.Client} client - The Discord client instance. + * @param {errorHandler} errorHandler - The error handler function to handle any errors that occur. + */ import 'colors'; import mongoose from 'mongoose'; import { ActivityType } from 'discord.js'; @@ -5,36 +11,39 @@ import { ActivityType } from 'discord.js'; const mongoURI = process.env.MONGODB_TOKEN; export default async (client, errorHandler) => { - try { - console.log(`${client.user.username} is online.`.blue); + try { + console.log(`${client.user.username} is online.`.blue); - client.user.setPresence({ - activities: [ - { - name: 'Clienterrverse', - type: ActivityType.Streaming, - url: 'https://www.youtube.com/watch?v=KXan_-lBt-8', - }, - ], - status: 'online', - }); + // Sets the client's presence to streaming with a specific activity. + client.user.setPresence({ + activities: [ + { + name: 'Clienterrverse', + type: ActivityType.Streaming, + url: 'https://www.youtube.com/watch?v=KXan_-lBt-8', + }, + ], + status: 'online', + }); - if (!mongoURI) { - console.log( - 'MongoDB URI not found. Skipping MongoDB connection.'.yellow - ); - return; - } + // Checks if the MongoDB URI is set in the environment variables. + if (!mongoURI) { + console.log('MongoDB URI not found. Skipping MongoDB connection.'.yellow); + return; + } - mongoose.set('strictQuery', true); + // Sets the mongoose strictQuery option to true for better error handling. + mongoose.set('strictQuery', true); - await mongoose.connect(mongoURI, { - serverSelectionTimeoutMS: 15000, - }); + // Connects to the MongoDB database with a timeout for server selection. + await mongoose.connect(mongoURI, { + serverSelectionTimeoutMS: 15000, + }); - console.log('Connected to the MongoDB database'.green); - } catch (error) { - errorHandler.handleError(error, { type: 'mongodbConnection' }); - console.error(`Error connecting to MongoDB: ${error.message}`.red); - } + console.log('Connected to the MongoDB database'.green); + } catch (error) { + // Handles any errors that occur during the MongoDB connection process. + errorHandler.handleError(error, { type: 'mongodbConnection' }); + console.error(`Error connecting to MongoDB: ${error.message}`.red); + } }; diff --git a/src/events/ready/registerCommands.js b/src/events/ready/registerCommands.js index 1490302..b33af44 100644 --- a/src/events/ready/registerCommands.js +++ b/src/events/ready/registerCommands.js @@ -7,215 +7,233 @@ import getLocalCommands from '../../utils/getLocalCommands.js'; /** * Registers, updates, or deletes application commands based on local command files. + * This function synchronizes the application commands with the local command files. + * It first fetches the current application commands and the local command files. + * Then, it deletes any application commands that do not have a corresponding local command file. + * Finally, it updates or creates new application commands based on the local command files. + * * @param {Client} client - The Discord client instance. * @param {DiscordBotErrorHandler} errorHandler - The error handler instance. */ export default async (client, errorHandler) => { - try { - const { testServerId } = config; - const [localCommands, applicationCommands] = await Promise.all([ - getLocalCommands(), - getApplicationCommands(client), - ]); + try { + const { testServerId } = config; + const [localCommands, applicationCommands] = await Promise.all([ + getLocalCommands(), + getApplicationCommands(client), + ]); - await deleteUnusedCommands( - applicationCommands, - localCommands, - errorHandler - ); - await updateOrCreateCommands( - applicationCommands, - localCommands, - errorHandler - ); - } catch (err) { - errorHandler.handleError(err, { type: 'commandSync' }); - console.error( - `[${new Date().toISOString()}] Error during command sync: ${err.message}` - .red - ); - } + await deleteUnusedCommands( + applicationCommands, + localCommands, + errorHandler + ); + await updateOrCreateCommands( + applicationCommands, + localCommands, + errorHandler + ); + } catch (err) { + errorHandler.handleError(err, { type: 'commandSync' }); + console.error( + `[${new Date().toISOString()}] Error during command sync: ${err.message}` + .red + ); + } }; /** * Deletes unused commands from the application. + * This function identifies and deletes application commands that do not have a corresponding local command file. + * * @param {Collection} applicationCommands - The current application commands. * @param {Array} localCommands - The local commands. * @param {DiscordBotErrorHandler} errorHandler - The error handler instance. */ async function deleteUnusedCommands( - applicationCommands, - localCommands, - errorHandler + applicationCommands, + localCommands, + errorHandler ) { - const localCommandNames = new Set(localCommands.map((cmd) => cmd.data.name)); - const commandsToDelete = applicationCommands.cache.filter( - (cmd) => - cmd.type === ApplicationCommandType.ChatInput && - !localCommandNames.has(cmd.name) - ); + const localCommandNames = new Set(localCommands.map((cmd) => cmd.data.name)); + const commandsToDelete = applicationCommands.cache.filter( + (cmd) => + cmd.type === ApplicationCommandType.ChatInput && + !localCommandNames.has(cmd.name) + ); - await Promise.all( - commandsToDelete.map((cmd) => - deleteCommand(applicationCommands, errorHandler)(cmd) - ) - ); + await Promise.all( + commandsToDelete.map((cmd) => + deleteCommand(applicationCommands, errorHandler)(cmd) + ) + ); } /** * Updates existing commands or creates new ones based on local command files. + * This function processes each local command file and updates or creates the corresponding application command. + * * @param {Collection} applicationCommands - The current application commands. * @param {Array} localCommands - The local commands. * @param {DiscordBotErrorHandler} errorHandler - The error handler instance. */ async function updateOrCreateCommands( - applicationCommands, - localCommands, - errorHandler + applicationCommands, + localCommands, + errorHandler ) { - await Promise.all( - localCommands.map(processCommand(applicationCommands, errorHandler)) - ); + await Promise.all( + localCommands.map(processCommand(applicationCommands, errorHandler)) + ); } /** * Deletes a command from the application. + * This function attempts to delete a specified command from the application. + * If successful, it logs the deletion. If not, it logs the error. + * * @param {Collection} applicationCommands - The current application commands. * @param {DiscordBotErrorHandler} errorHandler - The error handler instance. * @returns {Function} The delete command function. */ const deleteCommand = (applicationCommands, errorHandler) => async (cmd) => { - try { - await applicationCommands.delete(cmd.id); - console.log( - `[${new Date().toISOString()}] Deleted command: ${cmd.name}`.red - ); - } catch (err) { - errorHandler.handleError(err, { - type: 'deleteCommand', - commandName: cmd.name, - }); - console.error( - `[${new Date().toISOString()}] Failed to delete command ${cmd.name}: ${err.message}` - .red - ); - } + try { + await applicationCommands.delete(cmd.id); + console.log( + `[${new Date().toISOString()}] Deleted command: ${cmd.name}`.red + ); + } catch (err) { + errorHandler.handleError(err, { + type: 'deleteCommand', + commandName: cmd.name, + }); + console.error( + `[${new Date().toISOString()}] Failed to delete command ${cmd.name}: ${err.message}` + .red + ); + } }; /** * Processes a local command, updating or creating it as needed. + * This function checks if a local command exists in the application. + * If it does, it updates the command if necessary. If not, it creates a new command. + * * @param {Collection} applicationCommands - The current application commands. * @param {DiscordBotErrorHandler} errorHandler - The error handler instance. * @returns {Function} The process command function. */ const processCommand = - (applicationCommands, errorHandler) => async (localCommand) => { - const { data } = localCommand; - const commandName = data.name; - const existingCommand = applicationCommands.cache.find( - (cmd) => cmd.name === commandName - ); + (applicationCommands, errorHandler) => async (localCommand) => { + const { data } = localCommand; + const commandName = data.name; + const existingCommand = applicationCommands.cache.find( + (cmd) => cmd.name === commandName + ); - try { - if (existingCommand) { - await handleExistingCommand( - applicationCommands, - existingCommand, - localCommand, - errorHandler - ); - } else if (!localCommand.deleted) { - await createCommand(applicationCommands, data, errorHandler); - } else { - console.log( - `[${new Date().toISOString()}] Skipped command (marked as deleted): ${commandName}` - .grey - ); - } - } catch (err) { - errorHandler.handleError(err, { type: 'processCommand', commandName }); - console.error( - `[${new Date().toISOString()}] Failed to process command ${commandName}: ${err.message}` - .red - ); + try { + if (existingCommand) { + await handleExistingCommand( + applicationCommands, + existingCommand, + localCommand, + errorHandler + ); + } else if (!localCommand.deleted) { + await createCommand(applicationCommands, data, errorHandler); + } else { + console.log( + `[${new Date().toISOString()}] Skipped command (marked as deleted): ${commandName}` + .grey + ); } - }; + } catch (err) { + errorHandler.handleError(err, { type: 'processCommand', commandName }); + console.error( + `[${new Date().toISOString()}] Failed to process command ${commandName}: ${err.message}` + .red + ); + } + }; /** * Handles the updating or deletion of an existing command. + * This function checks if an existing command should be updated or deleted based on the local command file. + * * @param {Collection} applicationCommands - The current application commands. * @param {Object} existingCommand - The existing command to handle. * @param {Object} localCommand - The local command data. * @param {DiscordBotErrorHandler} errorHandler - The error handler instance. */ async function handleExistingCommand( - applicationCommands, - existingCommand, - localCommand, - errorHandler + applicationCommands, + existingCommand, + localCommand, + errorHandler ) { - const { data, deleted } = localCommand; - const commandName = data.name; + const { data, deleted } = localCommand; + const commandName = data.name; - try { - if (deleted) { - await applicationCommands.delete(existingCommand.id); - console.log( - `[${new Date().toISOString()}] Deleted command (marked as deleted): ${commandName}` - .red - ); - } else if (commandComparing(existingCommand, localCommand)) { - await applicationCommands.edit(existingCommand.id, { - name: commandName, - description: data.description, - options: data.options, - contexts: data.contexts, - integration_types: data.integration_types, - }); - console.log( - `[${new Date().toISOString()}] Updated command: ${commandName}` - .yellow - ); - } - } catch (err) { - errorHandler.handleError(err, { - type: 'handleExistingCommand', - commandName, + try { + if (deleted) { + await applicationCommands.delete(existingCommand.id); + console.log( + `[${new Date().toISOString()}] Deleted command (marked as deleted): ${commandName}` + .red + ); + } else if (commandComparing(existingCommand, localCommand)) { + await applicationCommands.edit(existingCommand.id, { + name: commandName, + description: data.description, + options: data.options, + contexts: data.contexts, + integration_types: data.integration_types, }); - console.error( - `[${new Date().toISOString()}] Failed to handle existing command ${commandName}: ${err.message}` - .red + console.log( + `[${new Date().toISOString()}] Updated command: ${commandName}`.yellow ); - } + } + } catch (err) { + errorHandler.handleError(err, { + type: 'handleExistingCommand', + commandName, + }); + console.error( + `[${new Date().toISOString()}] Failed to handle existing command ${commandName}: ${err.message}` + .red + ); + } } /** * Creates a new command in the application. + * This function attempts to create a new application command based on the provided data. + * If successful, it logs the creation. If not, it logs the error. + * * @param {Collection} applicationCommands - The current application commands. * @param {Object} data - The command data. * @param {DiscordBotErrorHandler} errorHandler - The error handler instance. */ async function createCommand(applicationCommands, data, errorHandler) { - try { - await applicationCommands.create({ - name: data.name, - description: data.description, - options: data.options, - contexts: data.contexts, - integration_types: data.integration_types, - }); - console.log( - `[${new Date().toISOString()}] Registered new command: ${data.name}` - .green - ); - } catch (err) { - errorHandler.handleError(err, { - type: 'createCommand', - commandName: data.name, - }); - console.error( - `[${new Date().toISOString()}] Failed to create command ${data.name}: ${err.message}` - .red - ); - } + try { + await applicationCommands.create({ + name: data.name, + description: data.description, + options: data.options, + contexts: data.contexts, + integration_types: data.integration_types, + }); + console.log( + `[${new Date().toISOString()}] Registered new command: ${data.name}`.green + ); + } catch (err) { + errorHandler.handleError(err, { + type: 'createCommand', + commandName: data.name, + }); + console.error( + `[${new Date().toISOString()}] Failed to create command ${data.name}: ${err.message}` + .red + ); + } } diff --git a/src/events/ready/registerContextMenus.js b/src/events/ready/registerContextMenus.js index 202164d..ab0069b 100644 --- a/src/events/ready/registerContextMenus.js +++ b/src/events/ready/registerContextMenus.js @@ -5,74 +5,75 @@ import getLocalContextMenus from '../../utils/getLocalContextMenus.js'; /** * Registers, updates, or deletes application context menus based on local context menu files. + * This function synchronizes the local context menus with the application context menus. + * It fetches both local and application context menus, compares them, and performs the necessary actions: + * - Deletes context menus marked as deleted locally. + * - Creates new context menus not existing in the application but present locally. + * - Skips context menus marked as deleted locally. + * * @param {Client} client - The Discord client instance. * @param {DiscordBotErrorHandler} errorHandler - Error handler instance. */ export default async (client, errorHandler) => { - try { - // Fetch local and application context menus - const localContextMenus = await getLocalContextMenus(); - const applicationContextMenus = await getApplicationContextMenus(client); + try { + // Fetch local and application context menus + const localContextMenus = await getLocalContextMenus(); + const applicationContextMenus = await getApplicationContextMenus(client); - // Create a set of local context menu names for quick lookup - const localContextMenuNames = new Set( - localContextMenus.map((menu) => menu.data.name) - ); + // Create a set of local context menu names for quick lookup + const localContextMenuNames = new Set( + localContextMenus.map((menu) => menu.data.name) + ); - // Process each local context menu - const tasks = localContextMenus.map(async (localContextMenu) => { - const { data } = localContextMenu; - const contextMenuName = data.name; - const contextMenuType = data.type; + // Process each local context menu + const tasks = localContextMenus.map(async (localContextMenu) => { + const { data } = localContextMenu; + const contextMenuName = data.name; + const contextMenuType = data.type; - const existingContextMenu = applicationContextMenus.cache.find( - (menu) => menu.name === contextMenuName - ); + const existingContextMenu = applicationContextMenus.cache.find( + (menu) => menu.name === contextMenuName + ); - try { - if (existingContextMenu) { - if (localContextMenu.deleted) { - // Delete the context menu if marked as deleted locally - await applicationContextMenus.delete(existingContextMenu.id); - console.log(`Deleted context menu: ${contextMenuName}`.red); - } - } else if (!localContextMenu.deleted) { - // Create a new context menu if not deleted locally and not existing in application - await applicationContextMenus.create({ - name: contextMenuName, - type: contextMenuType, - }); - console.log( - `Registered new context menu: ${contextMenuName}`.green - ); - } else { - // Log if context menu is skipped (marked as deleted locally) - console.log( - `Skipped context menu (marked as deleted): ${contextMenuName}` - .grey - ); - } - } catch (err) { - errorHandler.handleError(err, { - type: 'contextMenuSync', - contextMenuName, - action: existingContextMenu ? 'update' : 'create', - }); - console.error( - `Failed to process context menu ${contextMenuName}: ${err.message}` - .red - ); - } - }); + try { + if (existingContextMenu) { + if (localContextMenu.deleted) { + // Delete the context menu if marked as deleted locally + await applicationContextMenus.delete(existingContextMenu.id); + console.log(`Deleted context menu: ${contextMenuName}`.red); + } + } else if (!localContextMenu.deleted) { + // Create a new context menu if not deleted locally and not existing in application + await applicationContextMenus.create({ + name: contextMenuName, + type: contextMenuType, + }); + console.log(`Registered new context menu: ${contextMenuName}`.green); + } else { + // Log if context menu is skipped (marked as deleted locally) + console.log( + `Skipped context menu (marked as deleted): ${contextMenuName}`.grey + ); + } + } catch (err) { + errorHandler.handleError(err, { + type: 'contextMenuSync', + contextMenuName, + action: existingContextMenu ? 'update' : 'create', + }); + console.error( + `Failed to process context menu ${contextMenuName}: ${err.message}` + .red + ); + } + }); - // Execute all tasks concurrently - await Promise.all(tasks); + // Execute all tasks concurrently + await Promise.all(tasks); - console.log('Context menu synchronization complete.'.green); - } catch (err) { - errorHandler.handleError(err, { type: 'contextMenuSync' }); - console.error( - `Error while registering context menus: ${err.message}`.red - ); - } + console.log('Context menu synchronization complete.'.green); + } catch (err) { + errorHandler.handleError(err, { type: 'contextMenuSync' }); + console.error(`Error while registering context menus: ${err.message}`.red); + } }; diff --git a/src/events/validations/ModalCommandValidator.js b/src/events/validations/ModalCommandValidator.js index 17d543f..2eb7c9c 100644 --- a/src/events/validations/ModalCommandValidator.js +++ b/src/events/validations/ModalCommandValidator.js @@ -5,161 +5,206 @@ import { config } from '../../config/config.js'; import mConfig from '../../config/messageConfig.js'; import getModals from '../../utils/getModals.js'; +/** + * A collection to store modal commands. + * @type {Collection} + */ const modals = new Collection(); +/** + * A map to manage cooldowns for modal commands. + * @type {Map} + */ const cooldowns = new Map(); +/** + * Flag to indicate if modals have been loaded. + * @type {boolean} + */ let modalsLoaded = false; +/** + * Sends an embed reply to an interaction. + * + * @param {Interaction} interaction - The interaction to reply to. + * @param {string} color - The color of the embed. + * @param {string} description - The description of the embed. + * @param {boolean} [ephemeral=true] - Whether the reply should be ephemeral. + */ const sendEmbedReply = async ( - interaction, - color, - description, - ephemeral = true + interaction, + color, + description, + ephemeral = true ) => { - try { - const embed = new EmbedBuilder() - .setColor(color) - .setDescription(description); - await interaction.reply({ embeds: [embed], ephemeral }); - } catch (error) { - console.error('Error sending embed reply:'.red, error); - } + try { + const embed = new EmbedBuilder() + .setColor(color) + .setDescription(description); + await interaction.reply({ embeds: [embed], ephemeral }); + } catch (error) { + console.error('Error sending embed reply:'.red, error); + } }; +/** + * Checks if a member has the required permissions. + * + * @param {GuildMember} member - The member to check. + * @param {Array} permissions - The required permissions. + * @returns {boolean} - True if the member has all the required permissions. + */ const checkPermissions = (member, permissions) => - permissions.every((permission) => - member.permissions.has(PermissionsBitField.Flags[permission]) - ); - + permissions.every((permission) => + member.permissions.has(PermissionsBitField.Flags[permission]) + ); + +/** + * Loads modal commands from files. + * + * @param {function} errorHandler - The error handler function. + * @param {number} [retryCount=0] - The current retry count. + */ const loadModals = async (errorHandler, retryCount = 0) => { - try { - const modalFiles = await getModals(); - for (const modal of modalFiles) { - modal.compiledChecks = { - userPermissions: modal.userPermissions - ? (interaction) => - checkPermissions(interaction.member, modal.userPermissions) - : () => true, - botPermissions: modal.botPermissions - ? (interaction) => - checkPermissions( - interaction.guild.members.me, - modal.botPermissions - ) - : () => true, - }; - modals.set(modal.customId, modal); - } - console.log(`Loaded ${modals.size} modal commands`.green); - modalsLoaded = true; - } catch (error) { - errorHandler.handleError(error, { type: 'modalLoad' }); - console.error('Error loading modals:'.red, error); - - if (retryCount < 3) { - console.log( - `Retrying modal load... (Attempt ${retryCount + 1})`.yellow - ); - await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5 seconds before retry - await loadModals(errorHandler, retryCount + 1); - } else { - console.error('Failed to load modals after 3 attempts'.red); - } - } + try { + const modalFiles = await getModals(); + for (const modal of modalFiles) { + modal.compiledChecks = { + userPermissions: modal.userPermissions + ? (interaction) => + checkPermissions(interaction.member, modal.userPermissions) + : () => true, + botPermissions: modal.botPermissions + ? (interaction) => + checkPermissions( + interaction.guild.members.me, + modal.botPermissions + ) + : () => true, + }; + modals.set(modal.customId, modal); + } + console.log(`Loaded ${modals.size} modal commands`.green); + modalsLoaded = true; + } catch (error) { + errorHandler.handleError(error, { type: 'modalLoad' }); + console.error('Error loading modals:'.red, error); + + if (retryCount < 3) { + console.log(`Retrying modal load... (Attempt ${retryCount + 1})`.yellow); + await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5 seconds before retry + await loadModals(errorHandler, retryCount + 1); + } else { + console.error('Failed to load modals after 3 attempts'.red); + } + } }; +/** + * Handles a modal interaction. + * + * @param {Client} client - The Discord client. + * @param {function} errorHandler - The error handler function. + * @param {Interaction} interaction - The interaction to handle. + */ const handleModal = async (client, errorHandler, interaction) => { - const { customId } = interaction; - const modalObject = modals.get(customId); - - if (!modalObject) return; - - const { developersId, testServerId } = config; - - // Check if the modal is developer-only - if (modalObject.devOnly && !developersId.includes(interaction.user.id)) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandDevOnly - ); - } - - // Check if the modal is in test mode - if (modalObject.testMode && interaction.guild.id !== testServerId) { + const { customId } = interaction; + const modalObject = modals.get(customId); + + if (!modalObject) return; + + const { developersId, testServerId } = config; + + // Check if the modal is developer-only + if (modalObject.devOnly && !developersId.includes(interaction.user.id)) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandDevOnly + ); + } + + // Check if the modal is in test mode + if (modalObject.testMode && interaction.guild.id !== testServerId) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandTestMode + ); + } + + // Check user permissions + if (!modalObject.compiledChecks.userPermissions(interaction)) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.userNoPermissions + ); + } + + // Check bot permissions + if (!modalObject.compiledChecks.botPermissions(interaction)) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.botNoPermissions + ); + } + + // Check cooldown + if (modalObject.cooldown) { + const cooldownKey = `${interaction.user.id}-${customId}`; + const cooldownTime = cooldowns.get(cooldownKey); + if (cooldownTime && Date.now() < cooldownTime) { + const remainingTime = Math.ceil((cooldownTime - Date.now()) / 1000); return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandTestMode - ); - } - - // Check user permissions - if (!modalObject.compiledChecks.userPermissions(interaction)) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.userNoPermissions - ); - } - - // Check bot permissions - if (!modalObject.compiledChecks.botPermissions(interaction)) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.botNoPermissions - ); - } - - // Check cooldown - if (modalObject.cooldown) { - const cooldownKey = `${interaction.user.id}-${customId}`; - const cooldownTime = cooldowns.get(cooldownKey); - if (cooldownTime && Date.now() < cooldownTime) { - const remainingTime = Math.ceil((cooldownTime - Date.now()) / 1000); - return sendEmbedReply( - interaction, - mConfig.embedColorError, - `Please wait ${remainingTime} seconds before submitting this modal again.` - ); - } - cooldowns.set(cooldownKey, Date.now() + modalObject.cooldown * 1000); - } - - try { - await modalObject.run(client, interaction); - console.log(`Modal ${customId} executed successfully`.green); - } catch (error) { - console.error(`Error executing modal ${customId}:`.red, error); - - await errorHandler.handleError(error, { - type: 'modalError', - modalId: customId, - userId: interaction.user.id, - guildId: interaction.guild.id, - }); - - sendEmbedReply( - interaction, - mConfig.embedColorError, - 'There was an error while processing this modal submission!' + interaction, + mConfig.embedColorError, + `Please wait ${remainingTime} seconds before submitting this modal again.` ); - } + } + cooldowns.set(cooldownKey, Date.now() + modalObject.cooldown * 1000); + } + + try { + await modalObject.run(client, interaction); + console.log(`Modal ${customId} executed successfully`.green); + } catch (error) { + console.error(`Error executing modal ${customId}:`.red, error); + + await errorHandler.handleError(error, { + type: 'modalError', + modalId: customId, + userId: interaction.user.id, + guildId: interaction.guild.id, + }); + + sendEmbedReply( + interaction, + mConfig.embedColorError, + 'There was an error while processing this modal submission!' + ); + } }; +/** + * The main function to handle modal interactions. + * + * @param {Client} client - The Discord client. + * @param {function} errorHandler - The error handler function. + * @param {Interaction} interaction - The interaction to handle. + */ export default async (client, errorHandler, interaction) => { - if (!interaction.isModalSubmit()) return; + if (!interaction.isModalSubmit()) return; - if (!modalsLoaded) { - await loadModals(errorHandler); - } + if (!modalsLoaded) { + await loadModals(errorHandler); + } - console.log( - `Modal ${interaction.customId} submitted by ${interaction.user.tag} in ${interaction.guild.name}` - .yellow - ); + console.log( + `Modal ${interaction.customId} submitted by ${interaction.user.tag} in ${interaction.guild.name}` + .yellow + ); - await handleModal(client, errorHandler, interaction); + await handleModal(client, errorHandler, interaction); }; // TODO List // 1. **Implement Modal Cache Management**: Consider adding an LRU cache or similar mechanism to manage modal commands, similar to your button cache, to improve performance and memory usage. diff --git a/src/events/validations/buttonValidator.js b/src/events/validations/buttonValidator.js index fd37da5..9c8cd92 100644 --- a/src/events/validations/buttonValidator.js +++ b/src/events/validations/buttonValidator.js @@ -5,27 +5,44 @@ import { config } from '../../config/config.js'; import mConfig from '../../config/messageConfig.js'; import getButtons from '../../utils/getButtons.js'; +/** + * Class for LRUCache + */ class LRUCache { - constructor(capacity) { - this.capacity = capacity; - this.cache = new Map(); - } - - get(key) { - if (!this.cache.has(key)) return undefined; - const item = this.cache.get(key); - this.cache.delete(key); - this.cache.set(key, item); - return item; - } - - set(key, value) { - if (this.cache.size >= this.capacity) { - const oldestKey = this.cache.keys().next().value; - this.cache.delete(oldestKey); - } - this.cache.set(key, value); - } + /** + * Constructor for LRUCache + * @param {number} capacity - The capacity of the LRUCache + */ + constructor(capacity) { + this.capacity = capacity; + this.cache = new Map(); + } + + /** + * Get method for LRUCache + * @param {string} key - The key to get from the cache + * @returns {any} - The value associated with the key + */ + get(key) { + if (!this.cache.has(key)) return undefined; + const item = this.cache.get(key); + this.cache.delete(key); + this.cache.set(key, item); + return item; + } + + /** + * Set method for LRUCache + * @param {string} key - The key to set in the cache + * @param {any} value - The value to associate with the key + */ + set(key, value) { + if (this.cache.size >= this.capacity) { + const oldestKey = this.cache.keys().next().value; + this.cache.delete(oldestKey); + } + this.cache.set(key, value); + } } const buttons = new Map(); @@ -33,165 +50,178 @@ const cooldowns = new Map(); const buttonCache = new LRUCache(100); // Adjust capacity as needed let buttonsLoaded = false; +/** + * Function to send an embed reply + * @param {object} interaction - The interaction object + * @param {string} color - The color of the embed + * @param {string} description - The description of the embed + * @param {boolean} ephemeral - Whether the reply should be ephemeral + */ const sendEmbedReply = async ( - interaction, - color, - description, - ephemeral = true + interaction, + color, + description, + ephemeral = true ) => { - try { - const embed = new EmbedBuilder() - .setColor(color) - .setDescription(description) - .setAuthor({ - name: interaction.user.username, - iconURL: interaction.user.displayAvatarURL({ dynamic: true }), - }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed], ephemeral }); - } catch (err) {} + try { + const embed = new EmbedBuilder() + .setColor(color) + .setDescription(description) + .setAuthor({ + name: interaction.user.username, + iconURL: interaction.user.displayAvatarURL({ dynamic: true }), + }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed], ephemeral }); + } catch (err) {} }; +/** + * Function to check permissions + * @param {object} member - The member object + * @param {string[]} permissions - The permissions to check + * @returns {boolean} - Whether the member has all the permissions + */ const checkPermissions = (member, permissions) => - permissions.every((permission) => - member.permissions.has(PermissionsBitField.Flags[permission]) - ); - + permissions.every((permission) => + member.permissions.has(PermissionsBitField.Flags[permission]) + ); + +/** + * Function to load buttons + * @param {object} errorHandler - The error handler object + * @param {number} retryCount - The number of times to retry loading buttons + */ const loadButtons = async (errorHandler, retryCount = 0) => { - try { - const buttonFiles = await getButtons(); - for (const button of buttonFiles) { - button.compiledChecks = { - userPermissions: button.userPermissions - ? (interaction) => - checkPermissions(interaction.member, button.userPermissions) - : () => true, - botPermissions: button.botPermissions - ? (interaction) => - checkPermissions( - interaction.guild.members.me, - button.botPermissions - ) - : () => true, - }; - buttons.set(button.customId, button); - } - console.log(`Loaded ${buttons.size} buttons`.green); - buttonsLoaded = true; - } catch (error) { - errorHandler.handleError(error, { type: 'buttonLoad' }); - console.error('Error loading buttons:'.red, error); - - if (retryCount < 3) { - console.log( - `Retrying button load... (Attempt ${retryCount + 1})`.yellow - ); - await new Promise((resolve) => setTimeout(resolve, 5000)); - await loadButtons(errorHandler, retryCount + 1); - } else { - console.error('Failed to load buttons after 3 attempts'.red); - } - } + try { + const buttonFiles = await getButtons(); + for (const button of buttonFiles) { + button.compiledChecks = { + userPermissions: button.userPermissions + ? (interaction) => + checkPermissions(interaction.member, button.userPermissions) + : () => true, + botPermissions: button.botPermissions + ? (interaction) => + checkPermissions( + interaction.guild.members.me, + button.botPermissions + ) + : () => true, + }; + buttons.set(button.customId, button); + } + console.log(`Loaded ${buttons.size} buttons`.green); + buttonsLoaded = true; + } catch (error) { + errorHandler.handleError(error, { type: 'buttonLoad' }); + console.error('Error loading buttons:'.red, error); + + if (retryCount < 3) { + console.log(`Retrying button load... (Attempt ${retryCount + 1})`.yellow); + await new Promise((resolve) => setTimeout(resolve, 5000)); + await loadButtons(errorHandler, retryCount + 1); + } else { + console.error('Failed to load buttons after 3 attempts'.red); + } + } }; +/** + * Function to handle a button interaction + * @param {object} client - The client object + * @param {object} errorHandler - The error handler object + * @param {object} interaction - The interaction object + */ const handleButton = async (client, errorHandler, interaction) => { - const { customId } = interaction; - let button = buttonCache.get(customId); - if (!button) { - button = buttons.get(customId); - if (button) buttonCache.set(customId, button); - } - - if (!button) return; - const { developersId, testServerId } = config; - - if (button.devOnly && !developersId.includes(interaction.user.id)) { - return sendEmbedReply(interaction, 'error', mConfig.commandDevOnly, true); - } - - if (button.testMode && interaction.guild.id !== testServerId) { + const { customId } = interaction; + let button = buttonCache.get(customId); + if (!button) { + button = buttons.get(customId); + if (button) buttonCache.set(customId, button); + } + + if (!button) return; + const { developersId, testServerId } = config; + + if (button.devOnly && !developersId.includes(interaction.user.id)) { + return sendEmbedReply(interaction, 'error', mConfig.commandDevOnly, true); + } + + if (button.testMode && interaction.guild.id !== testServerId) { + return sendEmbedReply(interaction, 'error', mConfig.commandTestMode, true); + } + + if (!button.compiledChecks.userPermissions(interaction)) { + return sendEmbedReply( + interaction, + 'error', + mConfig.userNoPermissions, + true + ); + } + + if (!button.compiledChecks.botPermissions(interaction)) { + return sendEmbedReply(interaction, 'error', mConfig.botNoPermissions, true); + } + + if ( + interaction.message.interaction && + interaction.message.interaction.user.id !== interaction.user.id + ) { + return sendEmbedReply(interaction, 'error', mConfig.cannotUseButton, true); + } + + if (button.cooldown) { + const cooldownKey = `${interaction.user.id}-${customId}`; + const cooldownTime = cooldowns.get(cooldownKey); + if (cooldownTime && Date.now() < cooldownTime) { + const remainingTime = Math.ceil((cooldownTime - Date.now()) / 1000); return sendEmbedReply( - interaction, - 'error', - mConfig.commandTestMode, - true - ); - } - - if (!button.compiledChecks.userPermissions(interaction)) { - return sendEmbedReply( - interaction, - 'error', - mConfig.userNoPermissions, - true - ); - } - - if (!button.compiledChecks.botPermissions(interaction)) { - return sendEmbedReply( - interaction, - 'error', - mConfig.botNoPermissions, - true - ); - } - - if ( - interaction.message.interaction && - interaction.message.interaction.user.id !== interaction.user.id - ) { - return sendEmbedReply( - interaction, - 'error', - mConfig.cannotUseButton, - true - ); - } - - if (button.cooldown) { - const cooldownKey = `${interaction.user.id}-${customId}`; - const cooldownTime = cooldowns.get(cooldownKey); - if (cooldownTime && Date.now() < cooldownTime) { - const remainingTime = Math.ceil((cooldownTime - Date.now()) / 1000); - return sendEmbedReply( - interaction, - 'error', - `Please wait ${remainingTime} seconds before using this button again.`, - true - ); - } - cooldowns.set(cooldownKey, Date.now() + button.cooldown * 1000); - } - - try { - console.log( - `Executing button ${customId} for user ${interaction.user.tag}`.cyan - ); - await button.run(client, interaction); - } catch (error) { - console.error(`Error executing button ${customId}:`.red, error); - await errorHandler.handleError(error, { - type: 'buttonError', - buttonId: customId, - userId: interaction.user.id, - guildId: interaction.guild.id, - }); - sendEmbedReply( - interaction, - 'error', - 'There was an error while executing this button!', - true + interaction, + 'error', + `Please wait ${remainingTime} seconds before using this button again.`, + true ); - } + } + cooldowns.set(cooldownKey, Date.now() + button.cooldown * 1000); + } + + try { + console.log( + `Executing button ${customId} for user ${interaction.user.tag}`.cyan + ); + await button.run(client, interaction); + } catch (error) { + console.error(`Error executing button ${customId}:`.red, error); + await errorHandler.handleError(error, { + type: 'buttonError', + buttonId: customId, + userId: interaction.user.id, + guildId: interaction.guild.id, + }); + sendEmbedReply( + interaction, + 'error', + 'There was an error while executing this button!', + true + ); + } }; +/** + * Default export function for button validation + * @param {object} client - The client object + * @param {object} errorHandler - The error handler object + * @param {object} interaction - The interaction object + */ export default async (client, errorHandler, interaction) => { - if (!interaction.isButton()) return; + if (!interaction.isButton()) return; - if (!buttonsLoaded) { - await loadButtons(errorHandler); - } + if (!buttonsLoaded) { + await loadButtons(errorHandler); + } - await handleButton(client, errorHandler, interaction); + await handleButton(client, errorHandler, interaction); }; diff --git a/src/events/validations/chatInputCommandValidator.js b/src/events/validations/chatInputCommandValidator.js index 6e313fa..842a976 100644 --- a/src/events/validations/chatInputCommandValidator.js +++ b/src/events/validations/chatInputCommandValidator.js @@ -6,235 +6,300 @@ import { config } from '../../config/config.js'; import mConfig from '../../config/messageConfig.js'; import getLocalCommands from '../../utils/getLocalCommands.js'; +/** + * A simple LRU Cache implementation. + * + * @class LRUCache + * @param {number} capacity - The maximum number of items the cache can hold. + */ class LRUCache { - constructor(capacity) { - this.capacity = capacity; - this.cache = new Map(); - } - - get(key) { - if (!this.cache.has(key)) return undefined; - const value = this.cache.get(key); - this.cache.delete(key); - this.cache.set(key, value); - return value; - } - - set(key, value) { - if (this.cache.has(key)) this.cache.delete(key); - else if (this.cache.size >= this.capacity) { - const firstKey = this.cache.keys().next().value; - this.cache.delete(firstKey); - } - this.cache.set(key, value); - } + constructor(capacity) { + this.capacity = capacity; + this.cache = new Map(); + } + + /** + * Retrieves a value from the cache by its key. + * + * @param {string} key - The key of the value to retrieve. + * @returns {(any|undefined)} The value associated with the key or undefined if not found. + */ + get(key) { + if (!this.cache.has(key)) return undefined; + const value = this.cache.get(key); + this.cache.delete(key); + this.cache.set(key, value); + return value; + } + + /** + * Sets a value in the cache by its key. + * + * @param {string} key - The key of the value to set. + * @param {any} value - The value to set. + */ + set(key, value) { + if (this.cache.has(key)) this.cache.delete(key); + else if (this.cache.size >= this.capacity) { + const firstKey = this.cache.keys().next().value; + this.cache.delete(firstKey); + } + this.cache.set(key, value); + } } const cache = new LRUCache(100); // Adjust capacity as needed const cooldowns = new Collection(); +const permissionLevels = new Collection(); const commandMap = new Map(); +/** + * Sends an embed reply to an interaction. + * + * @param {Interaction} interaction - The interaction to reply to. + * @param {string} color - The color of the embed. + * @param {string} description - The description of the embed. + * @param {boolean} [ephemeral=true] - Whether the reply should be ephemeral. + */ const sendEmbedReply = async ( - interaction, - color, - description, - ephemeral = true + interaction, + color, + description, + ephemeral = true ) => { - try { - const embed = new EmbedBuilder() - .setColor(color) - .setDescription(description) - .setAuthor({ - name: interaction.user.username, - iconURL: interaction.user.displayAvatarURL({ dynamic: true }), - }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed], ephemeral }); - } catch (err) {} + try { + const embed = new EmbedBuilder() + .setColor(color) + .setDescription(description) + .setAuthor({ + name: interaction.user.username, + iconURL: interaction.user.displayAvatarURL({ dynamic: true }), + }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed], ephemeral }); + } catch (err) {} }; +/** + * Retrieves data from the cache or fetches it if not cached. + * + * @param {string} key - The key of the data to retrieve. + * @param {Function} fetchFunction - The function to fetch the data if not cached. + * @returns {Promise} A promise that resolves to the data. + */ const getCachedData = async (key, fetchFunction) => { - const cachedItem = cache.get(key); - if (cachedItem) return cachedItem; + const cachedItem = cache.get(key); + if (cachedItem) return cachedItem; - const data = await fetchFunction(); - cache.set(key, data); - return data; + const data = await fetchFunction(); + cache.set(key, data); + return data; }; +/** + * Retrieves cached local commands. + * + * @returns {Promise} A promise that resolves to the cached local commands. + */ const getCachedLocalCommands = () => - getCachedData('localCommands', getLocalCommands); + getCachedData('localCommands', getLocalCommands); +/** + * Initializes the command map with local commands. + */ const initializeCommandMap = async () => { - const localCommands = await getCachedLocalCommands(); - localCommands.forEach((cmd) => { - commandMap.set(cmd.data.name, cmd); - if (cmd.aliases) { - cmd.aliases.forEach((alias) => commandMap.set(alias, cmd)); - } - }); + const localCommands = await getCachedLocalCommands(); + localCommands.forEach((cmd) => { + commandMap.set(cmd.data.name, cmd); + if (cmd.aliases) { + cmd.aliases.forEach((alias) => commandMap.set(alias, cmd)); + } + }); }; +/** + * Applies a cooldown to a command for a user. + * + * @param {Interaction} interaction - The interaction to apply the cooldown for. + * @param {string} commandName - The name of the command. + * @param {number} cooldownAmount - The amount of time in milliseconds for the cooldown. + * @returns {{active: boolean, timeLeft: string}} An object indicating if the cooldown is active and the time left. + */ const applyCooldown = (interaction, commandName, cooldownAmount) => { - if (isNaN(cooldownAmount) || cooldownAmount <= 0) { - throw new Error('Invalid cooldown amount'); - } - - const userCooldowns = cooldowns.get(commandName) || new Collection(); - const now = Date.now(); - const userId = `${interaction.user.id}-${interaction.guild ? interaction.guild.id : 'DM'}`; - - if (userCooldowns.has(userId)) { - const expirationTime = userCooldowns.get(userId) + cooldownAmount; - if (now < expirationTime) { - return { - active: true, - timeLeft: ((expirationTime - now) / 1000).toFixed(1), - }; - } - } - - userCooldowns.set(userId, now); - setTimeout(() => userCooldowns.delete(userId), cooldownAmount); - cooldowns.set(commandName, userCooldowns); - return { active: false }; + if (isNaN(cooldownAmount) || cooldownAmount <= 0) { + throw new Error('Invalid cooldown amount'); + } + + const userCooldowns = cooldowns.get(commandName) || new Collection(); + const now = Date.now(); + const userId = `${interaction.user.id}-${interaction.guild ? interaction.guild.id : 'DM'}`; + + if (userCooldowns.has(userId)) { + const expirationTime = userCooldowns.get(userId) + cooldownAmount; + if (now < expirationTime) { + return { + active: true, + timeLeft: ((expirationTime - now) / 1000).toFixed(1), + }; + } + } + + userCooldowns.set(userId, now); + setTimeout(() => userCooldowns.delete(userId), cooldownAmount); + cooldowns.set(commandName, userCooldowns); + return { active: false }; }; +/** + * Checks if a member has the required permissions. + * + * @param {Interaction} interaction - The interaction to check permissions for. + * @param {Array} permissions - The required permissions. + * @param {'user'|'bot'} type - The type of member to check permissions for. + * @returns {boolean} True if the member has all the required permissions, false otherwise. + */ const checkPermissions = (interaction, permissions, type) => { - const member = - type === 'user' ? interaction.member : interaction.guild.members.me; - return permissions.every((permission) => member.permissions.has(permission)); + const member = + type === 'user' ? interaction.member : interaction.guild.members.me; + return permissions.every((permission) => member.permissions.has(permission)); }; +/** + * The main function to validate and execute chat input commands. + * + * @param {Client} client - The Discord client. + * @param {Function} errorHandler - The error handler function. + * @param {Interaction} interaction - The interaction to validate and execute. + */ export default async (client, errorHandler, interaction) => { - if (!interaction.isChatInputCommand() && !interaction.isAutocomplete()) - return; - - if (commandMap.size === 0) { - await initializeCommandMap(); // Initialize command map if it's empty - } - - const { developersId, testServerId, maintenance } = config; - - try { - const commandObject = commandMap.get(interaction.commandName); - - if (!commandObject) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - 'Command not found.' - ); + if (!interaction.isChatInputCommand() && !interaction.isAutocomplete()) + return; + + if (commandMap.size === 0) { + await initializeCommandMap(); // Initialize command map if it's empty + } + + const { developersId, testServerId, maintenance } = config; + + try { + const commandObject = commandMap.get(interaction.commandName); + if (!commandObject) { + for (const [name, cmd] of commandMap.entries()) { + if (cmd.aliases && cmd.aliases.includes(interaction.commandName)) { + commandObject = cmd; + break; + } } - - if (interaction.isAutocomplete()) { - return await commandObject.autocomplete(client, interaction); - } - - if (maintenance && !developersId.includes(interaction.user.id)) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - 'Bot is currently in maintenance mode. Please try again later.' - ); - } - - const cooldown = applyCooldown( - interaction, - commandObject.data.name, - (commandObject.cooldown || 3) * 1000 + } + if (!commandObject) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + 'Command not found.' ); - if (cooldown.active) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandCooldown.replace('{time}', cooldown.timeLeft) - ); - } + } - if ( - commandObject.devOnly && - !developersId.includes(interaction.user.id) - ) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandDevOnly - ); - } + if (interaction.isAutocomplete()) { + return await commandObject.autocomplete(client, interaction); + } - if (commandObject.testMode && interaction.guild.id !== testServerId) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandTestMode - ); - } - - if (commandObject.nsfwMode && !interaction.channel.nsfw) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.nsfw - ); - } - - if ( - commandObject.userPermissions?.length && - !checkPermissions(interaction, commandObject.userPermissions, 'user') - ) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.userNoPermissions - ); - } - - if ( - commandObject.botPermissions?.length && - !checkPermissions(interaction, commandObject.botPermissions, 'bot') - ) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.botNoPermissions - ); - } + if (maintenance && !developersId.includes(interaction.user.id)) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + 'Bot is currently in maintenance mode. Please try again later.' + ); + } + + const cooldown = applyCooldown( + interaction, + commandObject.data.name, + (commandObject.cooldown || 3) * 1000 + ); + if (cooldown.active) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandCooldown.replace('{time}', cooldown.timeLeft) + ); + } - try { - await commandObject.run(client, interaction); - } catch (err) { - await errorHandler.handleError(err, { - type: 'commandError', - commandName: interaction.commandName, - userId: interaction.user.id, - guildId: interaction.guild?.id, - }); - - sendEmbedReply( - interaction, - mConfig.embedColorError, - 'An error occurred while executing the command.' - ); - } + if (commandObject.devOnly && !developersId.includes(interaction.user.id)) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandDevOnly + ); + } - console.log( - `Command executed: ${interaction.commandName} by ${interaction.user.tag}` - .green + if (commandObject.testMode && interaction.guild.id !== testServerId) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandTestMode ); - } catch (err) { + } + + if (commandObject.nsfwMode && !interaction.channel.nsfw) { + return sendEmbedReply(interaction, mConfig.embedColorError, mConfig.nsfw); + } + + if ( + commandObject.userPermissions?.length && + !checkPermissions(interaction, commandObject.userPermissions, 'user') + ) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.userNoPermissions + ); + } + + if ( + commandObject.botPermissions?.length && + !checkPermissions(interaction, commandObject.botPermissions, 'bot') + ) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.botNoPermissions + ); + } + + try { + await commandObject.run(client, interaction); + } catch (err) { await errorHandler.handleError(err, { - type: 'commandError', - commandName: interaction.commandName, - userId: interaction.user.id, - guildId: interaction.guild?.id, + type: 'commandError', + commandName: interaction.commandName, + userId: interaction.user.id, + guildId: interaction.guild?.id, }); sendEmbedReply( - interaction, - mConfig.embedColorError, - 'An error occurred while processing the command.' + interaction, + mConfig.embedColorError, + 'An error occurred while executing the command.' ); - } + } + + console.log( + `Command executed: ${interaction.commandName} by ${interaction.user.tag}` + .green + ); + } catch (err) { + await errorHandler.handleError(err, { + type: 'commandError', + commandName: interaction.commandName, + userId: interaction.user.id, + guildId: interaction.guild?.id, + }); + + sendEmbedReply( + interaction, + mConfig.embedColorError, + 'An error occurred while processing the command.' + ); + } }; diff --git a/src/events/validations/contextMenuCommandValidator.js b/src/events/validations/contextMenuCommandValidator.js index bcab56c..a275cf5 100644 --- a/src/events/validations/contextMenuCommandValidator.js +++ b/src/events/validations/contextMenuCommandValidator.js @@ -6,150 +6,201 @@ import { config } from '../../config/config.js'; import mConfig from '../../config/messageConfig.js'; import getLocalContextMenus from '../../utils/getLocalContextMenus.js'; +/** + * A simple Least Recently Used (LRU) cache implementation. + * + * @class LRUCache + * @param {number} capacity - The maximum number of items the cache can hold. + */ class LRUCache { - constructor(capacity) { - this.capacity = capacity; - this.cache = new Map(); - } - - get(key) { - if (!this.cache.has(key)) return undefined; - const value = this.cache.get(key); - this.cache.delete(key); - this.cache.set(key, value); - return value; - } - - set(key, value) { - if (this.cache.has(key)) this.cache.delete(key); - else if (this.cache.size >= this.capacity) { - const firstKey = this.cache.keys().next().value; - this.cache.delete(firstKey); - } - this.cache.set(key, value); - } + constructor(capacity) { + this.capacity = capacity; + this.cache = new Map(); + } + + /** + * Retrieves a value from the cache by its key. If the key is found, it is moved to the front of the cache. + * + * @param {string} key - The key to retrieve the value for. + * @returns {(undefined|*)} The value associated with the key or undefined if not found. + */ + get(key) { + if (!this.cache.has(key)) return undefined; + const value = this.cache.get(key); + this.cache.delete(key); + this.cache.set(key, value); + return value; + } + + /** + * Sets a value in the cache. If the cache is full, the least recently used item is removed. + * + * @param {string} key - The key to associate with the value. + * @param {*} value - The value to store in the cache. + */ + set(key, value) { + if (this.cache.has(key)) this.cache.delete(key); + else if (this.cache.size >= this.capacity) { + const firstKey = this.cache.keys().next().value; + this.cache.delete(firstKey); + } + this.cache.set(key, value); + } } const contextMenus = new LRUCache(100); // Adjust capacity as needed +/** + * Sends an embed reply to an interaction. + * + * @param {Interaction} interaction - The interaction to reply to. + * @param {string} color - The color of the embed. + * @param {string} description - The description of the embed. + * @param {boolean} [ephemeral=true] - Whether the reply should be ephemeral. + */ const sendEmbedReply = async ( - interaction, - color, - description, - ephemeral = true + interaction, + color, + description, + ephemeral = true ) => { - try { - const embed = new EmbedBuilder() - .setColor(color) - .setDescription(description) - .setAuthor({ - name: interaction.user.username, - iconURL: interaction.user.displayAvatarURL({ dynamic: true }), - }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed], ephemeral }); - } catch (err) {} + try { + const embed = new EmbedBuilder() + .setColor(color) + .setDescription(description) + .setAuthor({ + name: interaction.user.username, + iconURL: interaction.user.displayAvatarURL({ dynamic: true }), + }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed], ephemeral }); + } catch (err) {} }; +/** + * Checks if a member has a set of permissions. + * + * @param {Interaction} interaction - The interaction to check permissions for. + * @param {Array} permissions - The permissions to check. + * @param {'user'|'bot'} type - The type of member to check permissions for. + * @returns {boolean} True if the member has all the specified permissions, false otherwise. + */ const checkPermissions = (interaction, permissions, type) => { - const member = - type === 'user' ? interaction.member : interaction.guild.members.me; - return permissions.every((permission) => member.permissions.has(permission)); + const member = + type === 'user' ? interaction.member : interaction.guild.members.me; + return permissions.every((permission) => member.permissions.has(permission)); }; +/** + * Loads context menus from local files. + */ const loadContextMenus = async () => { - try { - const menuFiles = await getLocalContextMenus(); - for (const menu of menuFiles) { - contextMenus.set(menu.data.name, menu); - } - console.log( - `Loaded ${contextMenus.cache.size} context menu commands`.green - ); - } catch (error) { - console.error('Error loading context menus:'.red, error); - } + try { + const menuFiles = await getLocalContextMenus(); + for (const menu of menuFiles) { + contextMenus.set(menu.data.name, menu); + } + console.log( + `Loaded ${contextMenus.cache.size} context menu commands`.green + ); + } catch (error) { + console.error('Error loading context menus:'.red, error); + } }; +/** + * Handles a context menu interaction. + * + * @param {Client} client - The Discord client. + * @param {Function} errorHandler - A function to handle errors. + * @param {Interaction} interaction - The interaction to handle. + */ const handleContextMenu = async (client, errorHandler, interaction) => { - const { commandName } = interaction; - const menuObject = contextMenus.get(commandName); - - if (!menuObject) return; - - const { developersId, testServerId } = config; - - if (menuObject.devOnly && !developersId.includes(interaction.user.id)) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandDevOnly - ); - } - - if (menuObject.testMode && interaction.guild.id !== testServerId) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandTestMode - ); - } - - if ( - menuObject.userPermissions?.length && - !checkPermissions(interaction, menuObject.userPermissions, 'user') - ) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.userNoPermissions - ); - } - - if ( - menuObject.botPermissions?.length && - !checkPermissions(interaction, menuObject.botPermissions, 'bot') - ) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.botNoPermissions - ); - } - - try { - await menuObject.run(client, interaction); - console.log( - `Context menu command executed: ${commandName} by ${interaction.user.tag}` - .green - ); - } catch (error) { - console.error( - `Error executing context menu command ${commandName}:`.red, - error - ); - - await errorHandler.handleError(error, { - type: 'contextMenuError', - commandName: commandName, - userId: interaction.user.id, - guildId: interaction.guild?.id, - }); - - sendEmbedReply( - interaction, - mConfig.embedColorError, - 'There was an error while executing this context menu command!' - ); - } + const { commandName } = interaction; + const menuObject = contextMenus.get(commandName); + + if (!menuObject) return; + + const { developersId, testServerId } = config; + + if (menuObject.devOnly && !developersId.includes(interaction.user.id)) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandDevOnly + ); + } + + if (menuObject.testMode && interaction.guild.id !== testServerId) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandTestMode + ); + } + + if ( + menuObject.userPermissions?.length && + !checkPermissions(interaction, menuObject.userPermissions, 'user') + ) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.userNoPermissions + ); + } + + if ( + menuObject.botPermissions?.length && + !checkPermissions(interaction, menuObject.botPermissions, 'bot') + ) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.botNoPermissions + ); + } + + try { + await menuObject.run(client, interaction); + console.log( + `Context menu command executed: ${commandName} by ${interaction.user.tag}` + .green + ); + } catch (error) { + console.error( + `Error executing context menu command ${commandName}:`.red, + error + ); + + await errorHandler.handleError(error, { + type: 'contextMenuError', + commandName: commandName, + userId: interaction.user.id, + guildId: interaction.guild?.id, + }); + + sendEmbedReply( + interaction, + mConfig.embedColorError, + 'There was an error while executing this context menu command!' + ); + } }; // Call this function during bot initialization await loadContextMenus(); +/** + * The main function to handle context menu commands. + * + * @param {Client} client - The Discord client. + * @param {Function} errorHandler - A function to handle errors. + * @param {Interaction} interaction - The interaction to handle. + */ export default async (client, errorHandler, interaction) => { - if (!interaction.isContextMenuCommand()) return; + if (!interaction.isContextMenuCommand()) return; - await handleContextMenu(client, errorHandler, interaction); + await handleContextMenu(client, errorHandler, interaction); }; diff --git a/src/events/validations/seclectMenuValidator.js b/src/events/validations/seclectMenuValidator.js index f677835..c3472de 100644 --- a/src/events/validations/seclectMenuValidator.js +++ b/src/events/validations/seclectMenuValidator.js @@ -6,157 +6,195 @@ import { config } from '../../config/config.js'; import mConfig from '../../config/messageConfig.js'; import getSelects from '../../utils/getSelects.js'; +/** + * A collection to store select menu commands. + * @type {Collection} + */ const selects = new Collection(); +/** + * Flag to indicate if select menus have been loaded. + * @type {boolean} + */ let selectsLoaded = false; +/** + * Sends an embed reply to an interaction. + * @param {Interaction} interaction - The interaction to reply to. + * @param {string} color - The color of the embed. + * @param {string} description - The description of the embed. + * @param {boolean} [ephemeral=true] - If the reply should be ephemeral. + */ const sendEmbedReply = async ( - interaction, - color, - description, - ephemeral = true + interaction, + color, + description, + ephemeral = true ) => { - try { - const embed = new EmbedBuilder() - .setColor(color) - .setDescription(description) - .setAuthor({ - name: interaction.user.username, - iconURL: interaction.user.displayAvatarURL({ dynamic: true }), - }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed], ephemeral }); - } catch (err) {} + try { + const embed = new EmbedBuilder() + .setColor(color) + .setDescription(description) + .setAuthor({ + name: interaction.user.username, + iconURL: interaction.user.displayAvatarURL({ dynamic: true }), + }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed], ephemeral }); + } catch (err) {} }; +/** + * Checks if a member has a set of permissions. + * @param {Interaction} interaction - The interaction to check permissions for. + * @param {Array} permissions - The permissions to check. + * @param {'user' | 'bot'} type - The type of member to check permissions for. + * @returns {boolean} - True if the member has all the specified permissions. + */ const checkPermissions = (interaction, permissions, type) => { - const member = - type === 'user' ? interaction.member : interaction.guild.members.me; - return permissions.every((permission) => - member.permissions.has(PermissionsBitField.Flags[permission]) - ); + const member = + type === 'user' ? interaction.member : interaction.guild.members.me; + return permissions.every((permission) => + member.permissions.has(PermissionsBitField.Flags[permission]) + ); }; +/** + * Loads select menu commands from files. + * @param {Function} errorHandler - The error handler function. + */ const loadSelects = async (errorHandler) => { - try { - const selectFiles = await getSelects(); - for (const select of selectFiles) { - if (select && select.customId) { - selects.set(select.customId, select); - } + try { + const selectFiles = await getSelects(); + for (const select of selectFiles) { + if (select && select.customId) { + selects.set(select.customId, select); } - console.log(`Loaded ${selects.size} select menu commands`.green); - selectsLoaded = true; - } catch (error) { - errorHandler.handleError(error, { type: 'selectLoad' }); - console.error('Error loading select menus:'.red, error); - } + } + console.log(`Loaded ${selects.size} select menu commands`.green); + selectsLoaded = true; + } catch (error) { + errorHandler.handleError(error, { type: 'selectLoad' }); + console.error('Error loading select menus:'.red, error); + } }; +/** + * Handles a select menu interaction. + * @param {Client} client - The Discord client. + * @param {Function} errorHandler - The error handler function. + * @param {Interaction} interaction - The interaction to handle. + */ const handleSelect = async (client, errorHandler, interaction) => { - const { customId } = interaction; - const selectObject = selects.get(customId); - if (!selectObject) return; - - const { developersId, testServerId } = config; - - if (selectObject.devOnly && !developersId.includes(interaction.user.id)) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandDevOnly - ); - } - - if ( - selectObject.testMode && - interaction.guild && - interaction.guild.id !== testServerId - ) { + const { customId } = interaction; + const selectObject = selects.get(customId); + if (!selectObject) return; + + const { developersId, testServerId } = config; + + if (selectObject.devOnly && !developersId.includes(interaction.user.id)) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandDevOnly + ); + } + + if ( + selectObject.testMode && + interaction.guild && + interaction.guild.id !== testServerId + ) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.commandTestMode + ); + } + + if ( + selectObject.userPermissions?.length && + !checkPermissions(interaction, selectObject.userPermissions, 'user') + ) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.userNoPermissions + ); + } + + if ( + selectObject.botPermissions?.length && + !checkPermissions(interaction, selectObject.botPermissions, 'bot') + ) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.botNoPermissions + ); + } + + if ( + interaction.message.interaction && + interaction.message.interaction.user.id !== interaction.user.id + ) { + return sendEmbedReply( + interaction, + mConfig.embedColorError, + mConfig.cannotUseSelect + ); + } + + if (selectObject.cooldown) { + const cooldownKey = `${interaction.user.id}-${customId}`; + const cooldownTime = selects.get(cooldownKey); + if (cooldownTime && Date.now() < cooldownTime) { + const remainingTime = Math.ceil((cooldownTime - Date.now()) / 1000); return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.commandTestMode - ); - } - - if ( - selectObject.userPermissions?.length && - !checkPermissions(interaction, selectObject.userPermissions, 'user') - ) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.userNoPermissions - ); - } - - if ( - selectObject.botPermissions?.length && - !checkPermissions(interaction, selectObject.botPermissions, 'bot') - ) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.botNoPermissions - ); - } - - if ( - interaction.message.interaction && - interaction.message.interaction.user.id !== interaction.user.id - ) { - return sendEmbedReply( - interaction, - mConfig.embedColorError, - mConfig.cannotUseSelect - ); - } - - if (selectObject.cooldown) { - const cooldownKey = `${interaction.user.id}-${customId}`; - const cooldownTime = selects.get(cooldownKey); - if (cooldownTime && Date.now() < cooldownTime) { - const remainingTime = Math.ceil((cooldownTime - Date.now()) / 1000); - return sendEmbedReply( - interaction, - mConfig.embedColorError, - `Please wait ${remainingTime} seconds before using this select menu again.` - ); - } - selects.set(cooldownKey, Date.now() + selectObject.cooldown * 1000); - } - - try { - await selectObject.run(client, interaction); - console.log( - `Select menu ${interaction.customId} used by ${interaction.user.tag} in ${interaction.guild?.name}` - .yellow - ); - } catch (error) { - console.error(`Error executing select menu ${customId}:`.red, error); - - await errorHandler.handleError(error, { - type: 'selectError', - selectId: customId, - userId: interaction.user.id, - guildId: interaction.guild?.id, - }); - - sendEmbedReply( - interaction, - mConfig.embedColorError, - 'There was an error while processing this select menu!' + interaction, + mConfig.embedColorError, + `Please wait ${remainingTime} seconds before using this select menu again.` ); - } + } + selects.set(cooldownKey, Date.now() + selectObject.cooldown * 1000); + } + + try { + await selectObject.run(client, interaction); + console.log( + `Select menu ${interaction.customId} used by ${interaction.user.tag} in ${interaction.guild?.name}` + .yellow + ); + } catch (error) { + console.error(`Error executing select menu ${customId}:`.red, error); + + await errorHandler.handleError(error, { + type: 'selectError', + selectId: customId, + userId: interaction.user.id, + guildId: interaction.guild?.id, + }); + + sendEmbedReply( + interaction, + mConfig.embedColorError, + 'There was an error while processing this select menu!' + ); + } }; +/** + * Handles select menu interactions. + * @param {Client} client - The Discord client. + * @param {Function} errorHandler - The error handler function. + * @param {Interaction} interaction - The interaction to handle. + */ export default async (client, errorHandler, interaction) => { - if (!interaction.isAnySelectMenu()) return; + if (!interaction.isAnySelectMenu()) return; - if (!selectsLoaded) { - await loadSelects(errorHandler); - } + if (!selectsLoaded) { + await loadSelects(errorHandler); + } - await handleSelect(client, errorHandler, interaction); + await handleSelect(client, errorHandler, interaction); }; diff --git a/src/events/voiceStateUpdate/joinToCreate.js b/src/events/voiceStateUpdate/joinToCreate.js index bc7b990..b71f84d 100644 --- a/src/events/voiceStateUpdate/joinToCreate.js +++ b/src/events/voiceStateUpdate/joinToCreate.js @@ -4,159 +4,181 @@ import JoinToSystemSetup from '../../schemas/joinToSystemSetup.js'; import createVoiceChannel from '../../utils/join-to-system/createVoiceChannel.js'; /** - * @param {import('discord.js').Client} client - * @param {{ handleError: Function }} errorHandler - * @param {import('discord.js').VoiceState} oldState - * @param {import('discord.js').VoiceState} newState + * Handles the voice state update event for join, leave, and move actions. + * + * @param {import('discord.js').Client} client - The Discord client instance. + * @param {{ handleError: Function }} errorHandler - An object containing an error handling function. + * @param {import('discord.js').VoiceState} oldState - The old voice state of the member. + * @param {import('discord.js').VoiceState} newState - The new voice state of the member. */ export default async (client, { handleError }, oldState, newState) => { - try { - if (!oldState.channelId && newState.channelId) { - await handleJoin(newState); - } else if (oldState.channelId && !newState.channelId) { - await handleLeave(oldState); - } else if (oldState.channelId !== newState.channelId) { - await handleMove(oldState, newState); - } - } catch (error) { - handleError(error, { type: 'joinToCreate event' }); - } + try { + if (!oldState.channelId && newState.channelId) { + await handleJoin(newState); + } else if (oldState.channelId && !newState.channelId) { + await handleLeave(oldState); + } else if (oldState.channelId !== newState.channelId) { + await handleMove(oldState, newState); + } + } catch (error) { + handleError(error, { type: 'joinToCreate event' }); + } }; +/** + * Handles the join action by creating a new voice channel if necessary. + * + * @param {import('discord.js').VoiceState} newState - The new voice state of the member. + */ async function handleJoin(newState) { - const setup = await JoinToSystemSetup.findOne({ - guildId: newState.guild.id, - }); - if (setup && setup.joinToCreateChannelId === newState.channelId) { - try { - const newChannel = await createVoiceChannel( - newState.member, - newState.guild, - newState.client - ); - if (newChannel) { - await JoinToSystem.create({ - guildId: newState.guild.id, - channelId: newChannel.id, - ownerId: newState.member.id, - }); - await newState.setChannel(newChannel); - } - } catch (error) { - console.error('Error creating new channel:', error); + const setup = await JoinToSystemSetup.findOne({ + guildId: newState.guild.id, + }); + if (setup && setup.joinToCreateChannelId === newState.channelId) { + try { + const newChannel = await createVoiceChannel( + newState.member, + newState.guild, + newState.client + ); + if (newChannel) { + await JoinToSystem.create({ + guildId: newState.guild.id, + channelId: newChannel.id, + ownerId: newState.member.id, + }); + await newState.setChannel(newChannel); } - } + } catch (error) { + console.error('Error creating new channel:', error); + } + } } +/** + * Handles the leave action by deleting the channel if it's empty. + * + * @param {import('discord.js').VoiceState} oldState - The old voice state of the member. + */ async function handleLeave(oldState) { - const channel = await JoinToSystem.findOne({ - channelId: oldState.channelId, - }); - if (channel && oldState.channel) { - try { - const updatedChannel = await oldState.channel.fetch(); - if (updatedChannel.members.size === 0) { - await updatedChannel.delete(); - await JoinToSystem.deleteOne({ channelId: oldState.channelId }); - } - } catch (error) { - console.error('Error handling channel leave:', error); + const channel = await JoinToSystem.findOne({ + channelId: oldState.channelId, + }); + if (channel && oldState.channel) { + try { + const updatedChannel = await oldState.channel.fetch(); + if (updatedChannel.members.size === 0) { + await updatedChannel.delete(); + await JoinToSystem.deleteOne({ channelId: oldState.channelId }); } - } + } catch (error) { + console.error('Error handling channel leave:', error); + } + } } +/** + * Handles the move action by creating a new channel if moving to a joinToCreate channel, + * or by checking permissions and handling the leave action for the old channel. + * + * @param {import('discord.js').VoiceState} oldState - The old voice state of the member. + * @param {import('discord.js').VoiceState} newState - The new voice state of the member. + */ async function handleMove(oldState, newState) { - const setup = await JoinToSystemSetup.findOne({ - guildId: newState.guild.id, - }); - const isMovingFromJoinToCreate = - setup && setup.joinToCreateChannelId === oldState.channelId; - const isMovingToJoinToCreate = - setup && setup.joinToCreateChannelId === newState.channelId; + const setup = await JoinToSystemSetup.findOne({ + guildId: newState.guild.id, + }); + const isMovingFromJoinToCreate = + setup && setup.joinToCreateChannelId === oldState.channelId; + const isMovingToJoinToCreate = + setup && setup.joinToCreateChannelId === newState.channelId; - if (isMovingToJoinToCreate) { - try { - const newChannel = await createVoiceChannel( - newState.member, - newState.guild, - newState.client - ); - if (newChannel) { - await JoinToSystem.create({ - guildId: newState.guild.id, - channelId: newChannel.id, - ownerId: newState.member.id, - }); - await newState.setChannel(newChannel); - // Delete the Join to Create channel - const joinToCreateChannel = newState.channel; - setTimeout(async () => { - try { - await joinToCreateChannel.delete(); - } catch (error) { - console.error( - 'Error deleting Join to Create channel:', - error - ); - } - }, 1000); // Delay to ensure the member has moved to the new channel - } - } catch (error) { - console.error('Error creating new channel:', error); - } - } else { - const newChannel = await JoinToSystem.findOne({ - channelId: newState.channelId, - }); + if (isMovingToJoinToCreate) { + try { + const newChannel = await createVoiceChannel( + newState.member, + newState.guild, + newState.client + ); if (newChannel) { - const canJoin = await checkJoinPermission( - newState.member, - newState.channel - ); - if (!canJoin) { - await newState.setChannel(oldState.channel); - return; - } + await JoinToSystem.create({ + guildId: newState.guild.id, + channelId: newChannel.id, + ownerId: newState.member.id, + }); + await newState.setChannel(newChannel); + // Delete the Join to Create channel + const joinToCreateChannel = newState.channel; + setTimeout(async () => { + try { + await joinToCreateChannel.delete(); + } catch (error) { + console.error('Error deleting Join to Create channel:', error); + } + }, 1000); // Delay to ensure the member has moved to the new channel } - await handleLeave(oldState); - } + } catch (error) { + console.error('Error creating new channel:', error); + } + } else { + const newChannel = await JoinToSystem.findOne({ + channelId: newState.channelId, + }); + if (newChannel) { + const canJoin = await checkJoinPermission( + newState.member, + newState.channel + ); + if (!canJoin) { + await newState.setChannel(oldState.channel); + return; + } + } + await handleLeave(oldState); + } - // Handle leaving the old channel if it's a created channel - const oldChannel = await JoinToSystem.findOne({ - channelId: oldState.channelId, - }); - if (oldChannel && oldState.channel) { - try { - const updatedOldChannel = await oldState.channel.fetch(); - if (updatedOldChannel.members.size === 0) { - await updatedOldChannel.delete(); - await JoinToSystem.deleteOne({ channelId: oldState.channelId }); - } - } catch (error) { - console.error('Error handling old channel leave:', error); + // Handle leaving the old channel if it's a created channel + const oldChannel = await JoinToSystem.findOne({ + channelId: oldState.channelId, + }); + if (oldChannel && oldState.channel) { + try { + const updatedOldChannel = await oldState.channel.fetch(); + if (updatedOldChannel.members.size === 0) { + await updatedOldChannel.delete(); + await JoinToSystem.deleteOne({ channelId: oldState.channelId }); } - } + } catch (error) { + console.error('Error handling old channel leave:', error); + } + } } +/** + * Checks if a member has permission to join a channel. + * + * @param {import('discord.js').GuildMember} member - The member attempting to join. + * @param {import('discord.js').VoiceChannel} channel - The channel to join. + * @returns {boolean} - True if the member can join, false otherwise. + */ async function checkJoinPermission(member, channel) { - if (!channel) return true; + if (!channel) return true; - const channelDoc = await JoinToSystem.findOne({ channelId: channel.id }); - if (!channelDoc) return true; + const channelDoc = await JoinToSystem.findOne({ channelId: channel.id }); + if (!channelDoc) return true; - if (channelDoc.isLocked) { - const isOwner = channelDoc.ownerId === member.id; - const hasAdminPermission = - channel - .permissionsFor(member) - ?.has(PermissionFlagsBits.Administrator) ?? false; - const isAllowed = channelDoc.allowedUsers?.includes(member.id) ?? false; + if (channelDoc.isLocked) { + const isOwner = channelDoc.ownerId === member.id; + const hasAdminPermission = + channel.permissionsFor(member)?.has(PermissionFlagsBits.Administrator) ?? + false; + const isAllowed = channelDoc.allowedUsers?.includes(member.id) ?? false; - if (!isOwner && !hasAdminPermission && !isAllowed) return false; - } + if (!isOwner && !hasAdminPermission && !isAllowed) return false; + } - if (channelDoc.userLimit > 0 && channel.members.size >= channelDoc.userLimit) - return false; + if (channelDoc.userLimit > 0 && channel.members.size >= channelDoc.userLimit) + return false; - return true; + return true; } diff --git a/src/handlers/errorHandler.js b/src/handlers/errorHandler.js index f221c00..76efc66 100644 --- a/src/handlers/errorHandler.js +++ b/src/handlers/errorHandler.js @@ -1,513 +1,510 @@ import { - WebhookClient, - EmbedBuilder, - Events, - DiscordAPIError, - Client, + WebhookClient, + EmbedBuilder, + Events, + DiscordAPIError, + Client, } from 'discord.js'; import Bottleneck from 'bottleneck'; import crypto from 'crypto'; import determineErrorCategory from '../utils/error/determineErrorCategory.js'; import getRecoverySuggestions from '../utils/error/getRecoverySuggestions.js'; class DiscordBotErrorHandler { - constructor(config) { - this.config = { - webhookUrl: process.env.ERROR_WEBHOOK_URL, - environment: process.env.NODE_ENV || 'development', - maxCacheSize: 100, - retryAttempts: 3, - retryDelay: 1000, - rateLimit: { maxConcurrent: 1, minTime: 2000 }, - clientName: '', - rateLimitThreshold: 5, - rateLimitPeriod: 60000, // 1 minute - groupThreshold: 5, - trendThreshold: 3, - trendPeriod: 3600000, // 1 hour - ...config, - }; - - if (!this.config.webhookUrl) { - console.error( - 'ERROR_WEBHOOK_URL is not set in the environment variables' - ); - } else { - this.webhookClient = new WebhookClient({ - url: this.config.webhookUrl, - }); - } - - this.errorCache = new Map(); - this.errorQueue = []; - this.processingQueue = false; - this.errorRateLimit = new Map(); - this.errorGroups = new Map(); - this.errorTrends = new Map(); - - this.limiter = new Bottleneck(this.config.rateLimit); - } - - initialize(client) { - this.client = client; - if (!this.client) { - console.error('Discord client is not provided'); - return; - } - this.setupEventListeners(); - this.client.ws.on('error', this.handleWebSocketError.bind(this)); - } - - setupEventListeners() { - this.client.on(Events.ClientReady, (client) => { - this.config.clientName = - this.client.user?.username || this.config.clientName; - }); - this.client.on(Events.Error, (error) => - this.handleError(error, { type: 'clientError' }) - ); - this.client.on(Events.Warn, (info) => - this.handleError(new Error(info), { - type: 'clientWarning', - severity: 'Warning', - }) - ); - process.on('unhandledRejection', (reason) => - this.handleError(reason, { type: 'unhandledRejection' }) - ); - process.on('uncaughtException', (error) => - this.handleError(error, { type: 'uncaughtException' }) + constructor(config) { + this.config = { + webhookUrl: process.env.ERROR_WEBHOOK_URL, + environment: process.env.NODE_ENV || 'development', + maxCacheSize: 100, + retryAttempts: 3, + retryDelay: 1000, + rateLimit: { maxConcurrent: 1, minTime: 2000 }, + clientName: '', + rateLimitThreshold: 5, + rateLimitPeriod: 60000, // 1 minute + groupThreshold: 5, + trendThreshold: 3, + trendPeriod: 3600000, // 1 hour + ...config, + }; + + if (!this.config.webhookUrl) { + console.error( + 'ERROR_WEBHOOK_URL is not set in the environment variables' ); - } - - handleWebSocketError(error) { - this.handleError(error, { type: 'webSocketError' }); - } - - async handleError(error, context = {}) { + } else { + this.webhookClient = new WebhookClient({ + url: this.config.webhookUrl, + }); + } + + this.errorCache = new Map(); + this.errorQueue = []; + this.processingQueue = false; + this.errorRateLimit = new Map(); + this.errorGroups = new Map(); + this.errorTrends = new Map(); + + this.limiter = new Bottleneck(this.config.rateLimit); + } + + initialize(client) { + this.client = client; + if (!this.client) { + console.error('Discord client is not provided'); + return; + } + this.setupEventListeners(); + this.client.ws.on('error', this.handleWebSocketError.bind(this)); + } + + setupEventListeners() { + this.client.on(Events.ClientReady, (client) => { + this.config.clientName = + this.client.user?.username || this.config.clientName; + }); + this.client.on(Events.Error, (error) => + this.handleError(error, { type: 'clientError' }) + ); + this.client.on(Events.Warn, (info) => + this.handleError(new Error(info), { + type: 'clientWarning', + severity: 'Warning', + }) + ); + process.on('unhandledRejection', (reason) => + this.handleError(reason, { type: 'unhandledRejection' }) + ); + process.on('uncaughtException', (error) => + this.handleError(error, { type: 'uncaughtException' }) + ); + } + + handleWebSocketError(error) { + this.handleError(error, { type: 'webSocketError' }); + } + + async handleError(error, context = {}) { + try { + const errorDetails = await this.formatErrorDetails(error, context); + await this.processError(errorDetails); + } catch (err) { + console.error('Failed to handle error:', err); + } + } + + cleanStackTrace(error, limit = 10) { + const stack = (error.stack || '') + .split('\n') + .filter( + (line) => !line.includes('node_modules') && !line.includes('timers.js') + ) + .slice(0, limit) + .join('\n'); + + const errorType = error.name ? `${error.name}: ` : ''; + return `\`\`\`${errorType}${stack}\`\`\``; + } + + async captureContext(providedContext) { + const guildContext = await this.getGuildContext(providedContext.guildId); + const userContext = await this.getUserContext(providedContext.userId); + const channelContext = await this.getChannelContext( + providedContext.channelId + ); + + return { + ...providedContext, + guild: guildContext, + user: userContext, + channel: channelContext, + command: providedContext.command || 'Unknown', + timestamp: new Date().toISOString(), + }; + } + + async getChannelContext(channelId) { + if (channelId) { try { - const errorDetails = await this.formatErrorDetails(error, context); - await this.processError(errorDetails); - } catch (err) { - console.error('Failed to handle error:', err); + const channel = await this.client.channels.fetch(channelId); + return { + id: channel.id, + name: channel.name, + type: channel.type, + parentId: channel.parentId, + }; + } catch (error) { + console.error('Failed to fetch channel context:', error); } - } - - cleanStackTrace(error, limit = 10) { - const stack = (error.stack || '') - .split('\n') - .filter( - (line) => - !line.includes('node_modules') && !line.includes('timers.js') - ) - .slice(0, limit) - .join('\n'); - - const errorType = error.name ? `${error.name}: ` : ''; - return `\`\`\`${errorType}${stack}\`\`\``; - } - - async captureContext(providedContext) { - const guildContext = await this.getGuildContext(providedContext.guildId); - const userContext = await this.getUserContext(providedContext.userId); - const channelContext = await this.getChannelContext( - providedContext.channelId - ); + } + return null; + } - return { - ...providedContext, - guild: guildContext, - user: userContext, - channel: channelContext, - command: providedContext.command || 'Unknown', - timestamp: new Date().toISOString(), - }; - } - - async getChannelContext(channelId) { - if (channelId) { - try { - const channel = await this.client.channels.fetch(channelId); - return { - id: channel.id, - name: channel.name, - type: channel.type, - parentId: channel.parentId, - }; - } catch (error) { - console.error('Failed to fetch channel context:', error); - } - } - return null; - } - - async getGuildContext(guildId) { - if (guildId) { - try { - const guild = await this.client.guilds.fetch(guildId); - return { - id: guild.id, - name: guild.name, - memberCount: guild.memberCount, - }; - } catch (error) { - console.error('Failed to fetch guild context:', error); - } - } - return null; - } - - async getUserContext(userId) { - if (userId) { - try { - const user = await this.client.users.fetch(userId); - return { id: user.id, tag: user.tag, createdAt: user.createdAt }; - } catch (error) { - console.error('Failed to fetch user context:', error); - } + async getGuildContext(guildId) { + if (guildId) { + try { + const guild = await this.client.guilds.fetch(guildId); + return { + id: guild.id, + name: guild.name, + memberCount: guild.memberCount, + }; + } catch (error) { + console.error('Failed to fetch guild context:', error); } - return null; - } - - determineErrorSeverity(error) { - if (error instanceof DiscordAPIError) { - const code = error.code; - if ([40001, 40002, 40003, 50013, 50001, 50014].includes(code)) - return 'Critical'; - if ( - [ - 10008, 10003, 30001, 30002, 30003, 30005, 30007, 30010, 30013, - 30015, 30016, - ].includes(code) - ) - return 'Major'; - if ([50035, 50041, 50045, 50046].includes(code)) return 'Moderate'; - return 'Minor'; + } + return null; + } + + async getUserContext(userId) { + if (userId) { + try { + const user = await this.client.users.fetch(userId); + return { id: user.id, tag: user.tag, createdAt: user.createdAt }; + } catch (error) { + console.error('Failed to fetch user context:', error); } + } + return null; + } + + determineErrorSeverity(error) { + if (error instanceof DiscordAPIError) { + const code = error.code; + if ([40001, 40002, 40003, 50013, 50001, 50014].includes(code)) + return 'Critical'; if ( - error instanceof Error && - error.message === "Unhandled 'error' event emitted" + [ + 10008, 10003, 30001, 30002, 30003, 30005, 30007, 30010, 30013, 30015, + 30016, + ].includes(code) ) - return 'Major'; - if (error.critical) return 'Critical'; - if (error instanceof TypeError) return 'Warning'; - if (error.message.includes('rate limit')) return 'Major'; - if (error.message.includes('unknown')) return 'Minor'; - return 'Moderate'; - } - - async capturePerformanceMetrics() { - if (!this.client) { - console.error('Discord client is not initialized'); - return {}; + return 'Major'; + if ([50035, 50041, 50045, 50046].includes(code)) return 'Moderate'; + return 'Minor'; + } + if ( + error instanceof Error && + error.message === "Unhandled 'error' event emitted" + ) + return 'Major'; + if (error.critical) return 'Critical'; + if (error instanceof TypeError) return 'Warning'; + if (error.message.includes('rate limit')) return 'Major'; + if (error.message.includes('unknown')) return 'Minor'; + return 'Moderate'; + } + + async capturePerformanceMetrics() { + if (!this.client) { + console.error('Discord client is not initialized'); + return {}; + } + + const memoryUsage = process.memoryUsage(); + const uptime = process.uptime(); + + return { + heapUsed: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)} MB`, + heapTotal: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)} MB`, + guildCount: this.client.guilds?.cache?.size || 0, + userCount: this.client.users?.cache?.size || 0, + uptime: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m ${Math.floor(uptime % 60)}s`, + cpuUsage: process.cpuUsage(), + latency: `${this.client.ws.ping}ms`, + }; + } + + async processError(errorDetails) { + const errorKey = `${errorDetails.category}:${errorDetails.message}`; + + if (this.isRateLimited(errorKey)) { + console.log(`Error rate-limited: ${errorKey}`); + return; + } + + if (this.errorCache.has(errorKey)) { + this.updateErrorFrequency(errorKey); + } else { + this.errorCache.set(errorKey, { + count: 1, + lastOccurrence: new Date(), + }); + this.errorQueue.push(errorDetails); + if (this.errorCache.size > this.config.maxCacheSize) { + const oldestError = [...this.errorCache.entries()].sort( + (a, b) => a[1].lastOccurrence - b[1].lastOccurrence + )[0][0]; + this.errorCache.delete(oldestError); } - - const memoryUsage = process.memoryUsage(); - const uptime = process.uptime(); - - return { - heapUsed: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)} MB`, - heapTotal: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)} MB`, - guildCount: this.client.guilds?.cache?.size || 0, - userCount: this.client.users?.cache?.size || 0, - uptime: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m ${Math.floor(uptime % 60)}s`, - cpuUsage: process.cpuUsage(), - latency: `${this.client.ws.ping}ms`, - }; - } - - async processError(errorDetails) { - const errorKey = `${errorDetails.category}:${errorDetails.message}`; - - if (this.isRateLimited(errorKey)) { - console.log(`Error rate-limited: ${errorKey}`); - return; + } + + this.updateErrorTrend(errorDetails); + + if (this.isErrorTrending(errorDetails)) { + await this.sendTrendAlert(errorDetails); + } + + if (!this.processingQueue) { + this.processingQueue = true; + await this.processErrorQueue(); + } + } + + isRateLimited(errorKey) { + const now = Date.now(); + const errorTimes = this.errorRateLimit.get(errorKey) || []; + + // Remove old entries + while ( + errorTimes.length > 0 && + errorTimes[0] < now - this.config.rateLimitPeriod + ) { + errorTimes.shift(); + } + + if (errorTimes.length >= this.config.rateLimitThreshold) { + return true; + } + + errorTimes.push(now); + this.errorRateLimit.set(errorKey, errorTimes); + return false; + } + + async processErrorQueue() { + const processedErrors = new Set(); + + while (this.errorQueue.length > 0) { + const errorDetails = this.errorQueue.shift(); + const errorHash = this.generateErrorHash(errorDetails); + + if (!processedErrors.has(errorHash)) { + this.addToErrorGroup(errorDetails); + processedErrors.add(errorHash); } - - if (this.errorCache.has(errorKey)) { - this.updateErrorFrequency(errorKey); + } + + await this.sendGroupedErrors(); + this.processingQueue = false; + } + + generateErrorHash(errorDetails) { + const { message, stackTrace, category, severity } = errorDetails; + return crypto + .createHash('md5') + .update(`${message}${stackTrace}${category}${severity}`) + .digest('hex'); + } + + addToErrorGroup(errorDetails) { + const groupKey = `${errorDetails.category}:${errorDetails.severity}`; + if (!this.errorGroups.has(groupKey)) { + this.errorGroups.set(groupKey, []); + } + this.errorGroups.get(groupKey).push(errorDetails); + } + + async sendGroupedErrors() { + for (const [groupKey, errors] of this.errorGroups.entries()) { + if (errors.length >= this.config.groupThreshold) { + await this.sendGroupedErrorToWebhook(groupKey, errors); } else { - this.errorCache.set(errorKey, { - count: 1, - lastOccurrence: new Date(), - }); - this.errorQueue.push(errorDetails); - if (this.errorCache.size > this.config.maxCacheSize) { - const oldestError = [...this.errorCache.entries()].sort( - (a, b) => a[1].lastOccurrence - b[1].lastOccurrence - )[0][0]; - this.errorCache.delete(oldestError); - } - } - - this.updateErrorTrend(errorDetails); - - if (this.isErrorTrending(errorDetails)) { - await this.sendTrendAlert(errorDetails); - } - - if (!this.processingQueue) { - this.processingQueue = true; - await this.processErrorQueue(); - } - } - - isRateLimited(errorKey) { - const now = Date.now(); - const errorTimes = this.errorRateLimit.get(errorKey) || []; - - // Remove old entries - while ( - errorTimes.length > 0 && - errorTimes[0] < now - this.config.rateLimitPeriod - ) { - errorTimes.shift(); - } - - if (errorTimes.length >= this.config.rateLimitThreshold) { - return true; + for (const error of errors) { + await this.limiter.schedule(() => this.sendErrorToWebhook(error)); + } } + } + this.errorGroups.clear(); + } + + async sendGroupedErrorToWebhook(groupKey, errors) { + const [category, severity] = groupKey.split(':'); + const summary = this.createErrorSummary(errors); + + const embed = new EmbedBuilder() + .setColor(this.getColorForSeverity(severity)) + .setTitle(`Grouped ${category} Errors - ${severity}`) + .setDescription(summary) + .addFields( + { + name: 'Error Count', + value: errors.length.toString(), + inline: true, + }, + { + name: 'Time Range', + value: `${new Date(errors[0].timestamp).toISOString()} - ${new Date(errors[errors.length - 1].timestamp).toISOString()}`, + inline: true, + } + ) + .setTimestamp(); + + await this.webhookClient.send({ + content: `Grouped ${severity} errors reported`, + embeds: [embed], + }); + } + + createErrorSummary(errors) { + const messageCounts = {}; + for (const error of errors) { + messageCounts[error.message] = (messageCounts[error.message] || 0) + 1; + } + + return Object.entries(messageCounts) + .map(([message, count]) => `${message} (${count} occurrences)`) + .join('\n'); + } + + updateErrorTrend(errorDetails) { + const trendKey = `${errorDetails.category}:${errorDetails.message}`; + const now = Date.now(); + + if (!this.errorTrends.has(trendKey)) { + this.errorTrends.set(trendKey, []); + } + + const trend = this.errorTrends.get(trendKey); + trend.push(now); + + // Remove old entries + while (trend.length > 0 && trend[0] < now - this.config.trendPeriod) { + trend.shift(); + } + } + + isErrorTrending(errorDetails) { + const trendKey = `${errorDetails.category}:${errorDetails.message}`; + const trend = this.errorTrends.get(trendKey); + return trend && trend.length >= this.config.trendThreshold; + } + + async sendTrendAlert(errorDetails) { + const embed = new EmbedBuilder() + .setColor(this.getColorForSeverity('Warning')) + .setTitle('Error Trend Detected') + .setDescription( + `The following error is trending:\n\`\`\`${errorDetails.message}\`\`\`` + ) + .addFields( + { + name: 'Category', + value: errorDetails.category, + inline: true, + }, + { + name: 'Severity', + value: errorDetails.severity, + inline: true, + }, + { + name: 'Occurrences in the last hour', + value: this.errorTrends + .get(`${errorDetails.category}:${errorDetails.message}`) + .length.toString(), + inline: true, + } + ) + .setTimestamp(); - errorTimes.push(now); - this.errorRateLimit.set(errorKey, errorTimes); - return false; - } - - async processErrorQueue() { - const processedErrors = new Set(); - - while (this.errorQueue.length > 0) { - const errorDetails = this.errorQueue.shift(); - const errorHash = this.generateErrorHash(errorDetails); - - if (!processedErrors.has(errorHash)) { - this.addToErrorGroup(errorDetails); - processedErrors.add(errorHash); - } - } + await this.webhookClient.send({ + content: 'Error trend alert', + embeds: [embed], + }); + } - await this.sendGroupedErrors(); - this.processingQueue = false; - } - - generateErrorHash(errorDetails) { - const { message, stackTrace, category, severity } = errorDetails; - return crypto - .createHash('md5') - .update(`${message}${stackTrace}${category}${severity}`) - .digest('hex'); - } - - addToErrorGroup(errorDetails) { - const groupKey = `${errorDetails.category}:${errorDetails.severity}`; - if (!this.errorGroups.has(groupKey)) { - this.errorGroups.set(groupKey, []); - } - this.errorGroups.get(groupKey).push(errorDetails); - } - - async sendGroupedErrors() { - for (const [groupKey, errors] of this.errorGroups.entries()) { - if (errors.length >= this.config.groupThreshold) { - await this.sendGroupedErrorToWebhook(groupKey, errors); - } else { - for (const error of errors) { - await this.limiter.schedule(() => - this.sendErrorToWebhook(error) - ); - } - } - } - this.errorGroups.clear(); - } - - async sendGroupedErrorToWebhook(groupKey, errors) { - const [category, severity] = groupKey.split(':'); - const summary = this.createErrorSummary(errors); - - const embed = new EmbedBuilder() - .setColor(this.getColorForSeverity(severity)) - .setTitle(`Grouped ${category} Errors - ${severity}`) - .setDescription(summary) - .addFields( + async sendErrorToWebhook(errorDetails) { + for (let attempt = 0; attempt < this.config.retryAttempts; attempt++) { + try { + const embed = new EmbedBuilder() + .setColor(this.getColorForSeverity(errorDetails.severity)) + .setTitle(`${errorDetails.category} - ${errorDetails.severity}`) + .setDescription(`\`\`\`${errorDetails.message}\`\`\``) + .addFields( { - name: 'Error Count', - value: errors.length.toString(), - inline: true, + name: 'Stack Trace', + value: `\`\`\`${errorDetails.stackTrace}\`\`\``, + inline: false, }, { - name: 'Time Range', - value: `${new Date(errors[0].timestamp).toISOString()} - ${new Date(errors[errors.length - 1].timestamp).toISOString()}`, - inline: true, - } - ) - .setTimestamp(); - - await this.webhookClient.send({ - content: `Grouped ${severity} errors reported`, - embeds: [embed], - }); - } - - createErrorSummary(errors) { - const messageCounts = {}; - for (const error of errors) { - messageCounts[error.message] = (messageCounts[error.message] || 0) + 1; - } - - return Object.entries(messageCounts) - .map(([message, count]) => `${message} (${count} occurrences)`) - .join('\n'); - } - - updateErrorTrend(errorDetails) { - const trendKey = `${errorDetails.category}:${errorDetails.message}`; - const now = Date.now(); - - if (!this.errorTrends.has(trendKey)) { - this.errorTrends.set(trendKey, []); - } - - const trend = this.errorTrends.get(trendKey); - trend.push(now); - - // Remove old entries - while (trend.length > 0 && trend[0] < now - this.config.trendPeriod) { - trend.shift(); - } - } - - isErrorTrending(errorDetails) { - const trendKey = `${errorDetails.category}:${errorDetails.message}`; - const trend = this.errorTrends.get(trendKey); - return trend && trend.length >= this.config.trendThreshold; - } - - async sendTrendAlert(errorDetails) { - const embed = new EmbedBuilder() - .setColor(this.getColorForSeverity('Warning')) - .setTitle('Error Trend Detected') - .setDescription( - `The following error is trending:\n\`\`\`${errorDetails.message}\`\`\`` - ) - .addFields( - { - name: 'Category', - value: errorDetails.category, - inline: true, + name: 'Context', + value: `\`\`\`json\n${JSON.stringify(errorDetails.context, null, 2)}\`\`\``, + inline: false, }, { - name: 'Severity', - value: errorDetails.severity, - inline: true, + name: 'Performance', + value: `\`\`\`json\n${JSON.stringify(await errorDetails.performance, null, 2)}\`\`\``, + inline: false, }, { - name: 'Occurrences in the last hour', - value: this.errorTrends - .get(`${errorDetails.category}:${errorDetails.message}`) - .length.toString(), - inline: true, + name: 'Recovery Suggestions', + value: errorDetails.recoverySuggestions, + inline: false, } - ) - .setTimestamp(); - - await this.webhookClient.send({ - content: 'Error trend alert', - embeds: [embed], - }); - } - - async sendErrorToWebhook(errorDetails) { - for (let attempt = 0; attempt < this.config.retryAttempts; attempt++) { - try { - const embed = new EmbedBuilder() - .setColor(this.getColorForSeverity(errorDetails.severity)) - .setTitle(`${errorDetails.category} - ${errorDetails.severity}`) - .setDescription(`\`\`\`${errorDetails.message}\`\`\``) - .addFields( - { - name: 'Stack Trace', - value: `\`\`\`${errorDetails.stackTrace}\`\`\``, - inline: false, - }, - { - name: 'Context', - value: `\`\`\`json\n${JSON.stringify(errorDetails.context, null, 2)}\`\`\``, - inline: false, - }, - { - name: 'Performance', - value: `\`\`\`json\n${JSON.stringify(await errorDetails.performance, null, 2)}\`\`\``, - inline: false, - }, - { - name: 'Recovery Suggestions', - value: errorDetails.recoverySuggestions, - inline: false, - } - ) - .setTimestamp(new Date(errorDetails.timestamp)) - .setFooter({ - text: `Environment: ${errorDetails.environment.nodeVersion} | Client: ${errorDetails.environment.clientName}`, - }); - - await this.webhookClient.send({ - content: `New ${errorDetails.severity} error reported`, - embeds: [embed], - }); - return; - } catch (err) { - console.error( - `Failed to send error to webhook (attempt ${attempt + 1}):`, - err - ); - if (attempt < this.config.retryAttempts - 1) { - await new Promise((resolve) => - setTimeout(resolve, this.config.retryDelay) - ); - } - } + ) + .setTimestamp(new Date(errorDetails.timestamp)) + .setFooter({ + text: `Environment: ${errorDetails.environment.nodeVersion} | Client: ${errorDetails.environment.clientName}`, + }); + + await this.webhookClient.send({ + content: `New ${errorDetails.severity} error reported`, + embeds: [embed], + }); + return; + } catch (err) { + console.error( + `Failed to send error to webhook (attempt ${attempt + 1}):`, + err + ); + if (attempt < this.config.retryAttempts - 1) { + await new Promise((resolve) => + setTimeout(resolve, this.config.retryDelay) + ); + } } - } - - getColorForSeverity(severity) { - const colors = { - Minor: 0xffa500, - Moderate: 0xff4500, - Major: 0xff0000, - Critical: 0x8b0000, - Warning: 0xffff00, - }; - return colors[severity] || 0x000000; - } - - updateErrorFrequency(errorKey) { - const errorInfo = this.errorCache.get(errorKey); - errorInfo.count += 1; - errorInfo.lastOccurrence = new Date(); - this.errorCache.set(errorKey, errorInfo); - } - - async formatErrorDetails(error, context) { - const fullContext = await this.captureContext(context); - const recoverySuggestions = await getRecoverySuggestions(error); - - return { - message: error.message || 'Unknown error', - stackTrace: error.stack - ? this.cleanStackTrace(error) - : 'No stack trace available', - category: determineErrorCategory(error), - severity: this.determineErrorSeverity(error), - context: fullContext, - performance: await this.capturePerformanceMetrics(), - timestamp: new Date().toISOString(), - environment: { - nodeVersion: process.version, - clientName: this.config.clientName, - }, - recoverySuggestions, - }; - } + } + } + + getColorForSeverity(severity) { + const colors = { + Minor: 0xffa500, + Moderate: 0xff4500, + Major: 0xff0000, + Critical: 0x8b0000, + Warning: 0xffff00, + }; + return colors[severity] || 0x000000; + } + + updateErrorFrequency(errorKey) { + const errorInfo = this.errorCache.get(errorKey); + errorInfo.count += 1; + errorInfo.lastOccurrence = new Date(); + this.errorCache.set(errorKey, errorInfo); + } + + async formatErrorDetails(error, context) { + const fullContext = await this.captureContext(context); + const recoverySuggestions = await getRecoverySuggestions(error); + + return { + message: error.message || 'Unknown error', + stackTrace: error.stack + ? this.cleanStackTrace(error) + : 'No stack trace available', + category: determineErrorCategory(error), + severity: this.determineErrorSeverity(error), + context: fullContext, + performance: await this.capturePerformanceMetrics(), + timestamp: new Date().toISOString(), + environment: { + nodeVersion: process.version, + clientName: this.config.clientName, + }, + recoverySuggestions, + }; + } } export default DiscordBotErrorHandler; diff --git a/src/handlers/eventHandler.js b/src/handlers/eventHandler.js index 1b5c1b6..e373c69 100644 --- a/src/handlers/eventHandler.js +++ b/src/handlers/eventHandler.js @@ -13,10 +13,10 @@ const __dirname = path.dirname(__filename); * @param {Object} eventInfo - Information about the event handler. */ const registerEvent = (eventRegistry, eventName, eventInfo) => { - if (!eventRegistry.has(eventName)) { - eventRegistry.set(eventName, []); - } - eventRegistry.get(eventName).push(eventInfo); + if (!eventRegistry.has(eventName)) { + eventRegistry.set(eventName, []); + } + eventRegistry.get(eventName).push(eventInfo); }; /** @@ -26,31 +26,31 @@ const registerEvent = (eventRegistry, eventName, eventInfo) => { * @param {Function} errorHandler - The error handler function. */ const loadEventFile = async ( - eventFile, - eventName, - errorHandler, - eventRegistry + eventFile, + eventName, + errorHandler, + eventRegistry ) => { - try { - const { default: eventFunction } = await import( - encodeURI(`file://${eventFile}`) - ); - if (typeof eventFunction !== 'function') { - throw new Error(`Invalid or missing event function in ${eventFile}`); - } - const eventInfo = { - function: eventFunction, - fileName: path.basename(eventFile), - priority: eventFunction.priority || 0, - }; - registerEvent(eventRegistry, eventName, eventInfo); - } catch (error) { - errorHandler.handleError(error, { - type: 'loadingEventFile', - eventFile, - eventName, - }); - } + try { + const { default: eventFunction } = await import( + encodeURI(`file://${eventFile}`) + ); + if (typeof eventFunction !== 'function') { + throw new Error(`Invalid or missing event function in ${eventFile}`); + } + const eventInfo = { + function: eventFunction, + fileName: path.basename(eventFile), + priority: eventFunction.priority || 0, + }; + registerEvent(eventRegistry, eventName, eventInfo); + } catch (error) { + errorHandler.handleError(error, { + type: 'loadingEventFile', + eventFile, + eventName, + }); + } }; /** @@ -60,27 +60,27 @@ const loadEventFile = async ( * @param {Map} eventRegistry - The event registry. */ const processEventFolder = async (eventFolder, errorHandler, eventRegistry) => { - try { - const eventFiles = getAllFiles(eventFolder); - let eventName = path.basename(eventFolder); + try { + const eventFiles = getAllFiles(eventFolder); + let eventName = path.basename(eventFolder); - if (eventName === 'validations') { - eventName = 'interactionCreate'; - } + if (eventName === 'validations') { + eventName = 'interactionCreate'; + } - const loadPromises = eventFiles - .filter((eventFile) => fs.lstatSync(eventFile).isFile()) - .map((eventFile) => - loadEventFile(eventFile, eventName, errorHandler, eventRegistry) - ); + const loadPromises = eventFiles + .filter((eventFile) => fs.lstatSync(eventFile).isFile()) + .map((eventFile) => + loadEventFile(eventFile, eventName, errorHandler, eventRegistry) + ); - await Promise.all(loadPromises); - } catch (error) { - errorHandler.handleError(error, { - type: 'processingEventFolder', - eventFolder, - }); - } + await Promise.all(loadPromises); + } catch (error) { + errorHandler.handleError(error, { + type: 'processingEventFolder', + eventFolder, + }); + } }; /** @@ -89,44 +89,44 @@ const processEventFolder = async (eventFolder, errorHandler, eventRegistry) => { * @param {Function} errorHandler - The error handler function. */ const loadEventHandlers = async (client, errorHandler) => { - const eventRegistry = new Map(); - const loadedEvents = new Set(); + const eventRegistry = new Map(); + const loadedEvents = new Set(); - try { - const eventFolders = getAllFiles( - path.join(__dirname, '..', 'events'), - true - ); + try { + const eventFolders = getAllFiles( + path.join(__dirname, '..', 'events'), + true + ); - await Promise.all( - eventFolders.map((eventFolder) => - processEventFolder(eventFolder, errorHandler, eventRegistry) - ) - ); + await Promise.all( + eventFolders.map((eventFolder) => + processEventFolder(eventFolder, errorHandler, eventRegistry) + ) + ); - for (const [eventName, eventHandlers] of eventRegistry) { - eventHandlers.sort((a, b) => b.priority - a.priority); + for (const [eventName, eventHandlers] of eventRegistry) { + eventHandlers.sort((a, b) => b.priority - a.priority); - if (!loadedEvents.has(eventName)) { - client.on(eventName, async (...args) => { - for (const handler of eventHandlers) { - try { - await handler.function(client, errorHandler, ...args); - } catch (error) { - errorHandler.handleError(error, { - type: 'executingEventHandler', - handler: handler.fileName, - eventName, - }); - } - } - }); - loadedEvents.add(eventName); - } + if (!loadedEvents.has(eventName)) { + client.on(eventName, async (...args) => { + for (const handler of eventHandlers) { + try { + await handler.function(client, errorHandler, ...args); + } catch (error) { + errorHandler.handleError(error, { + type: 'executingEventHandler', + handler: handler.fileName, + eventName, + }); + } + } + }); + loadedEvents.add(eventName); } - } catch (error) { - errorHandler.handleError(error, { type: 'settingUpEventHandlers' }); - } + } + } catch (error) { + errorHandler.handleError(error, { type: 'settingUpEventHandlers' }); + } }; export default loadEventHandlers; diff --git a/src/index.js b/src/index.js index a1e932c..48b82da 100644 --- a/src/index.js +++ b/src/index.js @@ -1,77 +1,97 @@ /** @format */ +/** + * Import required modules and configure the environment. + * @requires dotenv/config + * @requires discord.js + * @requires ./handlers/errorHandler + */ import 'dotenv/config'; import { Client, GatewayIntentBits } from 'discord.js'; import DiscordBotErrorHandler from './handlers/errorHandler.js'; // @ts-check +/** + * Checks if all required environment variables are defined. + * If any are missing, it logs an error message and exits the process. + * @throws {Error} If any environment variable is missing. + */ const checkEnvVariables = () => { - if (!process.env.TOKEN) { - console.error( - 'ERROR: TOKEN is not defined in the environment variables.' - ); - process.exit(1); - } - if (!process.env.MONGODB_TOKEN) { - console.error( - 'ERROR: MONGODB_TOKEN is not defined in the environment variables.' - ); - process.exit(1); - } - if (!process.env.GITHUB_TOKEN) { - console.error( - 'ERROR: GITHUB_TOKEN is not defined in the environment variables.' - ); - process.exit(1); - } + if (!process.env.TOKEN) { + console.error('ERROR: TOKEN is not defined in the environment variables.'); + process.exit(1); + } + if (!process.env.MONGODB_TOKEN) { + console.error( + 'ERROR: MONGODB_TOKEN is not defined in the environment variables.' + ); + process.exit(1); + } + if (!process.env.GITHUB_TOKEN) { + console.error( + 'ERROR: GITHUB_TOKEN is not defined in the environment variables.' + ); + process.exit(1); + } }; +/** + * The main function that initializes the Discord bot. + * It checks environment variables, sets up the event handler, and logs into Discord. + * @async + * @throws {Error} If any step of the initialization process fails. + */ const main = async () => { - checkEnvVariables(); - let eventHandler; - try { - const module = await import('./handlers/eventHandler.js'); - eventHandler = module.default; - } catch (error) { - console.error('ERROR: Failed to import event handler:', error); - process.exit(1); - } + checkEnvVariables(); + let eventHandler; + try { + // Dynamically imports the event handler module + const module = await import('./handlers/eventHandler.js'); + eventHandler = module.default; + } catch (error) { + console.error('ERROR: Failed to import event handler:', error); + process.exit(1); + } - // Create a new Discord client with the necessary intents - const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMembers, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - GatewayIntentBits.DirectMessages, - GatewayIntentBits.GuildVoiceStates, - ], - }); + // Creates a new Discord client with the necessary intents + const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.DirectMessages, + GatewayIntentBits.GuildVoiceStates, + ], + }); - const errorHandler = new DiscordBotErrorHandler({ - webhookUrl: process.env.ERROR_WEBHOOK_URL, - }); + // Initializes the error handler with the error webhook URL + const errorHandler = new DiscordBotErrorHandler({ + webhookUrl: process.env.ERROR_WEBHOOK_URL, + }); - try { - eventHandler(client, errorHandler); - } catch (error) { - console.error('ERROR: Failed to set up event handler:', error); - process.exit(1); - } + try { + // Sets up the event handler for the client + eventHandler(client, errorHandler); + } catch (error) { + console.error('ERROR: Failed to set up event handler:', error); + process.exit(1); + } - try { - errorHandler.initialize(client); - client.errorHandler = errorHandler; + try { + // Initializes the error handler and logs the client into Discord + errorHandler.initialize(client); + client.errorHandler = errorHandler; - await client.login(process.env.TOKEN); - } catch (error) { - console.error('ERROR: Logging into Discord failed:', error); - process.exit(1); - } + await client.login(process.env.TOKEN); + } catch (error) { + console.error('ERROR: Logging into Discord failed:', error); + process.exit(1); + } }; +// Calls the main function and catches any unhandled errors main().catch((error) => { - console.error('Unhandled error in main function:', error); - process.exit(1); + console.error('Unhandled error in main function:', error); + process.exit(1); }); diff --git a/src/modals/closeTicketModal.js b/src/modals/closeTicketModal.js index cee2d74..6d1aa8c 100644 --- a/src/modals/closeTicketModal.js +++ b/src/modals/closeTicketModal.js @@ -2,47 +2,40 @@ import { EmbedBuilder } from 'discord.js'; import { closeTicket } from '../utils/ticket/ticketClose.js'; export default { - customId: 'closeTicketModal', - userPermissions: [], - botPermissions: [], - run: async (client, interaction) => { - try { - const { channel, member, guild, fields } = interaction; + customId: 'closeTicketModal', + userPermissions: [], + botPermissions: [], + run: async (client, interaction) => { + try { + const { channel, member, guild, fields } = interaction; - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); - const reason = - fields.getTextInputValue('closeTicketReason') || - 'No reason provided'; + const reason = + fields.getTextInputValue('closeTicketReason') || 'No reason provided'; - const result = await closeTicket( - client, - guild, - channel, - member, - reason - ); + const result = await closeTicket(client, guild, channel, member, reason); - if (result.success) { - const closedEmbed = new EmbedBuilder() - .setColor('Red') - .setTitle('Ticket Closed') - .setDescription('This ticket has been closed.'); + if (result.success) { + const closedEmbed = new EmbedBuilder() + .setColor('Red') + .setTitle('Ticket Closed') + .setDescription('This ticket has been closed.'); - await interaction.editReply({ embeds: [closedEmbed] }); - } else { - await interaction.editReply({ - content: result.message, - ephemeral: true, - }); - } - } catch (error) { - console.error('Error handling ticket closure:', error); - await interaction.editReply({ - content: - 'There was an error closing the ticket. Please try again later.', - ephemeral: true, - }); + await interaction.editReply({ embeds: [closedEmbed] }); + } else { + await interaction.editReply({ + content: result.message, + ephemeral: true, + }); } - }, + } catch (error) { + console.error('Error handling ticket closure:', error); + await interaction.editReply({ + content: + 'There was an error closing the ticket. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/modals/guild-join.js b/src/modals/guild-join.js index c5c4b9c..26cafdd 100644 --- a/src/modals/guild-join.js +++ b/src/modals/guild-join.js @@ -1,106 +1,106 @@ import { - EmbedBuilder, - ButtonBuilder, - ActionRowBuilder, - ButtonStyle, + EmbedBuilder, + ButtonBuilder, + ActionRowBuilder, + ButtonStyle, } from 'discord.js'; import Guild from '../schemas/guildSchema.js'; export default { - customId: 'guild-join', - userPermissions: [], - botPermissions: [], - run: async (client, interaction) => { - try { - const ign = interaction.fields.getTextInputValue('ign'); - const reason = interaction.fields.getTextInputValue('reason'); + customId: 'guild-join', + userPermissions: [], + botPermissions: [], + run: async (client, interaction) => { + try { + const ign = interaction.fields.getTextInputValue('ign'); + const reason = interaction.fields.getTextInputValue('reason'); - // Find the guild - const guild = await Guild.findOne(); - if (!guild) { - return interaction.reply({ - content: 'The guild has not been set up yet.', - ephemeral: true, - }); - } + // Find the guild + const guild = await Guild.findOne(); + if (!guild) { + return interaction.reply({ + content: 'The guild has not been set up yet.', + ephemeral: true, + }); + } - // Check if user is already a member or has a pending request - const isExistingMember = guild.members.some( - (member) => member.userId === interaction.user.id - ); - const hasPendingRequest = guild.pendingMembers.some( - (member) => member.userId === interaction.user.id - ); + // Check if user is already a member or has a pending request + const isExistingMember = guild.members.some( + (member) => member.userId === interaction.user.id + ); + const hasPendingRequest = guild.pendingMembers.some( + (member) => member.userId === interaction.user.id + ); - if (isExistingMember || hasPendingRequest) { - return interaction.reply({ - content: - 'You are already a member or have a pending request for this guild.', - ephemeral: true, - }); - } + if (isExistingMember || hasPendingRequest) { + return interaction.reply({ + content: + 'You are already a member or have a pending request for this guild.', + ephemeral: true, + }); + } - const approvalChannel = interaction.client.channels.cache.get( - guild.approvalChannelId - ); - if (!approvalChannel) { - return interaction.reply({ - content: 'The approval channel has not been set up correctly.', - ephemeral: true, - }); - } + const approvalChannel = interaction.client.channels.cache.get( + guild.approvalChannelId + ); + if (!approvalChannel) { + return interaction.reply({ + content: 'The approval channel has not been set up correctly.', + ephemeral: true, + }); + } - const embed = new EmbedBuilder() - .setTitle('New Guild Join Request') - .setColor('#0099ff') - .addFields( - { name: 'User', value: `<@${interaction.user.id}>` }, - { name: 'Hypixel IGN', value: ign }, - { name: 'Reason', value: reason } - ) - .setTimestamp(); + const embed = new EmbedBuilder() + .setTitle('New Guild Join Request') + .setColor('#0099ff') + .addFields( + { name: 'User', value: `<@${interaction.user.id}>` }, + { name: 'Hypixel IGN', value: ign }, + { name: 'Reason', value: reason } + ) + .setTimestamp(); - const approveButton = new ButtonBuilder() - .setCustomId('approve-guild-join') - .setLabel('Approve') - .setStyle(ButtonStyle.Success); + const approveButton = new ButtonBuilder() + .setCustomId('approve-guild-join') + .setLabel('Approve') + .setStyle(ButtonStyle.Success); - const denyButton = new ButtonBuilder() - .setCustomId('deny-guild-join') - .setLabel('Deny') - .setStyle(ButtonStyle.Danger); + const denyButton = new ButtonBuilder() + .setCustomId('deny-guild-join') + .setLabel('Deny') + .setStyle(ButtonStyle.Danger); - const row = new ActionRowBuilder().addComponents( - approveButton, - denyButton - ); + const row = new ActionRowBuilder().addComponents( + approveButton, + denyButton + ); - const approvalMessage = await approvalChannel.send({ - embeds: [embed], - components: [row], - }); + const approvalMessage = await approvalChannel.send({ + embeds: [embed], + components: [row], + }); - guild.pendingMembers.push({ - userId: interaction.user.id, - ign: ign, - reason: reason, - messageId: approvalMessage.id, - appliedDate: new Date(), - }); + guild.pendingMembers.push({ + userId: interaction.user.id, + ign: ign, + reason: reason, + messageId: approvalMessage.id, + appliedDate: new Date(), + }); - await guild.save(); + await guild.save(); - return interaction.reply({ - content: - 'Your request to join the guild has been sent to the admins for approval.', - ephemeral: true, - }); - } catch (error) { - console.error(error); - return interaction.reply({ - content: 'An error occurred while processing your request.', - ephemeral: true, - }); - } - }, + return interaction.reply({ + content: + 'Your request to join the guild has been sent to the admins for approval.', + ephemeral: true, + }); + } catch (error) { + console.error(error); + return interaction.reply({ + content: 'An error occurred while processing your request.', + ephemeral: true, + }); + } + }, }; diff --git a/src/modals/set_limit_modal.js b/src/modals/set_limit_modal.js index 208346e..dbfcc3b 100644 --- a/src/modals/set_limit_modal.js +++ b/src/modals/set_limit_modal.js @@ -1,114 +1,112 @@ import { - EmbedBuilder, - ButtonBuilder, - ActionRowBuilder, - ButtonStyle, + EmbedBuilder, + ButtonBuilder, + ActionRowBuilder, + ButtonStyle, } from 'discord.js'; import JoinToSystemChannel from '../schemas/joinToSystemSchema.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; export default { - customId: 'set_limit_modal', - userPermissions: [], - botPermissions: [], - - run: async (client, interaction) => { - await interaction.deferReply({ ephemeral: true }); - - try { - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); - - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return await interaction.editReply({ - content: - checkResult.message || - 'You do not have permission to manage this channel.', - }); - } - - const userLimit = interaction.fields.getTextInputValue('user_limit'); - const limitNumber = parseInt(userLimit, 10); - - if (isNaN(limitNumber) || limitNumber < 0 || limitNumber > 99) { - return await interaction.editReply({ - content: - 'Please enter a valid number between 0 and 99. (0 means no limit)', - }); - } - - const member = await interaction.guild.members - .fetch(interaction.user.id) - .catch(() => null); - if (!member) { - return await interaction.editReply({ - content: - 'Failed to fetch your member information. Please try again.', - }); - } - - const voiceChannel = member.voice.channel; - if (!voiceChannel) { - return await interaction.editReply({ - content: - 'You need to be in a voice channel to set the user limit.', - }); - } - - await voiceChannel.setUserLimit(limitNumber).catch((error) => { - console.error('Error setting user limit:', error); - throw new Error('Failed to set user limit for the voice channel.'); - }); - - const channelDoc = await JoinToSystemChannel.findOneAndUpdate( - { channelId: voiceChannel.id }, - { userLimit: limitNumber }, - { new: true, upsert: true } - ).catch((error) => { - console.error('Error updating database:', error); - throw new Error( - 'Failed to update the database with the new user limit.' - ); - }); - - const embed = new EmbedBuilder() - .setColor('#00FF00') - .setTitle('User Limit Updated') - .setDescription( - `The user limit for this voice channel has been set to ${limitNumber}.` - ) - .setFooter({ text: `Requested by ${interaction.user.tag}` }) - .setTimestamp(); - - const resetButton = new ButtonBuilder() - .setCustomId('reset_user_limit') - .setLabel('Reset Limit') - .setStyle(ButtonStyle.Secondary); - - const row = new ActionRowBuilder().addComponents(resetButton); - - await interaction.editReply({ - embeds: [embed], - components: [row], - }); - } catch (error) { - console.error('Error in set_limit_modal:', error); - - const errorMessage = - error.message || - 'An unexpected error occurred while processing your request.'; - - await interaction - .editReply({ - content: `${errorMessage} Please try again later.`, - }) - .catch(console.error); + customId: 'set_limit_modal', + userPermissions: [], + botPermissions: [], + + run: async (client, interaction) => { + await interaction.deferReply({ ephemeral: true }); + + try { + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); + + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return await interaction.editReply({ + content: + checkResult.message || + 'You do not have permission to manage this channel.', + }); } - }, + + const userLimit = interaction.fields.getTextInputValue('user_limit'); + const limitNumber = parseInt(userLimit, 10); + + if (isNaN(limitNumber) || limitNumber < 0 || limitNumber > 99) { + return await interaction.editReply({ + content: + 'Please enter a valid number between 0 and 99. (0 means no limit)', + }); + } + + const member = await interaction.guild.members + .fetch(interaction.user.id) + .catch(() => null); + if (!member) { + return await interaction.editReply({ + content: 'Failed to fetch your member information. Please try again.', + }); + } + + const voiceChannel = member.voice.channel; + if (!voiceChannel) { + return await interaction.editReply({ + content: 'You need to be in a voice channel to set the user limit.', + }); + } + + await voiceChannel.setUserLimit(limitNumber).catch((error) => { + console.error('Error setting user limit:', error); + throw new Error('Failed to set user limit for the voice channel.'); + }); + + const channelDoc = await JoinToSystemChannel.findOneAndUpdate( + { channelId: voiceChannel.id }, + { userLimit: limitNumber }, + { new: true, upsert: true } + ).catch((error) => { + console.error('Error updating database:', error); + throw new Error( + 'Failed to update the database with the new user limit.' + ); + }); + + const embed = new EmbedBuilder() + .setColor('#00FF00') + .setTitle('User Limit Updated') + .setDescription( + `The user limit for this voice channel has been set to ${limitNumber}.` + ) + .setFooter({ text: `Requested by ${interaction.user.tag}` }) + .setTimestamp(); + + const resetButton = new ButtonBuilder() + .setCustomId('reset_user_limit') + .setLabel('Reset Limit') + .setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(resetButton); + + await interaction.editReply({ + embeds: [embed], + components: [row], + }); + } catch (error) { + console.error('Error in set_limit_modal:', error); + + const errorMessage = + error.message || + 'An unexpected error occurred while processing your request.'; + + await interaction + .editReply({ + content: `${errorMessage} Please try again later.`, + }) + .catch(console.error); + } + }, }; diff --git a/src/modals/ticketCreateModal.js b/src/modals/ticketCreateModal.js index b261f95..009bb7f 100644 --- a/src/modals/ticketCreateModal.js +++ b/src/modals/ticketCreateModal.js @@ -2,69 +2,69 @@ import { createTicket } from '../utils/ticket/ticketCreate.js'; import ticketSetupSchema from '../schemas/ticketSetupSchema.js'; export default { - customId: 'ticketModal', - userPermissions: [], - botPermissions: [], + customId: 'ticketModal', + userPermissions: [], + botPermissions: [], - run: async (client, interaction) => { - try { - const { fields, guild, member, channel } = interaction; + run: async (client, interaction) => { + try { + const { fields, guild, member, channel } = interaction; - // Retrieve values from the modal - const subject = fields.getTextInputValue('ticketSubject'); - const description = fields.getTextInputValue('ticketDesc'); + // Retrieve values from the modal + const subject = fields.getTextInputValue('ticketSubject'); + const description = fields.getTextInputValue('ticketDesc'); - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); - // Fetch ticket setup configuration from the database - const ticketSetup = await ticketSetupSchema.findOne({ - guildID: guild.id, - }); + // Fetch ticket setup configuration from the database + const ticketSetup = await ticketSetupSchema.findOne({ + guildID: guild.id, + }); - if (!ticketSetup) { - return await interaction.editReply({ - content: 'Ticket setup not found.', - ephemeral: true, - }); - } + if (!ticketSetup) { + return await interaction.editReply({ + content: 'Ticket setup not found.', + ephemeral: true, + }); + } - // Get the ticket category and staff role - const category = guild.channels.cache.get(ticketSetup.categoryID); - if (!category) { - return await interaction.editReply({ - content: 'Ticket category not found.', - ephemeral: true, - }); - } - const staffRole = guild.roles.cache.get(ticketSetup.staffRoleID); - if (!staffRole) { - return await interaction.editReply({ - content: 'Staff role not found.', - ephemeral: true, - }); - } + // Get the ticket category and staff role + const category = guild.channels.cache.get(ticketSetup.categoryID); + if (!category) { + return await interaction.editReply({ + content: 'Ticket category not found.', + ephemeral: true, + }); + } + const staffRole = guild.roles.cache.get(ticketSetup.staffRoleID); + if (!staffRole) { + return await interaction.editReply({ + content: 'Staff role not found.', + ephemeral: true, + }); + } - const result = await createTicket( - guild, - member, - staffRole, - category, - subject, - description, - channel.id - ); + const result = await createTicket( + guild, + member, + staffRole, + category, + subject, + description, + channel.id + ); - await interaction.editReply({ - content: result.message, - ephemeral: true, - }); - } catch (error) { - console.error('Error handling ticket creation:', error); - await interaction.editReply({ - content: - 'There was an error creating your ticket. Please try again later.', - ephemeral: true, - }); - } - }, + await interaction.editReply({ + content: result.message, + ephemeral: true, + }); + } catch (error) { + console.error('Error handling ticket creation:', error); + await interaction.editReply({ + content: + 'There was an error creating your ticket. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/plugins/test.js b/src/plugins/test.js new file mode 100644 index 0000000..242136d --- /dev/null +++ b/src/plugins/test.js @@ -0,0 +1,9 @@ +{ + data: { + name: 'test'; + type: ''; + } + // async init() { + + // } +} diff --git a/src/schemas/AvatarSchema.js b/src/schemas/AvatarSchema.js index 6ffc2fb..e34c986 100644 --- a/src/schemas/AvatarSchema.js +++ b/src/schemas/AvatarSchema.js @@ -2,42 +2,42 @@ import mongoose from 'mongoose'; // Avatar Schema const AvatarSchema = new mongoose.Schema({ - userId: { type: String, required: true }, - guildId: { type: String, required: true }, - avatarUrl: { type: String, required: true }, - timestamp: { type: Date, default: Date.now }, - isGlobal: { type: Boolean, default: true }, + userId: { type: String, required: true }, + guildId: { type: String, required: true }, + avatarUrl: { type: String, required: true }, + timestamp: { type: Date, default: Date.now }, + isGlobal: { type: Boolean, default: true }, }); // Avatar Rating Schema const AvatarRatingSchema = new mongoose.Schema({ - avatarId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Avatar', - required: true, - }, - raterId: { type: String, required: true }, - rating: { type: Number, min: 1, max: 5, required: true }, - timestamp: { type: Date, default: Date.now }, + avatarId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Avatar', + required: true, + }, + raterId: { type: String, required: true }, + rating: { type: Number, min: 1, max: 5, required: true }, + timestamp: { type: Date, default: Date.now }, }); // Avatar Challenge Schema const AvatarChallengeSchema = new mongoose.Schema({ - guildId: { type: String, required: true }, - title: { type: String, required: true }, - description: { type: String }, - startDate: { type: Date, required: true }, - endDate: { type: Date, required: true }, - participants: [{ type: String }], // Array of user IDs - winner: { type: String }, + guildId: { type: String, required: true }, + title: { type: String, required: true }, + description: { type: String }, + startDate: { type: Date, required: true }, + endDate: { type: Date, required: true }, + participants: [{ type: String }], // Array of user IDs + winner: { type: String }, }); // Create models const Avatar = mongoose.model('Avatar', AvatarSchema); const AvatarRating = mongoose.model('AvatarRating', AvatarRatingSchema); const AvatarChallenge = mongoose.model( - 'AvatarChallenge', - AvatarChallengeSchema + 'AvatarChallenge', + AvatarChallengeSchema ); export { Avatar, AvatarRating, AvatarChallenge }; diff --git a/src/schemas/economy.js b/src/schemas/economy.js index b120487..0c8706a 100644 --- a/src/schemas/economy.js +++ b/src/schemas/economy.js @@ -7,20 +7,20 @@ import mongoose from 'mongoose'; * This schema stores the balance information for each user, including their wallet and bank balances, as well as timestamps for various cooldowns. */ const balanceSchema = new mongoose.Schema( - { - userId: { type: String, required: true, unique: true, index: true }, - balance: { type: Number, default: 0 }, - bank: { type: Number, default: 0 }, - lastDaily: { type: Date, default: null }, - lastWeekly: { type: Date, default: null }, - lastHourly: { type: Date, default: null }, - lastBeg: { type: Date, default: null }, - lastCoin: { type: Date, default: null }, - lastWork: { type: Date, default: null }, - lastCrime: { type: Date, default: null }, - lastSlots: { type: Date, default: null }, - }, - { timestamps: true } + { + userId: { type: String, required: true, unique: true, index: true }, + balance: { type: Number, default: 0 }, + bank: { type: Number, default: 0 }, + lastDaily: { type: Date, default: null }, + lastWeekly: { type: Date, default: null }, + lastHourly: { type: Date, default: null }, + lastBeg: { type: Date, default: null }, + lastCoin: { type: Date, default: null }, + lastWork: { type: Date, default: null }, + lastCrime: { type: Date, default: null }, + lastSlots: { type: Date, default: null }, + }, + { timestamps: true } ); /** @format @@ -28,14 +28,14 @@ const balanceSchema = new mongoose.Schema( * This schema tracks all transactions made by users, including deposits, withdrawals, and transfers. Each transaction has a type, amount, date, and optional description. */ const transactionSchema = new mongoose.Schema( - { - userId: { type: String, required: true, index: true }, - type: { type: String, required: true }, // e.g., deposit, withdraw, transfer - amount: { type: Number, required: true, min: 0 }, - date: { type: Date, default: Date.now }, - description: { type: String, default: '' }, - }, - { timestamps: true } + { + userId: { type: String, required: true, index: true }, + type: { type: String, required: true }, // e.g., deposit, withdraw, transfer + amount: { type: Number, required: true, min: 0 }, + date: { type: Date, default: Date.now }, + description: { type: String, default: '' }, + }, + { timestamps: true } ); /** @format @@ -43,15 +43,15 @@ const transactionSchema = new mongoose.Schema( * This schema defines the structure for items that can be created, with properties such as itemId, name, price, description, emoji, and category. */ const itemSchema = new mongoose.Schema( - { - itemId: { type: String, required: true, unique: true }, - name: { type: String, required: true }, - price: { type: Number, required: true, min: 0 }, - description: { type: String, default: '' }, - emoji: { type: String, default: '❔' }, - category: { type: String, default: 'misc' }, - }, - { timestamps: true } + { + itemId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + price: { type: Number, required: true, min: 0 }, + description: { type: String, default: '' }, + emoji: { type: String, default: '❔' }, + category: { type: String, default: 'misc' }, + }, + { timestamps: true } ); /** @format @@ -59,16 +59,16 @@ const itemSchema = new mongoose.Schema( * This schema manages the inventory of items each user possesses, including item IDs and their quantities. */ const inventorySchema = new mongoose.Schema( - { - userId: { type: String, required: true, index: true }, - items: [ - { - itemId: { type: String, required: true }, - quantity: { type: Number, default: 1, min: 1 }, - }, - ], - }, - { timestamps: true } + { + userId: { type: String, required: true, index: true }, + items: [ + { + itemId: { type: String, required: true }, + quantity: { type: Number, default: 1, min: 1 }, + }, + ], + }, + { timestamps: true } ); /** @format @@ -76,14 +76,14 @@ const inventorySchema = new mongoose.Schema( * This schema handles quests that users can undertake, including quest ID, name, description, reward, and the user ID of the developer who created the quest. */ const questSchema = new mongoose.Schema( - { - questId: { type: String, required: true, unique: true }, - name: { type: String, required: true }, - description: { type: String, required: true }, - reward: { type: Number, required: true }, - createdBy: { type: String, required: true }, // User ID of the developer who created the quest - }, - { timestamps: true } + { + questId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + description: { type: String, required: true }, + reward: { type: Number, required: true }, + createdBy: { type: String, required: true }, // User ID of the developer who created the quest + }, + { timestamps: true } ); const Quest = mongoose.model('Quest', questSchema); diff --git a/src/schemas/guildSchema.js b/src/schemas/guildSchema.js index e359c7a..36a08e5 100644 --- a/src/schemas/guildSchema.js +++ b/src/schemas/guildSchema.js @@ -2,35 +2,35 @@ import mongoose from 'mongoose'; const guildSchema = new mongoose.Schema( - { - name: { type: String, required: true, unique: true }, - leaderId: { type: String, required: true }, - members: [ - { - userId: String, - role: { - type: String, - enum: ['member', 'officer', 'leader'], - default: 'member', - }, - joinDate: { type: Date, default: Date.now }, - }, - ], - pendingMembers: [ - { - userId: String, - ign: String, - reason: String, - messageId: String, - appliedDate: { type: Date, default: Date.now }, - }, - ], - creationDate: { type: Date, default: Date.now }, - level: { type: Number, default: 1 }, - description: String, - tags: [String], - maxMembers: { type: Number, default: 50 }, - }, - { timestamps: true } + { + name: { type: String, required: true, unique: true }, + leaderId: { type: String, required: true }, + members: [ + { + userId: String, + role: { + type: String, + enum: ['member', 'officer', 'leader'], + default: 'member', + }, + joinDate: { type: Date, default: Date.now }, + }, + ], + pendingMembers: [ + { + userId: String, + ign: String, + reason: String, + messageId: String, + appliedDate: { type: Date, default: Date.now }, + }, + ], + creationDate: { type: Date, default: Date.now }, + level: { type: Number, default: 1 }, + description: String, + tags: [String], + maxMembers: { type: Number, default: 50 }, + }, + { timestamps: true } ); export default mongoose.model('Guild', guildSchema); diff --git a/src/schemas/joinToSystemSchema.js b/src/schemas/joinToSystemSchema.js index c9a0be4..21abfc5 100644 --- a/src/schemas/joinToSystemSchema.js +++ b/src/schemas/joinToSystemSchema.js @@ -1,63 +1,63 @@ import mongoose from 'mongoose'; const joinToSystemChannelSchema = new mongoose.Schema( - { - guildId: { - type: String, - required: true, - }, - channelId: { - type: String, - required: true, - }, - ownerId: { - type: String, - required: true, - }, - name: { - type: String, - required: true, - default: 'Unnamed Channel', - }, - userLimit: { - type: Number, - default: 0, - min: 0, - max: 99, - }, - bitrate: { - type: Number, - default: 64000, - min: 8000, - max: 384000, - }, - rtcRegion: { - type: String, - default: null, - }, - isLocked: { - type: Boolean, - default: false, - }, - allowedRoles: [ - { - type: String, - }, - ], - allowedUsers: [ - { - type: String, - }, - ], - }, - { - timestamps: true, - } + { + guildId: { + type: String, + required: true, + }, + channelId: { + type: String, + required: true, + }, + ownerId: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + default: 'Unnamed Channel', + }, + userLimit: { + type: Number, + default: 0, + min: 0, + max: 99, + }, + bitrate: { + type: Number, + default: 64000, + min: 8000, + max: 384000, + }, + rtcRegion: { + type: String, + default: null, + }, + isLocked: { + type: Boolean, + default: false, + }, + allowedRoles: [ + { + type: String, + }, + ], + allowedUsers: [ + { + type: String, + }, + ], + }, + { + timestamps: true, + } ); const JoinToSystemChannel = mongoose.model( - 'JoinToSystemChannel', - joinToSystemChannelSchema + 'JoinToSystemChannel', + joinToSystemChannelSchema ); export default JoinToSystemChannel; diff --git a/src/schemas/joinToSystemSetup.js b/src/schemas/joinToSystemSetup.js index 3e00d1d..e70c96a 100644 --- a/src/schemas/joinToSystemSetup.js +++ b/src/schemas/joinToSystemSetup.js @@ -1,22 +1,22 @@ import mongoose from 'mongoose'; const joinToSystemSchema = new mongoose.Schema({ - guildId: { - type: String, - required: true, - }, - joinToCreateChannelId: { - type: String, - required: true, - }, - controlChannelId: { - type: String, - required: true, - }, - categoryId: { - type: String, - required: true, - }, + guildId: { + type: String, + required: true, + }, + joinToCreateChannelId: { + type: String, + required: true, + }, + controlChannelId: { + type: String, + required: true, + }, + categoryId: { + type: String, + required: true, + }, }); const JoinToSystem = mongoose.model('JoinToSystem', joinToSystemSchema); diff --git a/src/schemas/prefix.js b/src/schemas/prefix.js index 1c44484..9d47ffd 100644 --- a/src/schemas/prefix.js +++ b/src/schemas/prefix.js @@ -1,18 +1,18 @@ import mongoose from 'mongoose'; const userPrefixSchema = new mongoose.Schema({ - userId: { - type: String, - required: true, - }, - prefix: { - type: String, - default: '!', - }, - exemptFromPrefix: { - type: Boolean, - default: false, - }, + userId: { + type: String, + required: true, + }, + prefix: { + type: String, + default: '!', + }, + exemptFromPrefix: { + type: Boolean, + default: false, + }, }); export const UserPrefix = mongoose.model('UserPrefixs', userPrefixSchema); diff --git a/src/schemas/stockSchema.js b/src/schemas/stockSchema.js index acab832..6968ad7 100644 --- a/src/schemas/stockSchema.js +++ b/src/schemas/stockSchema.js @@ -2,26 +2,26 @@ import mongoose from 'mongoose'; // Schema for stocks const stockSchema = new mongoose.Schema( - { - stockId: { type: String, required: true, unique: true }, - name: { type: String, required: true }, - description: { type: String, default: '' }, - price: { type: Number, required: true }, - lastUpdated: { type: Date, default: Date.now }, - }, - { timestamps: true } + { + stockId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + description: { type: String, default: '' }, + price: { type: Number, required: true }, + lastUpdated: { type: Date, default: Date.now }, + }, + { timestamps: true } ); // Schema for user investments const investmentSchema = new mongoose.Schema( - { - userId: { type: String, required: true, index: true }, - stockId: { type: String, required: true }, - quantity: { type: Number, required: true, min: 1 }, - purchasePrice: { type: Number, required: true }, - purchaseDate: { type: Date, default: Date.now }, - }, - { timestamps: true } + { + userId: { type: String, required: true, index: true }, + stockId: { type: String, required: true }, + quantity: { type: Number, required: true, min: 1 }, + purchasePrice: { type: Number, required: true }, + purchaseDate: { type: Date, default: Date.now }, + }, + { timestamps: true } ); // Create models diff --git a/src/schemas/ticketSchema.js b/src/schemas/ticketSchema.js index ac47dd7..197f2bb 100644 --- a/src/schemas/ticketSchema.js +++ b/src/schemas/ticketSchema.js @@ -1,29 +1,29 @@ import { model, Schema } from 'mongoose'; const ticketSchema = new Schema( - { - guildID: { type: String, required: true }, - ticketMemberID: { type: String, required: true }, - ticketChannelID: { type: String, required: true }, - subject: { type: String, required: false }, - description: { type: String, required: false }, - parentTicketChannelID: { type: String, required: true }, - closed: { type: Boolean, default: false }, - membersAdded: { type: [String], default: [] }, - claimedBy: { type: String, default: null }, - createdAt: { type: Date, default: Date.now }, - status: { - type: String, - enum: ['open', 'closed', 'locked'], - default: 'open', - }, - closeReason: { type: String, default: '' }, - actionLog: { type: [String], default: [] }, - }, - { - strict: false, - timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' }, - } + { + guildID: { type: String, required: true }, + ticketMemberID: { type: String, required: true }, + ticketChannelID: { type: String, required: true }, + subject: { type: String, required: false }, + description: { type: String, required: false }, + parentTicketChannelID: { type: String, required: true }, + closed: { type: Boolean, default: false }, + membersAdded: { type: [String], default: [] }, + claimedBy: { type: String, default: null }, + createdAt: { type: Date, default: Date.now }, + status: { + type: String, + enum: ['open', 'closed', 'locked'], + default: 'open', + }, + closeReason: { type: String, default: '' }, + actionLog: { type: [String], default: [] }, + }, + { + strict: false, + timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' }, + } ); export default model('Ticket', ticketSchema); diff --git a/src/schemas/ticketSetupSchema.js b/src/schemas/ticketSetupSchema.js index 52a440d..89a97ac 100644 --- a/src/schemas/ticketSetupSchema.js +++ b/src/schemas/ticketSetupSchema.js @@ -1,25 +1,25 @@ import { model, Schema } from 'mongoose'; const ticketSetupSchema = new Schema( - { - guildID: String, - ticketChannelID: String, - staffRoleID: String, - ticketType: String, - categoryID: String, - logChannelID: String, - messageID: String, - customOptions: [ - { - label: String, - value: String, - description: String, - }, - ], - }, - { - strict: false, - } + { + guildID: String, + ticketChannelID: String, + staffRoleID: String, + ticketType: String, + categoryID: String, + logChannelID: String, + messageID: String, + customOptions: [ + { + label: String, + value: String, + description: String, + }, + ], + }, + { + strict: false, + } ); export default model('ticketSetup', ticketSetupSchema); diff --git a/src/schemas/url.js b/src/schemas/url.js index c156faf..d9628b1 100644 --- a/src/schemas/url.js +++ b/src/schemas/url.js @@ -1,21 +1,21 @@ import { model, Schema } from 'mongoose'; const urlSchema = new Schema( - { - ID: { type: String, required: true, unique: true }, - userID: { type: String, required: true }, - originalURL: { type: String, required: true }, // Store the original URL - shortURL: { type: String, required: true, unique: true }, // Store the shortened URL - createdAt: { type: Date, default: Date.now }, - updatedAt: { type: Date, default: Date.now }, // Track when the URL was last updated - status: { - type: String, - enum: ['open', 'closed', 'locked'], - default: 'open', - }, - expiresAt: { type: Date }, // Optional: When the URL should expire - }, - { - timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' }, - } + { + ID: { type: String, required: true, unique: true }, + userID: { type: String, required: true }, + originalURL: { type: String, required: true }, // Store the original URL + shortURL: { type: String, required: true, unique: true }, // Store the shortened URL + createdAt: { type: Date, default: Date.now }, + updatedAt: { type: Date, default: Date.now }, // Track when the URL was last updated + status: { + type: String, + enum: ['open', 'closed', 'locked'], + default: 'open', + }, + expiresAt: { type: Date }, // Optional: When the URL should expire + }, + { + timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' }, + } ); export default model('Url', urlSchema); diff --git a/src/schemas/welcomeSchema.js b/src/schemas/welcomeSchema.js index ab5c397..1556410 100644 --- a/src/schemas/welcomeSchema.js +++ b/src/schemas/welcomeSchema.js @@ -1,11 +1,11 @@ import mongoose from 'mongoose'; const welcomeSchema = new mongoose.Schema({ - guildId: { type: String, required: true, unique: true }, - enabled: { type: Boolean, default: false }, - channelId: { type: String, default: null }, - roleId: { type: String, default: null }, - message: { type: String, default: 'Welcome {user} to the server!' }, + guildId: { type: String, required: true, unique: true }, + enabled: { type: Boolean, default: false }, + channelId: { type: String, default: null }, + roleId: { type: String, default: null }, + message: { type: String, default: 'Welcome {user} to the server!' }, }); export default mongoose.model('Welcome', welcomeSchema); diff --git a/src/selects/createTicket.js b/src/selects/createTicket.js index 6acfa02..8c127fe 100644 --- a/src/selects/createTicket.js +++ b/src/selects/createTicket.js @@ -2,79 +2,77 @@ import { createTicket } from '../utils/ticket/ticketCreate.js'; import ticketSetupSchema from '../schemas/ticketSetupSchema.js'; export default { - customId: 'createTicket', - run: async (client, interaction) => { - try { - const member = await interaction.guild.members.fetch( - interaction.user.id - ); - const selectedOption = interaction.values[0]; + customId: 'createTicket', + run: async (client, interaction) => { + try { + const member = await interaction.guild.members.fetch(interaction.user.id); + const selectedOption = interaction.values[0]; - await interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); - // Fetch ticket setup configuration from the database - const ticketSetup = await ticketSetupSchema.findOne({ - guildID: interaction.guild.id, - }); - if (!ticketSetup) { - return await interaction.editReply({ - content: 'Ticket setup not found.', - ephemeral: true, - }); - } + // Fetch ticket setup configuration from the database + const ticketSetup = await ticketSetupSchema.findOne({ + guildID: interaction.guild.id, + }); + if (!ticketSetup) { + return await interaction.editReply({ + content: 'Ticket setup not found.', + ephemeral: true, + }); + } - // Get the ticket category and staff role - const category = interaction.guild.channels.cache.get( - ticketSetup.categoryID - ); - if (!category) { - return await interaction.editReply({ - content: 'Ticket category not found.', - ephemeral: true, - }); - } + // Get the ticket category and staff role + const category = interaction.guild.channels.cache.get( + ticketSetup.categoryID + ); + if (!category) { + return await interaction.editReply({ + content: 'Ticket category not found.', + ephemeral: true, + }); + } - const staffRole = interaction.guild.roles.cache.get( - ticketSetup.staffRoleID - ); - if (!staffRole) { - return await interaction.editReply({ - content: 'Staff role not found.', - ephemeral: true, - }); - } + const staffRole = interaction.guild.roles.cache.get( + ticketSetup.staffRoleID + ); + if (!staffRole) { + return await interaction.editReply({ + content: 'Staff role not found.', + ephemeral: true, + }); + } - // Find the selected option in the custom options - const selectedOptionData = ticketSetup.customOptions.find( - (option) => option.value === selectedOption - ); - if (!selectedOptionData) { - return await interaction.editReply({ - content: 'Invalid ticket option selected.', - ephemeral: true, - }); - } + // Find the selected option in the custom options + const selectedOptionData = ticketSetup.customOptions.find( + (option) => option.value === selectedOption + ); + if (!selectedOptionData) { + return await interaction.editReply({ + content: 'Invalid ticket option selected.', + ephemeral: true, + }); + } - const result = await createTicket( - interaction.guild, - member, - staffRole, - category, - selectedOptionData.label, - selectedOptionData.description, - interaction.channelId - ); + const result = await createTicket( + interaction.guild, + member, + staffRole, + category, + selectedOptionData.label, + selectedOptionData.description, + interaction.channelId + ); - await interaction.editReply({ - content: result.message, - ephemeral: true, - }); - } catch (error) { - console.error('Error in createTicket interaction:', error); - await interaction.editReply({ - content: 'An error occurred while processing your request.', - ephemeral: true, - }); - } - }, + await interaction.editReply({ + content: result.message, + ephemeral: true, + }); + } catch (error) { + console.error('Error in createTicket interaction:', error); + await interaction.editReply({ + content: 'An error occurred while processing your request.', + ephemeral: true, + }); + } + }, }; diff --git a/src/selects/kick_user_select.js b/src/selects/kick_user_select.js index 27e0da6..6f2ff62 100644 --- a/src/selects/kick_user_select.js +++ b/src/selects/kick_user_select.js @@ -4,120 +4,118 @@ import JoinToSystemChannel from '../schemas/joinToSystemSchema.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; const defaultMessages = { - errors: { - notOwner: "You don't have permission to kick users from this channel.", - notInVoiceChannel: 'The selected member is not in the voice channel.', - cannotKickOwner: 'You cannot kick the channel owner.', - generic: - 'An error occurred while processing your request. Please try again later.', - }, - success: { - userKicked: 'The user has been kicked from the voice channel!', - }, - embeds: { - userKicked: { - title: 'User Kicked from Voice Channel', - description: (kickedUser) => - `${kickedUser} has been kicked from the voice channel.`, - color: 'Orange', - }, - }, + errors: { + notOwner: "You don't have permission to kick users from this channel.", + notInVoiceChannel: 'The selected member is not in the voice channel.', + cannotKickOwner: 'You cannot kick the channel owner.', + generic: + 'An error occurred while processing your request. Please try again later.', + }, + success: { + userKicked: 'The user has been kicked from the voice channel!', + }, + embeds: { + userKicked: { + title: 'User Kicked from Voice Channel', + description: (kickedUser) => + `${kickedUser} has been kicked from the voice channel.`, + color: 'Orange', + }, + }, }; export default { - customId: 'kick_user_select', + customId: 'kick_user_select', - /** - * @param {Client} client - * @param {import('discord.js').Interaction} interaction - */ - run: async (client, interaction) => { - try { - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + /** + * @param {Client} client + * @param {import('discord.js').Interaction} interaction + */ + run: async (client, interaction) => { + try { + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: - checkResult.message || - (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: + checkResult.message || + (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), + ephemeral: true, + }); + } - const kickedUserId = interaction.values[0]; - const kickedMember = - await interaction.guild.members.fetch(kickedUserId); + const kickedUserId = interaction.values[0]; + const kickedMember = await interaction.guild.members.fetch(kickedUserId); - if ( - !kickedMember.voice.channel || - kickedMember.voice.channel.id !== checkResult.channel.id - ) { - const embed = new EmbedBuilder() - .setTitle('Failed to Kick User') - .setDescription( - mconfig.errors?.notInVoiceChannel ?? - defaultMessages.errors.notInVoiceChannel - ) - .setColor('Red'); + if ( + !kickedMember.voice.channel || + kickedMember.voice.channel.id !== checkResult.channel.id + ) { + const embed = new EmbedBuilder() + .setTitle('Failed to Kick User') + .setDescription( + mconfig.errors?.notInVoiceChannel ?? + defaultMessages.errors.notInVoiceChannel + ) + .setColor('Red'); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } + return interaction.reply({ embeds: [embed], ephemeral: true }); + } - const channelDoc = await JoinToSystemChannel.findOne({ - channelId: checkResult.channel.id, - }); + const channelDoc = await JoinToSystemChannel.findOne({ + channelId: checkResult.channel.id, + }); - if (kickedUserId === channelDoc.ownerId) { - return interaction.reply({ - content: - mconfig.errors?.cannotKickOwner ?? - defaultMessages.errors.cannotKickOwner, - ephemeral: true, - }); - } + if (kickedUserId === channelDoc.ownerId) { + return interaction.reply({ + content: + mconfig.errors?.cannotKickOwner ?? + defaultMessages.errors.cannotKickOwner, + ephemeral: true, + }); + } - // Kick the user from the voice channel - await kickedMember.voice.disconnect(); + // Kick the user from the voice channel + await kickedMember.voice.disconnect(); - // Remove the kicked user from the allowedUsers array if present - await JoinToSystemChannel.findOneAndUpdate( - { channelId: checkResult.channel.id }, - { $pull: { allowedUsers: kickedUserId } } - ); + // Remove the kicked user from the allowedUsers array if present + await JoinToSystemChannel.findOneAndUpdate( + { channelId: checkResult.channel.id }, + { $pull: { allowedUsers: kickedUserId } } + ); - const embedConfig = - mconfig.embeds?.userKicked ?? defaultMessages.embeds.userKicked; + const embedConfig = + mconfig.embeds?.userKicked ?? defaultMessages.embeds.userKicked; - const embed = new EmbedBuilder() - .setTitle(embedConfig.title) - .setDescription( - typeof embedConfig.description === 'function' - ? embedConfig.description(kickedMember.user.username) - : `${kickedMember.user.username} has been kicked from the voice channel.` - ) - .setColor(embedConfig.color) - .setTimestamp(); + const embed = new EmbedBuilder() + .setTitle(embedConfig.title) + .setDescription( + typeof embedConfig.description === 'function' + ? embedConfig.description(kickedMember.user.username) + : `${kickedMember.user.username} has been kicked from the voice channel.` + ) + .setColor(embedConfig.color) + .setTimestamp(); - await interaction.update({ - content: - mconfig.success?.userKicked ?? - defaultMessages.success.userKicked, - embeds: [embed], - components: [], - }); - } catch (error) { - console.error('Error in kick_user_select menu:', error); - await interaction.reply({ - content: mconfig.errors?.generic ?? defaultMessages.errors.generic, - ephemeral: true, - }); - } - }, + await interaction.update({ + content: + mconfig.success?.userKicked ?? defaultMessages.success.userKicked, + embeds: [embed], + components: [], + }); + } catch (error) { + console.error('Error in kick_user_select menu:', error); + await interaction.reply({ + content: mconfig.errors?.generic ?? defaultMessages.errors.generic, + ephemeral: true, + }); + } + }, }; diff --git a/src/selects/manage_talk_select.js b/src/selects/manage_talk_select.js index 67697e9..83dc8b4 100644 --- a/src/selects/manage_talk_select.js +++ b/src/selects/manage_talk_select.js @@ -5,120 +5,118 @@ import JoinToSystemChannel from '../schemas/joinToSystemSchema.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; const defaultMessages = { - errors: { - notOwner: - "You don't have permission to manage talk access in this channel.", - notInVoiceChannel: 'The selected member is not in the voice channel.', - cannotManageOwner: 'You cannot manage talk access for the channel owner.', - generic: - 'An error occurred while processing your request. Please try again later.', - }, - success: { - userMuted: 'The user has been muted in the voice channel!', - userUnmuted: 'The user has been unmuted in the voice channel!', - }, - embeds: { - userMuted: { - title: 'User Muted in Voice Channel', - description: (mutedUser) => - `${mutedUser} has been muted in the voice channel.`, - color: 'Orange', - }, - userUnmuted: { - title: 'User Unmuted in Voice Channel', - description: (unmutedUser) => - `${unmutedUser} has been unmuted in the voice channel.`, - color: 'Green', - }, - }, + errors: { + notOwner: + "You don't have permission to manage talk access in this channel.", + notInVoiceChannel: 'The selected member is not in the voice channel.', + cannotManageOwner: 'You cannot manage talk access for the channel owner.', + generic: + 'An error occurred while processing your request. Please try again later.', + }, + success: { + userMuted: 'The user has been muted in the voice channel!', + userUnmuted: 'The user has been unmuted in the voice channel!', + }, + embeds: { + userMuted: { + title: 'User Muted in Voice Channel', + description: (mutedUser) => + `${mutedUser} has been muted in the voice channel.`, + color: 'Orange', + }, + userUnmuted: { + title: 'User Unmuted in Voice Channel', + description: (unmutedUser) => + `${unmutedUser} has been unmuted in the voice channel.`, + color: 'Green', + }, + }, }; export default { - customId: 'manage_talk_select', + customId: 'manage_talk_select', - /** - * @param {Client} client - * @param {import('discord.js').Interaction} interaction - */ - run: async (client, interaction) => { - try { - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + /** + * @param {Client} client + * @param {import('discord.js').Interaction} interaction + */ + run: async (client, interaction) => { + try { + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: - checkResult.message || - (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: + checkResult.message || + (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), + ephemeral: true, + }); + } - const selectedUserId = interaction.values[0]; - const selectedMember = - await interaction.guild.members.fetch(selectedUserId); + const selectedUserId = interaction.values[0]; + const selectedMember = + await interaction.guild.members.fetch(selectedUserId); - if ( - !selectedMember.voice.channel || - selectedMember.voice.channel.id !== checkResult.channel.id - ) { - const embed = new EmbedBuilder() - .setTitle('Failed to Manage Talk Access') - .setDescription( - mconfig.errors?.notInVoiceChannel ?? - defaultMessages.errors.notInVoiceChannel - ) - .setColor('Red'); + if ( + !selectedMember.voice.channel || + selectedMember.voice.channel.id !== checkResult.channel.id + ) { + const embed = new EmbedBuilder() + .setTitle('Failed to Manage Talk Access') + .setDescription( + mconfig.errors?.notInVoiceChannel ?? + defaultMessages.errors.notInVoiceChannel + ) + .setColor('Red'); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } + return interaction.reply({ embeds: [embed], ephemeral: true }); + } - const channelDoc = await JoinToSystemChannel.findOne({ - channelId: checkResult.channel.id, - }); + const channelDoc = await JoinToSystemChannel.findOne({ + channelId: checkResult.channel.id, + }); - // Toggle mute/unmute status - const newMuteStatus = !selectedMember.voice.serverMute; - await selectedMember.voice.setMute(newMuteStatus); + // Toggle mute/unmute status + const newMuteStatus = !selectedMember.voice.serverMute; + await selectedMember.voice.setMute(newMuteStatus); - const embedConfig = newMuteStatus - ? (mconfig.embeds?.userMuted ?? defaultMessages.embeds.userMuted) - : (mconfig.embeds?.userUnmuted ?? - defaultMessages.embeds.userUnmuted); + const embedConfig = newMuteStatus + ? (mconfig.embeds?.userMuted ?? defaultMessages.embeds.userMuted) + : (mconfig.embeds?.userUnmuted ?? defaultMessages.embeds.userUnmuted); - const embed = new EmbedBuilder() - .setTitle(embedConfig.title) - .setDescription( - typeof embedConfig.description === 'function' - ? embedConfig.description(selectedMember.user.username) - : newMuteStatus - ? `${selectedMember.user.username} has been muted in the voice channel.` - : `${selectedMember.user.username} has been unmuted in the voice channel.` - ) - .setColor(embedConfig.color) - .setTimestamp(); + const embed = new EmbedBuilder() + .setTitle(embedConfig.title) + .setDescription( + typeof embedConfig.description === 'function' + ? embedConfig.description(selectedMember.user.username) + : newMuteStatus + ? `${selectedMember.user.username} has been muted in the voice channel.` + : `${selectedMember.user.username} has been unmuted in the voice channel.` + ) + .setColor(embedConfig.color) + .setTimestamp(); - await interaction.update({ - content: newMuteStatus - ? (mconfig.success?.userMuted ?? - defaultMessages.success.userMuted) - : (mconfig.success?.userUnmuted ?? - defaultMessages.success.userUnmuted), - embeds: [embed], - components: [], - }); - } catch (error) { - console.error('Error in manage_talk_select menu:', error); - await interaction.reply({ - content: mconfig.errors?.generic ?? defaultMessages.errors.generic, - ephemeral: true, - }); - } - }, + await interaction.update({ + content: newMuteStatus + ? (mconfig.success?.userMuted ?? defaultMessages.success.userMuted) + : (mconfig.success?.userUnmuted ?? + defaultMessages.success.userUnmuted), + embeds: [embed], + components: [], + }); + } catch (error) { + console.error('Error in manage_talk_select menu:', error); + await interaction.reply({ + content: mconfig.errors?.generic ?? defaultMessages.errors.generic, + ephemeral: true, + }); + } + }, }; diff --git a/src/selects/members_manage.js b/src/selects/members_manage.js index 7b67958..2ec3421 100644 --- a/src/selects/members_manage.js +++ b/src/selects/members_manage.js @@ -2,66 +2,64 @@ import { PermissionFlagsBits } from 'discord.js'; import JoinToSystemChannel from '../schemas/joinToSystemSchema.js'; export default { - customId: 'members_manage', + customId: 'members_manage', - run: async (client, interaction) => { - try { - const member = await interaction.guild.members.fetch( - interaction.user.id - ); - const voiceChannel = member.voice.channel; + run: async (client, interaction) => { + try { + const member = await interaction.guild.members.fetch(interaction.user.id); + const voiceChannel = member.voice.channel; - if (!voiceChannel) { - await interaction.reply({ - content: 'You need to be in a voice channel to manage access.', - ephemeral: true, - }); - return; - } + if (!voiceChannel) { + await interaction.reply({ + content: 'You need to be in a voice channel to manage access.', + ephemeral: true, + }); + return; + } - const channelId = voiceChannel.id; - const userId = interaction.values[0]; - const targetMember = await interaction.guild.members.fetch(userId); - const channelDoc = await JoinToSystemChannel.findOne({ channelId }); + const channelId = voiceChannel.id; + const userId = interaction.values[0]; + const targetMember = await interaction.guild.members.fetch(userId); + const channelDoc = await JoinToSystemChannel.findOne({ channelId }); - // Check if the target member has the Connect permission - const permissions = voiceChannel.permissionsFor(targetMember); - const hasAccess = permissions.has(PermissionFlagsBits.Connect); + // Check if the target member has the Connect permission + const permissions = voiceChannel.permissionsFor(targetMember); + const hasAccess = permissions.has(PermissionFlagsBits.Connect); - if (hasAccess) { - // Remove access - await voiceChannel.permissionOverwrites.edit(targetMember, { - Connect: false, - }); - channelDoc.allowedUsers = channelDoc.allowedUsers.filter( - (id) => id !== userId - ); - await channelDoc.save(); + if (hasAccess) { + // Remove access + await voiceChannel.permissionOverwrites.edit(targetMember, { + Connect: false, + }); + channelDoc.allowedUsers = channelDoc.allowedUsers.filter( + (id) => id !== userId + ); + await channelDoc.save(); - await interaction.reply({ - content: `Access removed from ${targetMember.displayName}.`, - ephemeral: true, - }); - } else { - // Grant access - await voiceChannel.permissionOverwrites.edit(targetMember, { - Connect: true, - }); - channelDoc.allowedUsers.push(userId); - await channelDoc.save(); + await interaction.reply({ + content: `Access removed from ${targetMember.displayName}.`, + ephemeral: true, + }); + } else { + // Grant access + await voiceChannel.permissionOverwrites.edit(targetMember, { + Connect: true, + }); + channelDoc.allowedUsers.push(userId); + await channelDoc.save(); - await interaction.reply({ - content: `Access granted to ${targetMember.displayName}.`, - ephemeral: true, - }); - } - } catch (error) { - console.error('Error in members_manage interaction:', error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); + await interaction.reply({ + content: `Access granted to ${targetMember.displayName}.`, + ephemeral: true, + }); } - }, + } catch (error) { + console.error('Error in members_manage interaction:', error); + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/selects/owner_select.js b/src/selects/owner_select.js index 42a7f0f..d9a3511 100644 --- a/src/selects/owner_select.js +++ b/src/selects/owner_select.js @@ -4,104 +4,103 @@ import JoinToSystemChannel from '../schemas/joinToSystemSchema.js'; import { comprehensiveVoiceCheck } from '../utils/join-to-system/checkChannelOwnership.js'; const defaultMessages = { - errors: { - notOwner: - "You don't have permission to transfer ownership of this channel.", - notInVoiceChannel: 'The selected member is not in the voice channel.', - generic: - 'An error occurred while processing your request. Please try again later.', - }, - success: { - ownerUpdated: 'Owner updated successfully!', - }, - embeds: { - ownershipTransferred: { - title: 'Channel Ownership Transferred', - description: (newOwner) => - `The ownership of this channel has been transferred to ${newOwner}.`, - color: 'Green', - }, - }, + errors: { + notOwner: + "You don't have permission to transfer ownership of this channel.", + notInVoiceChannel: 'The selected member is not in the voice channel.', + generic: + 'An error occurred while processing your request. Please try again later.', + }, + success: { + ownerUpdated: 'Owner updated successfully!', + }, + embeds: { + ownershipTransferred: { + title: 'Channel Ownership Transferred', + description: (newOwner) => + `The ownership of this channel has been transferred to ${newOwner}.`, + color: 'Green', + }, + }, }; export default { - customId: 'owner_select', - botPermissions: [], + customId: 'owner_select', + botPermissions: [], - /** - * @param {Client} client - * @param {import('discord.js').Interaction} interaction - */ - run: async (client, interaction) => { - try { - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + /** + * @param {Client} client + * @param {import('discord.js').Interaction} interaction + */ + run: async (client, interaction) => { + try { + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - !checkResult.isOwner - ) { - return interaction.reply({ - content: - checkResult.message || - (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + !checkResult.isOwner + ) { + return interaction.reply({ + content: + checkResult.message || + (mconfig.errors?.notOwner ?? defaultMessages.errors.notOwner), + ephemeral: true, + }); + } - const newOwnerId = interaction.values[0]; - const newOwner = await interaction.guild.members.fetch(newOwnerId); + const newOwnerId = interaction.values[0]; + const newOwner = await interaction.guild.members.fetch(newOwnerId); - if ( - !newOwner.voice.channel || - newOwner.voice.channel.id !== checkResult.channel.id - ) { - const embed = new EmbedBuilder() - .setTitle('Failed to Transfer Ownership') - .setDescription( - mconfig.errors?.notInVoiceChannel ?? - defaultMessages.errors.notInVoiceChannel - ) - .setColor('Red'); + if ( + !newOwner.voice.channel || + newOwner.voice.channel.id !== checkResult.channel.id + ) { + const embed = new EmbedBuilder() + .setTitle('Failed to Transfer Ownership') + .setDescription( + mconfig.errors?.notInVoiceChannel ?? + defaultMessages.errors.notInVoiceChannel + ) + .setColor('Red'); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } + return interaction.reply({ embeds: [embed], ephemeral: true }); + } - await JoinToSystemChannel.findOneAndUpdate( - { channelId: checkResult.channel.id }, - { ownerId: newOwnerId } - ); + await JoinToSystemChannel.findOneAndUpdate( + { channelId: checkResult.channel.id }, + { ownerId: newOwnerId } + ); - const embedConfig = - mconfig.embeds?.ownershipTransferred ?? - defaultMessages.embeds.ownershipTransferred; + const embedConfig = + mconfig.embeds?.ownershipTransferred ?? + defaultMessages.embeds.ownershipTransferred; - const embed = new EmbedBuilder() - .setTitle(embedConfig.title) - .setDescription( - typeof embedConfig.description === 'function' - ? embedConfig.description(newOwner.user.username) - : `The ownership of this channel has been transferred to ${newOwner.user.username}.` - ) - .setColor(embedConfig.color) - .setTimestamp(); + const embed = new EmbedBuilder() + .setTitle(embedConfig.title) + .setDescription( + typeof embedConfig.description === 'function' + ? embedConfig.description(newOwner.user.username) + : `The ownership of this channel has been transferred to ${newOwner.user.username}.` + ) + .setColor(embedConfig.color) + .setTimestamp(); - await interaction.update({ - content: - mconfig.success?.ownerUpdated ?? - defaultMessages.success.ownerUpdated, - embeds: [embed], - components: [], - }); - } catch (error) { - console.error('Error in owner_select menu:', error); - await interaction.reply({ - content: mconfig.errors?.generic ?? defaultMessages.errors.generic, - ephemeral: true, - }); - } - }, + await interaction.update({ + content: + mconfig.success?.ownerUpdated ?? defaultMessages.success.ownerUpdated, + embeds: [embed], + components: [], + }); + } catch (error) { + console.error('Error in owner_select menu:', error); + await interaction.reply({ + content: mconfig.errors?.generic ?? defaultMessages.errors.generic, + ephemeral: true, + }); + } + }, }; diff --git a/src/selects/role_manage.js b/src/selects/role_manage.js index 51346f7..46ab555 100644 --- a/src/selects/role_manage.js +++ b/src/selects/role_manage.js @@ -2,76 +2,74 @@ import { PermissionFlagsBits } from 'discord.js'; import JoinToSystemChannel from '../schemas/joinToSystemSchema.js'; export default { - customId: 'role_manage', + customId: 'role_manage', - run: async (client, interaction) => { - try { - const member = await interaction.guild.members.fetch( - interaction.user.id - ); - const voiceChannel = member.voice.channel; + run: async (client, interaction) => { + try { + const member = await interaction.guild.members.fetch(interaction.user.id); + const voiceChannel = member.voice.channel; - if (!voiceChannel) { - await interaction.reply({ - content: 'You need to be in a voice channel to manage access.', - ephemeral: true, - }); - return; - } + if (!voiceChannel) { + await interaction.reply({ + content: 'You need to be in a voice channel to manage access.', + ephemeral: true, + }); + return; + } - const channelId = voiceChannel.id; - const roleId = interaction.values[0]; - const targetRole = await interaction.guild.roles.fetch(roleId); - const channelDoc = await JoinToSystemChannel.findOne({ channelId }); + const channelId = voiceChannel.id; + const roleId = interaction.values[0]; + const targetRole = await interaction.guild.roles.fetch(roleId); + const channelDoc = await JoinToSystemChannel.findOne({ channelId }); - if (!channelDoc) { - await interaction.reply({ - content: 'This channel is not managed by the join-to-system.', - ephemeral: true, - }); - return; - } + if (!channelDoc) { + await interaction.reply({ + content: 'This channel is not managed by the join-to-system.', + ephemeral: true, + }); + return; + } - // Check if the target role has the Connect permission - const permissions = voiceChannel.permissionsFor(targetRole); - const hasAccess = permissions.has(PermissionFlagsBits.Connect); + // Check if the target role has the Connect permission + const permissions = voiceChannel.permissionsFor(targetRole); + const hasAccess = permissions.has(PermissionFlagsBits.Connect); - if (hasAccess) { - // Remove access - await voiceChannel.permissionOverwrites.edit(targetRole, { - Connect: false, - }); - channelDoc.allowedRoles = channelDoc.allowedRoles.filter( - (id) => id !== roleId - ); - await channelDoc.save(); + if (hasAccess) { + // Remove access + await voiceChannel.permissionOverwrites.edit(targetRole, { + Connect: false, + }); + channelDoc.allowedRoles = channelDoc.allowedRoles.filter( + (id) => id !== roleId + ); + await channelDoc.save(); - await interaction.reply({ - content: `Access removed from role: ${targetRole.name}.`, - ephemeral: true, - }); - } else { - // Grant access - await voiceChannel.permissionOverwrites.edit(targetRole, { - Connect: true, - }); - if (!channelDoc.allowedRoles.includes(roleId)) { - channelDoc.allowedRoles.push(roleId); - } - await channelDoc.save(); + await interaction.reply({ + content: `Access removed from role: ${targetRole.name}.`, + ephemeral: true, + }); + } else { + // Grant access + await voiceChannel.permissionOverwrites.edit(targetRole, { + Connect: true, + }); + if (!channelDoc.allowedRoles.includes(roleId)) { + channelDoc.allowedRoles.push(roleId); + } + await channelDoc.save(); - await interaction.reply({ - content: `Access granted to role: ${targetRole.name}.`, - ephemeral: true, - }); - } - } catch (error) { - console.error('Error in role_manage interaction:', error); - await interaction.reply({ - content: - 'An error occurred while processing your request. Please try again later.', - ephemeral: true, - }); + await interaction.reply({ + content: `Access granted to role: ${targetRole.name}.`, + ephemeral: true, + }); } - }, + } catch (error) { + console.error('Error in role_manage interaction:', error); + await interaction.reply({ + content: + 'An error occurred while processing your request. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/src/selects/shopSSM.js b/src/selects/shopSSM.js index 56732f4..4dcdbf7 100644 --- a/src/selects/shopSSM.js +++ b/src/selects/shopSSM.js @@ -3,132 +3,132 @@ import { Balance, Transaction, Item, Inventory } from '../schemas/economy.js'; import mconfig from '../config/messageConfig.js'; export default { - customId: 'shop', - botPermissions: [], + customId: 'shop', + botPermissions: [], - /** - * @param {Client} client - * @param {Interaction} interaction - */ - run: async (client, interaction) => { - try { - const userId = interaction.user.id; - const selectedItemId = interaction.values[0]; + /** + * @param {Client} client + * @param {Interaction} interaction + */ + run: async (client, interaction) => { + try { + const userId = interaction.user.id; + const selectedItemId = interaction.values[0]; - const [item, userBalance, userInventory] = await Promise.all([ - Item.findOne({ itemId: selectedItemId }), - Balance.findOneAndUpdate( - { userId }, - { $setOnInsert: { userId, balance: 0 } }, - { upsert: true, new: true } - ), - Inventory.findOneAndUpdate( - { userId }, - { $setOnInsert: { userId, items: [] } }, - { upsert: true, new: true } - ), - ]); + const [item, userBalance, userInventory] = await Promise.all([ + Item.findOne({ itemId: selectedItemId }), + Balance.findOneAndUpdate( + { userId }, + { $setOnInsert: { userId, balance: 0 } }, + { upsert: true, new: true } + ), + Inventory.findOneAndUpdate( + { userId }, + { $setOnInsert: { userId, items: [] } }, + { upsert: true, new: true } + ), + ]); - if (!item) { - return interaction.reply({ - content: 'Item not found.', - ephemeral: true, - }); - } + if (!item) { + return interaction.reply({ + content: 'Item not found.', + ephemeral: true, + }); + } - if (userBalance.balance < item.price) { - return interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Purchase Failed', - 'You do not have enough balance to buy this item.' - ), - ], - ephemeral: true, - }); - } + if (userBalance.balance < item.price) { + return interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Purchase Failed', + 'You do not have enough balance to buy this item.' + ), + ], + ephemeral: true, + }); + } - // Process the purchase - await processPurchase(userId, item, userBalance, userInventory); + // Process the purchase + await processPurchase(userId, item, userBalance, userInventory); - // Send success message - await interaction.reply({ - embeds: [createSuccessEmbed(interaction, item)], - ephemeral: true, - }); - } catch (error) { - console.error('Error processing shop command:', error); - await interaction.reply({ - embeds: [ - createErrorEmbed( - interaction, - 'Error', - 'There was an error processing your purchase.' - ), - ], - ephemeral: true, - }); - } - }, + // Send success message + await interaction.reply({ + embeds: [createSuccessEmbed(interaction, item)], + ephemeral: true, + }); + } catch (error) { + console.error('Error processing shop command:', error); + await interaction.reply({ + embeds: [ + createErrorEmbed( + interaction, + 'Error', + 'There was an error processing your purchase.' + ), + ], + ephemeral: true, + }); + } + }, }; async function processPurchase(userId, item, userBalance, userInventory) { - // Deduct the item price from the user's balance - userBalance.balance -= item.price; - await userBalance.save(); + // Deduct the item price from the user's balance + userBalance.balance -= item.price; + await userBalance.save(); - // Update the user's inventory - const inventoryItem = userInventory.items.find( - (i) => i.itemId === item.itemId - ); - if (inventoryItem) { - inventoryItem.quantity += 1; - } else { - userInventory.items.push({ itemId: item.itemId, quantity: 1 }); - } - await userInventory.save(); + // Update the user's inventory + const inventoryItem = userInventory.items.find( + (i) => i.itemId === item.itemId + ); + if (inventoryItem) { + inventoryItem.quantity += 1; + } else { + userInventory.items.push({ itemId: item.itemId, quantity: 1 }); + } + await userInventory.save(); - // Create a transaction record - const purchaseTransaction = new Transaction({ - userId: userId, - type: 'purchase', - amount: item.price, - description: `Purchased ${item.name}`, - }); - await purchaseTransaction.save(); + // Create a transaction record + const purchaseTransaction = new Transaction({ + userId: userId, + type: 'purchase', + amount: item.price, + description: `Purchased ${item.name}`, + }); + await purchaseTransaction.save(); } function createSuccessEmbed(interaction, item) { - return new EmbedBuilder() - .setColor(mconfig.embedColorSuccess) - .setTitle('🎉 Purchase Successful') - .setDescription( - `🛒 You have successfully bought **${item.name}** for **${item.price}** clienterr coins.` - ) - .addFields( - { name: '📦 Item Name', value: item.name, inline: true }, - { - name: '💰 Price', - value: `${item.price} clienterr coins`, - inline: true, - } - ) - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + return new EmbedBuilder() + .setColor(mconfig.embedColorSuccess) + .setTitle('🎉 Purchase Successful') + .setDescription( + `🛒 You have successfully bought **${item.name}** for **${item.price}** clienterr coins.` + ) + .addFields( + { name: '📦 Item Name', value: item.name, inline: true }, + { + name: '💰 Price', + value: `${item.price} clienterr coins`, + inline: true, + } + ) + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); } function createErrorEmbed(interaction, title, description) { - return new EmbedBuilder() - .setColor(mconfig.embedColorError) - .setTitle(`❌ ${title}`) - .setDescription(`⚠️ ${description}`) - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + return new EmbedBuilder() + .setColor(mconfig.embedColorError) + .setTitle(`❌ ${title}`) + .setDescription(`⚠️ ${description}`) + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); } diff --git a/src/utils/SlashCommandtoPrifix.js b/src/utils/SlashCommandtoPrifix.js index 0bb5ef0..837c642 100644 --- a/src/utils/SlashCommandtoPrifix.js +++ b/src/utils/SlashCommandtoPrifix.js @@ -1,218 +1,245 @@ import { Collection } from 'discord.js'; import loadCommands from './getLocalCommands.js'; +/** + * Converts slash commands to prefix commands. + * @returns {Collection} A collection of prefix commands. + */ export async function convertSlashCommandsToPrefix() { - const prefixCommands = new Collection(); - const slashCommands = await loadCommands(); - - slashCommands.forEach((slashCommand) => { - if (!slashCommand.prefix) return; - - const prefixCommand = { - ...slashCommand, - name: slashCommand.data.name, - description: slashCommand.data.description, - aliases: slashCommand.data.aliases || [], - run: async (client, message, args) => { - // Handle subcommands - const { subcommand, remainingArgs, error } = handleSubcommands( - slashCommand, - args, - message - ); - if (error) return; - - const mockOptions = {}; - const options = subcommand - ? slashCommand.data.options.find( - (opt) => opt.name === subcommand - )?.options || [] - : slashCommand.data.options || []; - - options.forEach((option, index) => { - const value = remainingArgs[index]; - mockOptions[option.name] = parseOptionValue( - option, - value, - message - ); - }); - - const mockInteraction = createMockInteraction( - message, - subcommand, - mockOptions - ); - - try { - await slashCommand.run(client, mockInteraction); - } catch (error) { - console.error( - `Error executing command ${slashCommand.data.name}:`, - error - ); - await message.reply( - 'An error occurred while executing the command. Please try again later.' - ); - } - }, - }; - - prefixCommands.set(prefixCommand.name, prefixCommand); - prefixCommand.aliases.forEach((alias) => - prefixCommands.set(alias, prefixCommand) - ); - }); + const prefixCommands = new Collection(); + const slashCommands = await loadCommands(); + + slashCommands.forEach((slashCommand) => { + if (!slashCommand.prefix) return; + + const prefixCommand = { + ...slashCommand, + name: slashCommand.data.name, + description: slashCommand.data.description, + aliases: slashCommand.data.aliases || [], + run: async (client, message, args) => { + // Handle subcommands + const { subcommand, remainingArgs, error } = handleSubcommands( + slashCommand, + args, + message + ); + if (error) return; + + const mockOptions = {}; + const options = subcommand + ? slashCommand.data.options.find((opt) => opt.name === subcommand) + ?.options || [] + : slashCommand.data.options || []; + + options.forEach((option, index) => { + const value = remainingArgs[index]; + mockOptions[option.name] = parseOptionValue(option, value, message); + }); + + const mockInteraction = createMockInteraction( + message, + subcommand, + mockOptions + ); + + try { + await slashCommand.run(client, mockInteraction); + } catch (error) { + console.error( + `Error executing command ${slashCommand.data.name}:`, + error + ); + await message.reply( + 'An error occurred while executing the command. Please try again later.' + ); + } + }, + }; + + prefixCommands.set(prefixCommand.name, prefixCommand); + prefixCommand.aliases.forEach((alias) => + prefixCommands.set(alias, prefixCommand) + ); + }); - return prefixCommands; + return prefixCommands; } +/** + * Handles subcommands for a given slash command. + * @param {Object} slashCommand - The slash command to handle subcommands for. + * @param {Array} args - The arguments passed with the command. + * @param {Object} message - The message object. + * @returns {Object} An object containing the subcommand, remaining arguments, and error status. + */ function handleSubcommands(slashCommand, args, message) { - if (!slashCommand.data.options?.some((opt) => opt.type === 1)) { - return { subcommand: null, remainingArgs: args }; - } - - const subcommandName = args[0]?.toLowerCase(); - const subcommands = slashCommand.data.options.filter( - (opt) => opt.type === 1 - ); - - if (!subcommandName) { - const subcommandList = subcommands - .map((sc) => `\`${sc.name}\``) - .join(', '); - message.reply( - `This command requires a subcommand. Available subcommands: ${subcommandList}` - ); - return { error: true }; - } - - const subcommand = subcommands.find( - (sc) => sc.name.toLowerCase() === subcommandName - ); - - if (!subcommand) { - const subcommandList = subcommands - .map((sc) => `\`${sc.name}\``) - .join(', '); - message.reply( - `Invalid subcommand \`${subcommandName}\`. Available subcommands: ${subcommandList}` - ); - return { error: true }; - } - - return { - subcommand: subcommand.name, - remainingArgs: args.slice(1), - }; + if (!slashCommand.data.options?.some((opt) => opt.type === 1)) { + return { subcommand: null, remainingArgs: args }; + } + + const subcommandName = args[0]?.toLowerCase(); + const subcommands = slashCommand.data.options.filter((opt) => opt.type === 1); + + if (!subcommandName) { + const subcommandList = subcommands.map((sc) => `\`${sc.name}\``).join(', '); + message.reply( + `This command requires a subcommand. Available subcommands: ${subcommandList}` + ); + return { error: true }; + } + + const subcommand = subcommands.find( + (sc) => sc.name.toLowerCase() === subcommandName + ); + + if (!subcommand) { + const subcommandList = subcommands.map((sc) => `\`${sc.name}\``).join(', '); + message.reply( + `Invalid subcommand \`${subcommandName}\`. Available subcommands: ${subcommandList}` + ); + return { error: true }; + } + + return { + subcommand: subcommand.name, + remainingArgs: args.slice(1), + }; } +/** + * Parses the value of an option based on its type. + * @param {Object} option - The option to parse the value for. + * @param {any} value - The value to parse. + * @param {Object} message - The message object. + * @returns {any} The parsed value. + */ function parseOptionValue(option, value, message) { - switch (option.type) { - case 3: // STRING - return parseStringOption(option, value); - case 4: // INTEGER - return parseInt(value) || null; - case 10: // NUMBER - return parseFloat(value) || null; - case 5: // BOOLEAN - return value?.toLowerCase() === 'true'; - case 6: // USER - return ( - message.mentions.users.first() || - message.guild.members.cache.get(value)?.user || - null - ); - case 7: // CHANNEL - return ( - message.mentions.channels.first() || - message.guild.channels.cache.get(value) || - null - ); - case 8: // ROLE - return ( - message.mentions.roles.first() || - message.guild.roles.cache.get(value) || - null - ); - case 9: // MENTIONABLE - return ( - message.mentions.members.first() || - message.mentions.roles.first() || - message.mentions.users.first() || - message.guild.members.cache.get(value) || - message.guild.roles.cache.get(value) || - null - ); - case 11: // ATTACHMENT - return message.attachments.first() || null; - default: - return null; - } + switch (option.type) { + case 3: // STRING + return parseStringOption(option, value); + case 4: // INTEGER + return parseInt(value) || null; + case 10: // NUMBER + return parseFloat(value) || null; + case 5: // BOOLEAN + return value?.toLowerCase() === 'true'; + case 6: // USER + return ( + message.mentions.users.first() || + message.guild.members.cache.get(value)?.user || + null + ); + case 7: // CHANNEL + return ( + message.mentions.channels.first() || + message.guild.channels.cache.get(value) || + null + ); + case 8: // ROLE + return ( + message.mentions.roles.first() || + message.guild.roles.cache.get(value) || + null + ); + case 9: // MENTIONABLE + return ( + message.mentions.members.first() || + message.mentions.roles.first() || + message.mentions.users.first() || + message.guild.members.cache.get(value) || + message.guild.roles.cache.get(value) || + null + ); + case 11: // ATTACHMENT + return message.attachments.first() || null; + default: + return null; + } } +/** + * Parses the value of a string option. + * @param {Object} option - The string option to parse the value for. + * @param {string} value - The value to parse. + * @returns {any} The parsed value. + */ function parseStringOption(option, value) { - if (option.choices) { - const validChoice = option.choices.find( - (choice) => - choice.name.toLowerCase() === value?.toLowerCase() || - choice.value.toLowerCase() === value?.toLowerCase() - ); - return validChoice ? validChoice.value : null; - } - return value || null; + if (option.choices) { + const validChoice = option.choices.find( + (choice) => + choice.name.toLowerCase() === value?.toLowerCase() || + choice.value.toLowerCase() === value?.toLowerCase() + ); + return validChoice ? validChoice.value : null; + } + return value || null; } +/** + * Creates a mock interaction for a given message, subcommand, and options. + * @param {Object} message - The message object. + * @param {string} subcommand - The subcommand. + * @param {Object} mockOptions - The mock options. + * @returns {Object} The mock interaction. + */ function createMockInteraction(message, subcommand, mockOptions) { - return { - reply: async (options) => handleReply(message, options), - deferReply: async () => message.channel.sendTyping(), - editReply: async (options) => handleReply(message, options, true), - followUp: async (options) => handleReply(message, options), - fetchReply: async () => message, - guild: message.guild, - channel: message.channel, - member: message.member, - user: message.author, - options: { - getSubcommand: () => subcommand, - getString: (name) => mockOptions[name], - getInteger: (name) => mockOptions[name], - getNumber: (name) => mockOptions[name], - getBoolean: (name) => mockOptions[name], - getUser: (name) => mockOptions[name], - getMember: (name) => - message.guild.members.cache.get(mockOptions[name]?.id), - getChannel: (name) => mockOptions[name], - getRole: (name) => mockOptions[name], - getMentionable: (name) => mockOptions[name], - getAttachment: (name) => mockOptions[name], - }, - }; + return { + reply: async (options) => handleReply(message, options), + deferReply: async () => message.channel.sendTyping(), + editReply: async (options) => handleReply(message, options, true), + followUp: async (options) => handleReply(message, options), + fetchReply: async () => message, + guild: message.guild, + channel: message.channel, + member: message.member, + user: message.author, + options: { + getSubcommand: () => subcommand, + getString: (name) => mockOptions[name], + getInteger: (name) => mockOptions[name], + getNumber: (name) => mockOptions[name], + getBoolean: (name) => mockOptions[name], + getUser: (name) => mockOptions[name], + getMember: (name) => + message.guild.members.cache.get(mockOptions[name]?.id), + getChannel: (name) => mockOptions[name], + getRole: (name) => mockOptions[name], + getMentionable: (name) => mockOptions[name], + getAttachment: (name) => mockOptions[name], + }, + }; } +/** + * Handles the reply for a given message and options. + * @param {Object} message - The message object. + * @param {Object} options - The options for the reply. + * @param {boolean} isEdit - Whether the reply is an edit or not. + * @returns {Promise} A promise that resolves when the reply is handled. + */ async function handleReply(message, options, isEdit = false) { - const content = options.content || ''; - const embeds = options.embeds || []; - const components = options.components || []; - - if (!content && embeds.length === 0 && components.length === 0) return; - - const replyOptions = { content, embeds, components }; - - if (options.ephemeral) { - return message.author.send(replyOptions); - } - - if (isEdit) { - if (message.lastBotReply && !message.lastBotReply.deleted) { - return message.lastBotReply.edit(replyOptions); - } else { - return message.channel.send(replyOptions); - } - } else { - const sentMessage = await message.reply(replyOptions); - message.lastBotReply = sentMessage; - return sentMessage; - } + const content = options.content || ''; + const embeds = options.embeds || []; + const components = options.components || []; + + if (!content && embeds.length === 0 && components.length === 0) return; + + const replyOptions = { content, embeds, components }; + + if (options.ephemeral) { + return message.author.send(replyOptions); + } + + if (isEdit) { + if (message.lastBotReply && !message.lastBotReply.deleted) { + return message.lastBotReply.edit(replyOptions); + } else { + return message.channel.send(replyOptions); + } + } else { + const sentMessage = await message.reply(replyOptions); + message.lastBotReply = sentMessage; + return sentMessage; + } } diff --git a/src/utils/buttonPagination.js b/src/utils/buttonPagination.js index 2bcc39a..68d85c7 100644 --- a/src/utils/buttonPagination.js +++ b/src/utils/buttonPagination.js @@ -1,261 +1,357 @@ import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ComponentType, - EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ComponentType, + EmbedBuilder, } from 'discord.js'; -// Utility function to create a button +/** + * Creates a button with enhanced features like hover effects and tooltips. + * @param {string} customId - The custom ID of the button. + * @param {string} emoji - The emoji to display on the button. + * @param {ButtonStyle} style - The style of the button. + * @param {boolean} [disabled=false] - Whether the button is disabled. + * @returns {ButtonBuilder} The created button. + */ const createButton = (customId, emoji, style, disabled = false) => { - return new ButtonBuilder() - .setCustomId(customId) - .setEmoji(emoji) - .setStyle(style) - .setDisabled(disabled); + return new ButtonBuilder() + .setCustomId(customId) + .setEmoji(emoji) + .setStyle(style) + .setDisabled(disabled) + .setLabel(customId.charAt(0).toUpperCase() + customId.slice(1)); }; -// Enhanced button state management -const updateButtons = (buttons, index, pagesLength) => { - buttons.forEach((button) => { - switch (button.data.custom_id) { - case 'first': - case 'prev': - button.setDisabled(index === 0); - break; - case 'next': - case 'last': - button.setDisabled(index === pagesLength - 1); - break; - } - }); +/** + * Updates the state of buttons based on the current index and total pages. + * @param {Array} buttons - The array of buttons to update. + * @param {number} index - The current index. + * @param {number} totalPages - The total number of pages. + */ +const updateButtons = (buttons, index, totalPages) => { + buttons.forEach((button) => { + switch (button.data.custom_id) { + case 'first': + button.setDisabled(index === 0); + break; + case 'prev': + button.setDisabled(index === 0); + break; + case 'next': + button.setDisabled(index === totalPages - 1); + break; + case 'last': + button.setDisabled(index === totalPages - 1); + break; + } + button.setStyle(ButtonStyle.Primary); // Ensure all buttons maintain Primary style + }); }; -// Animated transition helper -const animateTransition = async (msg, newEmbed) => { - const loadingEmbed = new EmbedBuilder() - .setDescription('Loading...') - .setColor('#FFA500'); +/** + * Animates the transition between pages with a progress indicator. + * @param {Message} msg - The message to edit. + * @param {EmbedBuilder} newEmbed - The new embed to display. + * @param {number} currentPage - The current page number. + * @param {number} totalPages - The total number of pages. + */ +const animateTransition = async (msg, newEmbed, currentPage, totalPages) => { + const loadingEmbed = new EmbedBuilder() + .setDescription('Loading...') + .setColor('#FFA500') + .setFooter({ text: `Changing page... ${currentPage}/${totalPages}` }); + + await msg.edit({ embeds: [loadingEmbed] }); + await new Promise((resolve) => setTimeout(resolve, 300)); - await msg.edit({ embeds: [loadingEmbed] }); - await new Promise((resolve) => setTimeout(resolve, 300)); - await msg.edit({ embeds: [newEmbed] }); -}; + const fadeEmbed = new EmbedBuilder(newEmbed.data) + .setColor('#FFFFFF') + .setFooter({ text: `Page ${currentPage}/${totalPages}` }); -// LRU Cache implementation for efficient embed caching -class EnhancedLRUCache { - constructor(capacity) { - this.capacity = capacity; - this.cache = new Map(); - } - - get(key) { - if (!this.cache.has(key)) return undefined; - const value = this.cache.get(key); - this.cache.delete(key); - this.cache.set(key, value); - return value; - } - - put(key, value) { - if (this.cache.size >= this.capacity) { - const oldestKey = this.cache.keys().next().value; - this.cache.delete(oldestKey); - } - this.cache.set(key, value); - } + await msg.edit({ embeds: [fadeEmbed] }); + await new Promise((resolve) => setTimeout(resolve, 200)); - prefetch(key, getValue) { - if (!this.cache.has(key)) { - const value = getValue(key); - this.put(key, value); - } - } + await msg.edit({ embeds: [newEmbed] }); +}; - clear() { - this.cache.clear(); - } +/** + * Represents an enhanced LRU Cache with improved analytics. + */ +class EnhancedLRUCache { + /** + * Creates a new EnhancedLRUCache instance. + * @param {number} capacity - The maximum size of the cache. + */ + constructor(capacity) { + this.capacity = capacity; + this.cache = new Map(); + this.hits = 0; + this.misses = 0; + this.totalAccesses = 0; + } + + /** + * Retrieves a value from the cache. + * @param {string} key - The key to retrieve. + * @returns {(any|undefined)} The value if found, otherwise undefined. + */ + get(key) { + this.totalAccesses++; + if (!this.cache.has(key)) { + this.misses++; + return undefined; + } + this.hits++; + const value = this.cache.get(key); + this.cache.delete(key); + this.cache.set(key, value); + return value; + } + + /** + * Adds a value to the cache. + * @param {string} key - The key to add. + * @param {any} value - The value to add. + */ + put(key, value) { + if (this.cache.size >= this.capacity) { + const oldestKey = this.cache.keys().next().value; + this.cache.delete(oldestKey); + } + this.cache.set(key, value); + } + + /** + * Prefetches a value into the cache if it's not already present. + * @param {string} key - The key to prefetch. + * @param {Function} getValue - A function to retrieve the value if not cached. + */ + prefetch(key, getValue) { + if (!this.cache.has(key)) { + const value = getValue(key); + this.put(key, value); + } + } + + /** + * Clears the cache. + */ + clear() { + this.cache.clear(); + this.hits = 0; + this.misses = 0; + this.totalAccesses = 0; + } + + /** + * Returns analytics about the cache. + * @returns {Object} An object containing cache analytics. + */ + getAnalytics() { + return { + hits: this.hits, + misses: this.misses, + hitRate: this.hits / this.totalAccesses || 0, + efficiency: this.hits / this.capacity || 0, + }; + } } /** - * Creates an advanced pagination system using buttons. - * @param {Interaction} interaction The interaction that triggered the pagination. - * @param {Array} pages An array of embeds to paginate. - * @param {Object} options Customization options + * Creates an advanced pagination system with enhanced user experience. + * @param {Interaction} interaction - The interaction that triggered the pagination. + * @param {Array} pages - An array of embeds to paginate. + * @param {Object} [options={}] - Customization options. */ export default async (interaction, pages, options = {}) => { - try { - if (!interaction) throw new Error('Invalid interaction'); - if (!pages || !Array.isArray(pages) || pages.length === 0) - throw new Error('Invalid pages array'); - - const defaultOptions = { - time: 5 * 60 * 1000, - buttonEmojis: { - first: '⏮️', - prev: '⬅️', - next: '➡️', - last: '⏭️', - }, - buttonStyles: { - first: ButtonStyle.Primary, - prev: ButtonStyle.Primary, - next: ButtonStyle.Primary, - last: ButtonStyle.Primary, - }, - animateTransitions: true, - cacheSize: 10, - }; - - const mergedOptions = { ...defaultOptions, ...options }; - - if (!interaction.deferred) await interaction.deferReply(); - - // Handle edge cases - if (pages.length === 0) { - return await interaction.editReply({ - content: 'No pages to display.', - components: [], - }); - } + try { + if (!interaction) throw new Error('Invalid interaction'); + if (!pages || !Array.isArray(pages) || pages.length === 0) + throw new Error('Invalid pages array'); + + const defaultOptions = { + time: 10 * 60 * 1000, + buttonEmojis: { + first: '⏮️', + prev: '⬅️', + next: '➡️', + last: '⏭️', + }, + buttonStyles: { + first: ButtonStyle.Primary, + prev: ButtonStyle.Primary, + next: ButtonStyle.Primary, + last: ButtonStyle.Primary, + }, + animateTransitions: true, + cacheSize: 15, + showPageIndicator: true, + allowUserNavigation: true, + }; + + const mergedOptions = { ...defaultOptions, ...options }; + + if (!interaction.deferred) await interaction.deferReply(); + + if (pages.length === 0) { + return await interaction.editReply({ + content: + 'No content available at the moment. Please check back later or contact support if this persists.', + components: [], + }); + } - if (pages.length === 1) { - return await interaction.editReply({ - embeds: pages, - components: [], - }); + if (pages.length === 1) { + return await interaction.editReply({ + embeds: pages, + components: [], + }); + } + + const buttons = [ + createButton( + 'first', + mergedOptions.buttonEmojis.first, + mergedOptions.buttonStyles.first + ), + createButton( + 'prev', + mergedOptions.buttonEmojis.prev, + mergedOptions.buttonStyles.prev + ), + createButton( + 'next', + mergedOptions.buttonEmojis.next, + mergedOptions.buttonStyles.next + ), + createButton( + 'last', + mergedOptions.buttonEmojis.last, + mergedOptions.buttonStyles.last + ), + ]; + + const row = new ActionRowBuilder().addComponents(buttons); + let index = 0; + + const embedCache = new EnhancedLRUCache(mergedOptions.cacheSize); + + const getEmbed = (index) => { + let embed = embedCache.get(index); + if (!embed) { + embed = new EmbedBuilder(pages[index].data); + if (mergedOptions.showPageIndicator) { + embed.setFooter({ + text: `Page ${index + 1} of ${pages.length} | Use buttons to navigate`, + }); + } + embedCache.put(index, embed); } - - const buttons = [ - createButton( - 'first', - mergedOptions.buttonEmojis.first, - mergedOptions.buttonStyles.first - ), - createButton( - 'prev', - mergedOptions.buttonEmojis.prev, - mergedOptions.buttonStyles.prev - ), - createButton( - 'next', - mergedOptions.buttonEmojis.next, - mergedOptions.buttonStyles.next - ), - createButton( - 'last', - mergedOptions.buttonEmojis.last, - mergedOptions.buttonStyles.last - ), + return embed; + }; + + const prefetchAdjacentPages = (currentIndex) => { + const prefetchIndexes = [ + currentIndex - 1, + currentIndex + 1, + currentIndex - 2, + currentIndex + 2, ]; + prefetchIndexes.forEach((idx) => { + if (idx >= 0 && idx < pages.length) { + embedCache.prefetch(idx, getEmbed); + } + }); + }; + + updateButtons(buttons, index, pages.length); + const msg = await interaction.editReply({ + embeds: [getEmbed(index)], + components: [row], + fetchReply: true, + }); + + const collector = msg.createMessageComponentCollector({ + componentType: ComponentType.Button, + time: mergedOptions.time, + }); + + let usageCount = 0; + + collector.on('collect', async (i) => { + if ( + !mergedOptions.allowUserNavigation && + i.user.id !== interaction.user.id + ) { + return i.reply({ + content: 'You are not authorized to navigate this content.', + ephemeral: true, + }); + } - const row = new ActionRowBuilder().addComponents(buttons); - let index = 0; - - const embedCache = new EnhancedLRUCache(mergedOptions.cacheSize); - - const getEmbed = (index) => { - let embed = embedCache.get(index); - if (!embed) { - embed = pages[index].setFooter({ - text: `Page ${index + 1} of ${pages.length}`, - }); - embedCache.put(index, embed); - } - return embed; - }; - - const prefetchAdjacentPages = (currentIndex) => { - const prefetchIndexes = [currentIndex - 1, currentIndex + 1]; - prefetchIndexes.forEach((idx) => { - if (idx >= 0 && idx < pages.length) { - embedCache.prefetch(idx, getEmbed); - } - }); - }; + await i.deferUpdate(); + usageCount++; + + const oldIndex = index; + switch (i.customId) { + case 'first': + index = 0; + break; + case 'prev': + index = Math.max(0, index - 1); + break; + case 'next': + index = Math.min(pages.length - 1, index + 1); + break; + case 'last': + index = pages.length - 1; + break; + } updateButtons(buttons, index, pages.length); - const msg = await interaction.editReply({ - embeds: [getEmbed(index)], - components: [row], - fetchReply: true, - }); - const collector = msg.createMessageComponentCollector({ - componentType: ComponentType.Button, - time: mergedOptions.time, + await msg.edit({ + embeds: [pages[index]], + components: [row], }); - let usageCount = 0; - - collector.on('collect', async (i) => { - if (i.user.id !== interaction.user.id) { - return i.reply({ - content: 'You are not allowed to interact with this pagination.', - ephemeral: true, - }); - } - - await i.deferUpdate(); - usageCount++; - - const oldIndex = index; - - switch (i.customId) { - case 'first': - index = 0; - break; - case 'prev': - index = Math.max(0, index - 1); - break; - case 'next': - index = Math.min(pages.length - 1, index + 1); - break; - case 'last': - index = pages.length - 1; - break; - } - - updateButtons(buttons, index, pages.length); - - if (mergedOptions.animateTransitions && oldIndex !== index) { - await animateTransition(msg, getEmbed(index)); - } else { - await msg.edit({ - embeds: [getEmbed(index)], - components: [row], - }); - } - prefetchAdjacentPages(index); - - collector.resetTimer(); - }); + collector.resetTimer(); + }); - collector.on('end', async () => { - buttons.forEach((button) => button.setDisabled(true)); - await msg - .edit({ - embeds: [getEmbed(index)], - components: [row], - }) - .catch(() => null); - - console.log(`Pagination ended. Total interactions: ${usageCount}`); - embedCache.clear(); + collector.on('end', async () => { + buttons.forEach((button) => button.setDisabled(true)); + const finalEmbed = new EmbedBuilder(getEmbed(index).data).setFooter({ + text: 'This pagination session has ended. Use the command again to start a new session.', }); - return msg; - } catch (err) { - console.error('Pagination error:', err); - if (!interaction.replied && !interaction.deferred) { - await interaction.reply({ - content: 'An error occurred while setting up pagination.', - ephemeral: true, - }); - } else { - await interaction.editReply({ - content: 'An error occurred while setting up pagination.', - }); - } - } + await msg + .edit({ + embeds: [finalEmbed], + components: [row], + }) + .catch(() => null); + + const analytics = embedCache.getAnalytics(); + console.log('Pagination Analytics:', analytics); + console.log(`Total interactions: ${usageCount}`); + embedCache.clear(); + }); + + return msg; + } catch (err) { + console.error('Pagination error:', err); + if (!interaction.replied && !interaction.deferred) { + await interaction.reply({ + content: + 'An unexpected error occurred while setting up the content. Please try again or contact support if the issue persists.', + ephemeral: true, + }); + } else { + await interaction.editReply({ + content: + 'An unexpected error occurred while displaying the content. Please try again or contact support if the issue persists.', + }); + } + } }; diff --git a/src/utils/commandComparing.js b/src/utils/commandComparing.js index fc2627c..a12ca2b 100644 --- a/src/utils/commandComparing.js +++ b/src/utils/commandComparing.js @@ -1,71 +1,71 @@ export default (existing, local) => { - const changed = (a, b) => JSON.stringify(a) !== JSON.stringify(b); + const changed = (a, b) => JSON.stringify(a) !== JSON.stringify(b); - // Check if the name or description has changed - if ( - changed(existing.name, local.data.name) || - changed( - existing.description || undefined, - local.data.description || undefined - ) - ) { - return true; - } + // Check if the name or description has changed + if ( + changed(existing.name, local.data.name) || + changed( + existing.description || undefined, + local.data.description || undefined + ) + ) { + return true; + } - const optionsChanged = changed( - optionsArray(existing), - optionsArray(local.data) - ); + const optionsChanged = changed( + optionsArray(existing), + optionsArray(local.data) + ); - return optionsChanged; + return optionsChanged; - function cleanObject(obj) { - for (const key in obj) { - if (typeof obj[key] === 'object') { - cleanObject(obj[key]); - if (!obj[key] || (Array.isArray(obj[key]) && !obj[key].length)) { - delete obj[key]; - } - } else if (obj[key] === undefined) { - delete obj[key]; - } + function cleanObject(obj) { + for (const key in obj) { + if (typeof obj[key] === 'object') { + cleanObject(obj[key]); + if (!obj[key] || (Array.isArray(obj[key]) && !obj[key].length)) { + delete obj[key]; + } + } else if (obj[key] === undefined) { + delete obj[key]; } - } + } + } - // normalize an object - function normalizeObject(input) { - if (Array.isArray(input)) { - return input.map((item) => normalizeObject(item)); - } + // normalize an object + function normalizeObject(input) { + if (Array.isArray(input)) { + return input.map((item) => normalizeObject(item)); + } + + return { + type: input.type, + name: input.name, + description: input.description, + options: input.options ? normalizeObject(input.options) : undefined, + required: input.required, + }; + } + // create a normalized options array + function optionsArray(cmd) { + return (cmd.options || []).map((option) => { + let cleanedOption = JSON.parse(JSON.stringify(option)); + cleanedOption.options + ? (cleanedOption.options = normalizeObject(cleanedOption.options)) + : (cleanedOption = normalizeObject(cleanedOption)); + cleanObject(cleanedOption); return { - type: input.type, - name: input.name, - description: input.description, - options: input.options ? normalizeObject(input.options) : undefined, - required: input.required, + ...cleanedOption, + choices: cleanedOption.choices + ? stringifyChoices(cleanedOption.choices) + : null, }; - } - - // create a normalized options array - function optionsArray(cmd) { - return (cmd.options || []).map((option) => { - let cleanedOption = JSON.parse(JSON.stringify(option)); - cleanedOption.options - ? (cleanedOption.options = normalizeObject(cleanedOption.options)) - : (cleanedOption = normalizeObject(cleanedOption)); - cleanObject(cleanedOption); - return { - ...cleanedOption, - choices: cleanedOption.choices - ? stringifyChoices(cleanedOption.choices) - : null, - }; - }); - } + }); + } - // Function to stringify choices array - function stringifyChoices(choices) { - return JSON.stringify(choices.map((c) => c.value)); - } + // Function to stringify choices array + function stringifyChoices(choices) { + return JSON.stringify(choices.map((c) => c.value)); + } }; diff --git a/src/utils/error/determineErrorCategory.js b/src/utils/error/determineErrorCategory.js index f61d9b6..6830c9d 100644 --- a/src/utils/error/determineErrorCategory.js +++ b/src/utils/error/determineErrorCategory.js @@ -1,272 +1,272 @@ import { DiscordAPIError } from 'discord.js'; export default function determineErrorCategory(error) { - if (error instanceof DiscordAPIError) { - switch (error.code) { - case 10001: - return 'Unknown Account'; - case 10002: - return 'Unknown Application'; - case 10003: - return 'Unknown Channel'; - case 10004: - return 'Unknown Guild'; - case 10005: - return 'Unknown Integration'; - case 10006: - return 'Unknown Invite'; - case 10007: - return 'Unknown Member'; - case 10008: - return 'Unknown Message'; - case 10009: - return 'Unknown Permission Overwrite'; - case 10010: - return 'Unknown Provider'; - case 10011: - return 'Unknown Role'; - case 10012: - return 'Unknown Token'; - case 10013: - return 'Unknown User'; - case 10014: - return 'Unknown Emoji'; - case 10015: - return 'Unknown Webhook'; - case 10026: - return 'Unknown Ban'; - case 10027: - return 'Unknown SKU'; - case 10028: - return 'Unknown Store Listing'; - case 10029: - return 'Unknown Entitlement'; - case 10030: - return 'Unknown Build'; - case 10031: - return 'Unknown Lobby'; - case 10032: - return 'Unknown Branch'; - case 10033: - return 'Unknown Redistributable'; - case 10036: - return 'Unknown Guild Template'; - case 10057: - return 'Unknown Guild Preview'; - case 10059: - return 'Unknown Ban Fetch'; - case 10060: - return 'Unknown SKU Fetch'; - case 10062: - return 'Unknown Store Listing Fetch'; - case 10063: - return 'Unknown Entitlement Fetch'; - case 10066: - return 'Unknown Build Fetch'; - case 10067: - return 'Unknown Lobby Fetch'; - case 10068: - return 'Unknown Branch Fetch'; - case 10069: - return 'Unknown Redistributable Fetch'; - case 20001: - return 'Bots cannot use this endpoint'; - case 20002: - return 'Only bots can use this endpoint'; - case 20009: - return 'Explicit Content Cannot Be Sent to the Desired Recipient(s)'; - case 20012: - return 'You are not authorized to perform this action on this application'; - case 20016: - return 'This action cannot be performed due to slowmode rate limit'; - case 20018: - return 'Only the owner of this account can perform this action'; - case 20022: - return 'This message cannot be edited due to announcement rate limits'; - case 20024: - return 'Under minimum age'; - case 20028: - return 'The channel you are writing has hit the write rate limit'; - case 20029: - return 'The write action you are performing on the server has hit the write rate limit'; - case 30001: - return 'Maximum number of guilds reached (100)'; - case 30002: - return 'Maximum number of friends reached (1000)'; - case 30003: - return 'Maximum number of pins reached for the channel (50)'; - case 30005: - return 'Maximum number of guild roles reached (250)'; - case 30007: - return 'Maximum number of webhooks reached (10)'; - case 30010: - return 'Maximum number of reactions reached (20)'; - case 30013: - return 'Maximum number of guild channels reached (500)'; - case 30015: - return 'Maximum number of attachments in a message reached (10)'; - case 30016: - return 'Maximum number of invites reached (1000)'; - case 30018: - return 'Maximum number of animated emojis reached'; - case 30019: - return 'Maximum number of server members reached'; - case 40001: - return 'Unauthorized'; - case 40002: - return 'You need to verify your account in order to perform this action'; - case 40003: - return 'You are opening direct messages too fast'; - case 40005: - return 'Request entity too large'; - case 40006: - return 'This feature has been temporarily disabled server-side'; - case 40007: - return 'The user is banned from this guild'; - case 40033: - return 'This message has already been crossposted'; - case 50001: - return 'Missing Access'; - case 50002: - return 'Invalid Account Type'; - case 50003: - return 'Cannot execute action on a DM channel'; - case 50004: - return 'Guild Widget Disabled'; - case 50005: - return 'Cannot edit a message authored by another user'; - case 50006: - return 'Cannot send an empty message'; - case 50007: - return 'Cannot send messages to this user'; - case 50008: - return 'Cannot send messages in a voice channel'; - case 50009: - return 'Channel verification level is too high for you to gain access'; - case 50010: - return 'OAuth2 application does not have a bot'; - case 50011: - return 'OAuth2 application limit reached'; - case 50012: - return 'Invalid OAuth2 State'; - case 50013: - return 'You lack permissions to perform that action'; - case 50014: - return 'Invalid authentication token provided'; - case 50015: - return 'Note was too long'; - case 50016: - return 'Provided too few or too many messages to delete. Must provide at least 2 and fewer than 100 messages to delete'; - case 50019: - return 'A message can only be pinned to the channel it was sent in'; - case 50020: - return 'Invite code was either invalid or taken'; - case 50021: - return 'Cannot execute action on a system message'; - case 50024: - return 'Cannot execute action on this channel type'; - case 50025: - return 'Invalid OAuth2 access token provided'; - case 50026: - return 'Missing required OAuth2 scope'; - case 50027: - return 'Invalid Webhook Token Provided'; - case 50028: - return 'Invalid role'; - case 50033: - return 'Invalid Recipient(s)'; - case 50034: - return 'A message provided was too old to bulk delete'; - case 50035: - return 'Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided'; - case 50036: - return "An invite was accepted to a guild the application's bot is not in"; - case 50041: - return 'Invalid API version provided'; - case 50045: - return 'File uploaded exceeds the maximum size'; - case 50046: - return 'Invalid file uploaded'; - case 50054: - return 'Cannot self-redeem this gift'; - case 50070: - return 'Payment source required to redeem gift'; - case 50074: - return 'Cannot delete a channel required for Community guilds'; - case 50080: - return 'Cannot edit stickers within a message'; - case 50081: - return 'Invalid sticker sent'; - case 50083: - return 'Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread'; - case 50084: - return 'Invalid thread notification settings'; - case 50085: - return 'before value is earlier than the thread creation date'; - case 50095: - return 'This server is not available in your location'; - case 50097: - return 'This server needs monetization enabled in order to perform this action'; - case 60003: - return 'Two factor is required for this operation'; - case 80004: - return 'No users with DiscordTag exist'; - case 90001: - return 'Reaction was blocked'; - case 110001: - return 'Application command with that name already exists'; - case 130000: - return 'API resource is currently overloaded. Try again a little later'; - case 150006: - return 'The Stage is already open'; - case 160002: - return 'Cannot reply without permission to read message history'; - case 160004: - return 'A thread has already been created for this message'; - case 160005: - return 'Thread is locked'; - case 160006: - return 'Maximum number of active threads reached'; - case 160007: - return 'Maximum number of active announcement threads reached'; - case 170001: - return 'Invalid JSON for uploaded Lottie file'; - case 170002: - return 'Uploaded Lotties cannot contain rasterized images such as PNG or JPEG'; - case 170003: - return 'Sticker maximum framerate exceeded'; - case 170004: - return 'Sticker frame count exceeds maximum of 1000 frames'; - case 170005: - return 'Lottie animation maximum dimensions exceeded'; - case 170006: - return 'Sticker frame rate is either too small or too large'; - case 170007: - return 'Sticker animation duration exceeds maximum of 5 seconds'; - default: - return 'Unknown Discord API Error'; - } - } - const message = error.message.toLowerCase(); - if (message.includes('api')) return 'Discord API Error'; - if (message.includes('permission')) return 'Permission Error'; - if (message.includes('rate limit')) return 'Rate Limit Error'; - if (message.includes('network')) return 'Network Error'; - if (message.includes('validation')) return 'Validation Error'; - if (message.includes('timeout')) return 'Timeout Error'; - if (message.includes('not found')) return 'Not Found Error'; - if (message.includes('database')) return 'Database Error'; - if (message.includes('auth') || message.includes('token')) - return 'Authentication Error'; - if (message.includes('connect') || message.includes('connection')) - return 'Connection Error'; - if (message.includes('parse') || message.includes('syntax')) - return 'Parsing Error'; - if (message.includes('memory')) return 'Memory Error'; - if (message.includes('disk') || message.includes('storage')) - return 'Disk Error'; - if (message.includes('timeout')) return 'Timeout Error'; - if (message.includes('format')) return 'Format Error'; - if (message.includes('range')) return 'Range Error'; - return 'Unknown Error'; + if (error instanceof DiscordAPIError) { + switch (error.code) { + case 10001: + return 'Unknown Account'; + case 10002: + return 'Unknown Application'; + case 10003: + return 'Unknown Channel'; + case 10004: + return 'Unknown Guild'; + case 10005: + return 'Unknown Integration'; + case 10006: + return 'Unknown Invite'; + case 10007: + return 'Unknown Member'; + case 10008: + return 'Unknown Message'; + case 10009: + return 'Unknown Permission Overwrite'; + case 10010: + return 'Unknown Provider'; + case 10011: + return 'Unknown Role'; + case 10012: + return 'Unknown Token'; + case 10013: + return 'Unknown User'; + case 10014: + return 'Unknown Emoji'; + case 10015: + return 'Unknown Webhook'; + case 10026: + return 'Unknown Ban'; + case 10027: + return 'Unknown SKU'; + case 10028: + return 'Unknown Store Listing'; + case 10029: + return 'Unknown Entitlement'; + case 10030: + return 'Unknown Build'; + case 10031: + return 'Unknown Lobby'; + case 10032: + return 'Unknown Branch'; + case 10033: + return 'Unknown Redistributable'; + case 10036: + return 'Unknown Guild Template'; + case 10057: + return 'Unknown Guild Preview'; + case 10059: + return 'Unknown Ban Fetch'; + case 10060: + return 'Unknown SKU Fetch'; + case 10062: + return 'Unknown Store Listing Fetch'; + case 10063: + return 'Unknown Entitlement Fetch'; + case 10066: + return 'Unknown Build Fetch'; + case 10067: + return 'Unknown Lobby Fetch'; + case 10068: + return 'Unknown Branch Fetch'; + case 10069: + return 'Unknown Redistributable Fetch'; + case 20001: + return 'Bots cannot use this endpoint'; + case 20002: + return 'Only bots can use this endpoint'; + case 20009: + return 'Explicit Content Cannot Be Sent to the Desired Recipient(s)'; + case 20012: + return 'You are not authorized to perform this action on this application'; + case 20016: + return 'This action cannot be performed due to slowmode rate limit'; + case 20018: + return 'Only the owner of this account can perform this action'; + case 20022: + return 'This message cannot be edited due to announcement rate limits'; + case 20024: + return 'Under minimum age'; + case 20028: + return 'The channel you are writing has hit the write rate limit'; + case 20029: + return 'The write action you are performing on the server has hit the write rate limit'; + case 30001: + return 'Maximum number of guilds reached (100)'; + case 30002: + return 'Maximum number of friends reached (1000)'; + case 30003: + return 'Maximum number of pins reached for the channel (50)'; + case 30005: + return 'Maximum number of guild roles reached (250)'; + case 30007: + return 'Maximum number of webhooks reached (10)'; + case 30010: + return 'Maximum number of reactions reached (20)'; + case 30013: + return 'Maximum number of guild channels reached (500)'; + case 30015: + return 'Maximum number of attachments in a message reached (10)'; + case 30016: + return 'Maximum number of invites reached (1000)'; + case 30018: + return 'Maximum number of animated emojis reached'; + case 30019: + return 'Maximum number of server members reached'; + case 40001: + return 'Unauthorized'; + case 40002: + return 'You need to verify your account in order to perform this action'; + case 40003: + return 'You are opening direct messages too fast'; + case 40005: + return 'Request entity too large'; + case 40006: + return 'This feature has been temporarily disabled server-side'; + case 40007: + return 'The user is banned from this guild'; + case 40033: + return 'This message has already been crossposted'; + case 50001: + return 'Missing Access'; + case 50002: + return 'Invalid Account Type'; + case 50003: + return 'Cannot execute action on a DM channel'; + case 50004: + return 'Guild Widget Disabled'; + case 50005: + return 'Cannot edit a message authored by another user'; + case 50006: + return 'Cannot send an empty message'; + case 50007: + return 'Cannot send messages to this user'; + case 50008: + return 'Cannot send messages in a voice channel'; + case 50009: + return 'Channel verification level is too high for you to gain access'; + case 50010: + return 'OAuth2 application does not have a bot'; + case 50011: + return 'OAuth2 application limit reached'; + case 50012: + return 'Invalid OAuth2 State'; + case 50013: + return 'You lack permissions to perform that action'; + case 50014: + return 'Invalid authentication token provided'; + case 50015: + return 'Note was too long'; + case 50016: + return 'Provided too few or too many messages to delete. Must provide at least 2 and fewer than 100 messages to delete'; + case 50019: + return 'A message can only be pinned to the channel it was sent in'; + case 50020: + return 'Invite code was either invalid or taken'; + case 50021: + return 'Cannot execute action on a system message'; + case 50024: + return 'Cannot execute action on this channel type'; + case 50025: + return 'Invalid OAuth2 access token provided'; + case 50026: + return 'Missing required OAuth2 scope'; + case 50027: + return 'Invalid Webhook Token Provided'; + case 50028: + return 'Invalid role'; + case 50033: + return 'Invalid Recipient(s)'; + case 50034: + return 'A message provided was too old to bulk delete'; + case 50035: + return 'Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided'; + case 50036: + return "An invite was accepted to a guild the application's bot is not in"; + case 50041: + return 'Invalid API version provided'; + case 50045: + return 'File uploaded exceeds the maximum size'; + case 50046: + return 'Invalid file uploaded'; + case 50054: + return 'Cannot self-redeem this gift'; + case 50070: + return 'Payment source required to redeem gift'; + case 50074: + return 'Cannot delete a channel required for Community guilds'; + case 50080: + return 'Cannot edit stickers within a message'; + case 50081: + return 'Invalid sticker sent'; + case 50083: + return 'Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread'; + case 50084: + return 'Invalid thread notification settings'; + case 50085: + return 'before value is earlier than the thread creation date'; + case 50095: + return 'This server is not available in your location'; + case 50097: + return 'This server needs monetization enabled in order to perform this action'; + case 60003: + return 'Two factor is required for this operation'; + case 80004: + return 'No users with DiscordTag exist'; + case 90001: + return 'Reaction was blocked'; + case 110001: + return 'Application command with that name already exists'; + case 130000: + return 'API resource is currently overloaded. Try again a little later'; + case 150006: + return 'The Stage is already open'; + case 160002: + return 'Cannot reply without permission to read message history'; + case 160004: + return 'A thread has already been created for this message'; + case 160005: + return 'Thread is locked'; + case 160006: + return 'Maximum number of active threads reached'; + case 160007: + return 'Maximum number of active announcement threads reached'; + case 170001: + return 'Invalid JSON for uploaded Lottie file'; + case 170002: + return 'Uploaded Lotties cannot contain rasterized images such as PNG or JPEG'; + case 170003: + return 'Sticker maximum framerate exceeded'; + case 170004: + return 'Sticker frame count exceeds maximum of 1000 frames'; + case 170005: + return 'Lottie animation maximum dimensions exceeded'; + case 170006: + return 'Sticker frame rate is either too small or too large'; + case 170007: + return 'Sticker animation duration exceeds maximum of 5 seconds'; + default: + return 'Unknown Discord API Error'; + } + } + const message = error.message.toLowerCase(); + if (message.includes('api')) return 'Discord API Error'; + if (message.includes('permission')) return 'Permission Error'; + if (message.includes('rate limit')) return 'Rate Limit Error'; + if (message.includes('network')) return 'Network Error'; + if (message.includes('validation')) return 'Validation Error'; + if (message.includes('timeout')) return 'Timeout Error'; + if (message.includes('not found')) return 'Not Found Error'; + if (message.includes('database')) return 'Database Error'; + if (message.includes('auth') || message.includes('token')) + return 'Authentication Error'; + if (message.includes('connect') || message.includes('connection')) + return 'Connection Error'; + if (message.includes('parse') || message.includes('syntax')) + return 'Parsing Error'; + if (message.includes('memory')) return 'Memory Error'; + if (message.includes('disk') || message.includes('storage')) + return 'Disk Error'; + if (message.includes('timeout')) return 'Timeout Error'; + if (message.includes('format')) return 'Format Error'; + if (message.includes('range')) return 'Range Error'; + return 'Unknown Error'; } diff --git a/src/utils/error/getRecoverySuggestions.js b/src/utils/error/getRecoverySuggestions.js index 9ca2847..bfde36f 100644 --- a/src/utils/error/getRecoverySuggestions.js +++ b/src/utils/error/getRecoverySuggestions.js @@ -1,189 +1,189 @@ import { DiscordAPIError } from 'discord.js'; export default function getRecoverySuggestions(error) { - if (error instanceof DiscordAPIError) { - switch (error.code) { - case 50001: - return 'Check bot permissions. Ensure the bot has the necessary permissions in the server and channel.'; - case 50013: - return "Bot is missing permissions. Review and update the bot's role permissions."; - case 50007: - return 'Cannot send messages to this user. The user may have DMs disabled or has blocked the bot.'; - case 10003: - return 'Unknown channel. Verify the channel ID and ensure the bot has access to it.'; - case 10004: - return 'Unknown guild. Check if the bot is still in the server or if the server ID is correct.'; - case 10008: - return 'Unknown message. The message may have been deleted or the ID is incorrect.'; - case 30001: - return 'Maximum number of guilds reached. Consider upgrading the bot or removing it from unused servers.'; - case 50035: - return 'Invalid form body. Check the request payload for any missing or incorrect parameters.'; - case 10001: - return 'Unknown account. Verify the account ID and ensure it exists.'; - case 10006: - return 'Unknown invite. Check if the invite code is correct and still valid.'; - case 50005: - return 'Cannot edit a message authored by another user. Ensure the bot only tries to edit its own messages.'; - case 50019: - return 'A message can only be pinned to the channel it was sent in. Verify the channel ID and message ID.'; - case 40001: - return 'Unauthorized. Ensure the bot token is valid and has not expired.'; - case 40002: - return 'You need to verify your account in order to perform this action. Check your email for a verification link from Discord.'; - case 10011: - return 'Unknown role. Ensure the role ID is correct and the role exists in the server.'; - case 10012: - return 'Unknown token. Verify the token and ensure it is still valid.'; - case 10014: - return 'Unknown webhook. Check if the webhook URL is correct and active.'; - case 20012: - return 'You are not authorized to perform this action on this application. Ensure you have the right permissions.'; - case 50008: - return 'Cannot send messages in a voice channel. Ensure the bot is trying to send messages in a text channel.'; - case 50033: - return 'Invalid recipients. Verify the recipient IDs and ensure they are correct.'; - case 50036: - return 'Invalid file uploaded. Check the file format and size.'; - case 60003: - return 'Two-factor authentication is required. Ensure 2FA is enabled for the account.'; - case 130000: - return 'API resource is currently overloaded. Try again later or reduce the load.'; - case 160002: - return "Cannot reply without permission to read message history. Ensure the bot has the 'Read Message History' permission."; - default: - return "Review the Discord API documentation for this error code and ensure your bot is compliant with Discord's terms of service."; - } - } - if (error instanceof Error) { - if (error.name === 'ReferenceError') { - return 'Reference error: Ensure that all variables and functions are defined before use.'; - } - if (error.name === 'TypeError') { - return 'Type error: Verify the data types of variables and function parameters.'; - } - if (error.name === 'SyntaxError') { - return 'Syntax error: Check for syntax errors in your code. Ensure all parentheses, brackets, and braces are properly closed.'; - } - if (error.name === 'RangeError') { - return 'Range error: Ensure values are within the permissible range. Check for infinite loops or excessive recursion.'; - } - if (error.name === 'EvalError') { - return 'Eval error: Avoid using `eval()` and ensure code being evaluated is correct.'; - } - if (error.name === 'URIError') { - return 'URI error: Check the encoding of URIs and ensure they are correctly formatted.'; - } - if (error.name === 'ValidationError') { - return 'Validation error: Check the input data for correctness and completeness.'; - } - if (error.name === 'DatabaseError') { - return 'Database error: Check your database connection and queries for issues.'; - } - if (error.name === 'AuthError') { - return 'Authorization error: Verify user permissions and authentication methods.'; - } - if (error.name === 'MongoNetworkError') { - return 'MongoDB network error: Check your MongoDB connection string and network settings.'; - } - } + if (error instanceof DiscordAPIError) { + switch (error.code) { + case 50001: + return 'Check bot permissions. Ensure the bot has the necessary permissions in the server and channel.'; + case 50013: + return "Bot is missing permissions. Review and update the bot's role permissions."; + case 50007: + return 'Cannot send messages to this user. The user may have DMs disabled or has blocked the bot.'; + case 10003: + return 'Unknown channel. Verify the channel ID and ensure the bot has access to it.'; + case 10004: + return 'Unknown guild. Check if the bot is still in the server or if the server ID is correct.'; + case 10008: + return 'Unknown message. The message may have been deleted or the ID is incorrect.'; + case 30001: + return 'Maximum number of guilds reached. Consider upgrading the bot or removing it from unused servers.'; + case 50035: + return 'Invalid form body. Check the request payload for any missing or incorrect parameters.'; + case 10001: + return 'Unknown account. Verify the account ID and ensure it exists.'; + case 10006: + return 'Unknown invite. Check if the invite code is correct and still valid.'; + case 50005: + return 'Cannot edit a message authored by another user. Ensure the bot only tries to edit its own messages.'; + case 50019: + return 'A message can only be pinned to the channel it was sent in. Verify the channel ID and message ID.'; + case 40001: + return 'Unauthorized. Ensure the bot token is valid and has not expired.'; + case 40002: + return 'You need to verify your account in order to perform this action. Check your email for a verification link from Discord.'; + case 10011: + return 'Unknown role. Ensure the role ID is correct and the role exists in the server.'; + case 10012: + return 'Unknown token. Verify the token and ensure it is still valid.'; + case 10014: + return 'Unknown webhook. Check if the webhook URL is correct and active.'; + case 20012: + return 'You are not authorized to perform this action on this application. Ensure you have the right permissions.'; + case 50008: + return 'Cannot send messages in a voice channel. Ensure the bot is trying to send messages in a text channel.'; + case 50033: + return 'Invalid recipients. Verify the recipient IDs and ensure they are correct.'; + case 50036: + return 'Invalid file uploaded. Check the file format and size.'; + case 60003: + return 'Two-factor authentication is required. Ensure 2FA is enabled for the account.'; + case 130000: + return 'API resource is currently overloaded. Try again later or reduce the load.'; + case 160002: + return "Cannot reply without permission to read message history. Ensure the bot has the 'Read Message History' permission."; + default: + return "Review the Discord API documentation for this error code and ensure your bot is compliant with Discord's terms of service."; + } + } + if (error instanceof Error) { + if (error.name === 'ReferenceError') { + return 'Reference error: Ensure that all variables and functions are defined before use.'; + } + if (error.name === 'TypeError') { + return 'Type error: Verify the data types of variables and function parameters.'; + } + if (error.name === 'SyntaxError') { + return 'Syntax error: Check for syntax errors in your code. Ensure all parentheses, brackets, and braces are properly closed.'; + } + if (error.name === 'RangeError') { + return 'Range error: Ensure values are within the permissible range. Check for infinite loops or excessive recursion.'; + } + if (error.name === 'EvalError') { + return 'Eval error: Avoid using `eval()` and ensure code being evaluated is correct.'; + } + if (error.name === 'URIError') { + return 'URI error: Check the encoding of URIs and ensure they are correctly formatted.'; + } + if (error.name === 'ValidationError') { + return 'Validation error: Check the input data for correctness and completeness.'; + } + if (error.name === 'DatabaseError') { + return 'Database error: Check your database connection and queries for issues.'; + } + if (error.name === 'AuthError') { + return 'Authorization error: Verify user permissions and authentication methods.'; + } + if (error.name === 'MongoNetworkError') { + return 'MongoDB network error: Check your MongoDB connection string and network settings.'; + } + } - if ( - error.response && - error.response.headers && - error.response.headers['x-ratelimit-reset'] - ) { - const retryAfter = - parseInt(error.response.headers['x-ratelimit-reset'], 10) * 1000; - return `Rate limit exceeded. Retry after ${retryAfter} milliseconds.`; - } + if ( + error.response && + error.response.headers && + error.response.headers['x-ratelimit-reset'] + ) { + const retryAfter = + parseInt(error.response.headers['x-ratelimit-reset'], 10) * 1000; + return `Rate limit exceeded. Retry after ${retryAfter} milliseconds.`; + } - if (error.code === 'LIMIT_FILE_SIZE') { - return 'File too large: Ensure the file size does not exceed the allowed limit.'; - } - if (error.code === 'LIMIT_FILE_TYPES') { - return 'Invalid file type: Ensure the file type is allowed.'; - } + if (error.code === 'LIMIT_FILE_SIZE') { + return 'File too large: Ensure the file size does not exceed the allowed limit.'; + } + if (error.code === 'LIMIT_FILE_TYPES') { + return 'Invalid file type: Ensure the file type is allowed.'; + } - if (error.response && error.response.status) { - switch (error.response.status) { - case 400: - return 'Bad Request: The request was invalid or cannot be served. Check the request parameters and ensure they are correctly formatted.'; - case 401: - return 'Unauthorized: The request requires authentication. Verify your bot token or user credentials.'; - case 403: - return 'Forbidden: The server understood the request but refuses to authorize it. Ensure your bot has the necessary permissions for this action.'; - case 404: - return 'Not Found: The requested resource could not be found. Double-check the URL, resource ID, or endpoint you are trying to access.'; - case 405: - return 'Method Not Allowed: The method specified in the request is not allowed for the resource. Verify you are using the correct HTTP method (GET, POST, PUT, DELETE, etc.).'; - case 408: - return 'Request Timeout: The server timed out waiting for the request. Try again and consider increasing your timeout settings if the issue persists.'; - case 413: - return 'Payload Too Large: The request is larger than the server is willing or able to process. Check the size of your request body or any files you are uploading.'; - case 414: - return 'URI Too Long: The URI provided was too long for the server to process. Try to reduce the length of your query parameters or use POST instead of GET for complex queries.'; - case 415: - return 'Unsupported Media Type: The server refuses to accept the request because the payload format is unsupported. Check your Content-Type header and request body format.'; - case 429: - return 'Too Many Requests: You have sent too many requests in a given amount of time. Implement rate limiting and honor the rate limit headers in the response.'; - case 431: - return 'Request Header Fields Too Large: The server is unwilling to process the request because its header fields are too large. Try to reduce the size of your request headers.'; - case 500: - return "Internal Server Error: The server encountered an unexpected condition that prevented it from fulfilling the request. This is a generic error message, usually generated by the server. Check Discord's status page or try again later."; - case 501: - return 'Not Implemented: The server does not support the functionality required to fulfill the request. Ensure you are using a supported API endpoint and method.'; - case 502: - return "Bad Gateway: The server received an invalid response from an upstream server. This could be a temporary issue with Discord's servers. Try again later."; - case 503: - return "Service Unavailable: The server is currently unable to handle the request due to temporary overloading or maintenance. Check Discord's status page and try again later."; - case 504: - return "Gateway Timeout: The server did not receive a timely response from an upstream server. This could be due to network issues or problems with Discord's servers. Try again later."; - case 507: - return "Insufficient Storage: The server is unable to store the representation needed to complete the request. Check if you're trying to upload or store data that exceeds Discord's limits."; - case 508: - return "Loop Detected: The server detected an infinite loop while processing the request. Check your bot's logic to ensure it's not caught in a redirect loop or similar issue."; - default: - return `Unexpected HTTP status code: ${error.response.status}. Check the Discord API documentation for more information on this status code.`; - } - } + if (error.response && error.response.status) { + switch (error.response.status) { + case 400: + return 'Bad Request: The request was invalid or cannot be served. Check the request parameters and ensure they are correctly formatted.'; + case 401: + return 'Unauthorized: The request requires authentication. Verify your bot token or user credentials.'; + case 403: + return 'Forbidden: The server understood the request but refuses to authorize it. Ensure your bot has the necessary permissions for this action.'; + case 404: + return 'Not Found: The requested resource could not be found. Double-check the URL, resource ID, or endpoint you are trying to access.'; + case 405: + return 'Method Not Allowed: The method specified in the request is not allowed for the resource. Verify you are using the correct HTTP method (GET, POST, PUT, DELETE, etc.).'; + case 408: + return 'Request Timeout: The server timed out waiting for the request. Try again and consider increasing your timeout settings if the issue persists.'; + case 413: + return 'Payload Too Large: The request is larger than the server is willing or able to process. Check the size of your request body or any files you are uploading.'; + case 414: + return 'URI Too Long: The URI provided was too long for the server to process. Try to reduce the length of your query parameters or use POST instead of GET for complex queries.'; + case 415: + return 'Unsupported Media Type: The server refuses to accept the request because the payload format is unsupported. Check your Content-Type header and request body format.'; + case 429: + return 'Too Many Requests: You have sent too many requests in a given amount of time. Implement rate limiting and honor the rate limit headers in the response.'; + case 431: + return 'Request Header Fields Too Large: The server is unwilling to process the request because its header fields are too large. Try to reduce the size of your request headers.'; + case 500: + return "Internal Server Error: The server encountered an unexpected condition that prevented it from fulfilling the request. This is a generic error message, usually generated by the server. Check Discord's status page or try again later."; + case 501: + return 'Not Implemented: The server does not support the functionality required to fulfill the request. Ensure you are using a supported API endpoint and method.'; + case 502: + return "Bad Gateway: The server received an invalid response from an upstream server. This could be a temporary issue with Discord's servers. Try again later."; + case 503: + return "Service Unavailable: The server is currently unable to handle the request due to temporary overloading or maintenance. Check Discord's status page and try again later."; + case 504: + return "Gateway Timeout: The server did not receive a timely response from an upstream server. This could be due to network issues or problems with Discord's servers. Try again later."; + case 507: + return "Insufficient Storage: The server is unable to store the representation needed to complete the request. Check if you're trying to upload or store data that exceeds Discord's limits."; + case 508: + return "Loop Detected: The server detected an infinite loop while processing the request. Check your bot's logic to ensure it's not caught in a redirect loop or similar issue."; + default: + return `Unexpected HTTP status code: ${error.response.status}. Check the Discord API documentation for more information on this status code.`; + } + } - if (typeof error.message === 'string') { - if (error.message.includes('rate limit')) { - return "Implement rate limiting in your bot to avoid hitting Discord's rate limits. Consider using a queue system for commands."; - } - if (error.message.includes('WebSocket')) { - return "Check your internet connection and Discord's status. If the issue persists, implement a reconnection strategy."; - } - if (error.message.includes('ECONNREFUSED')) { - return "Connection refused. Check your firewall settings and ensure Discord's servers are accessible."; - } - if (error.message.includes('timeout')) { - return 'Request timed out. Increase the timeout duration or ensure the server is responding promptly.'; - } - if (error.message.includes('ENOTFOUND')) { - return 'DNS lookup failed. Check your internet connection and DNS settings.'; - } - if (error.message.includes('ECONNRESET')) { - return 'Connection was reset. This may be due to network issues or server problems. Try reconnecting.'; - } - if (error.message.includes('EHOSTUNREACH')) { - return 'Host unreachable. Verify the server address and your network connection.'; - } - if (error.message.includes('ENETUNREACH')) { - return 'Network unreachable. Ensure your network is properly configured and connected.'; - } - if (error.message.includes('EAI_AGAIN')) { - return 'DNS lookup timeout. Try again later or check your DNS settings.'; - } - if (error.message.includes('Configuration Error')) { - return 'Configuration error: Ensure all configuration parameters are set correctly.'; - } - if (error.message.includes('Deployment Failed')) { - return 'Deployment error: Check deployment logs and ensure all dependencies are correctly installed.'; - } - } + if (typeof error.message === 'string') { + if (error.message.includes('rate limit')) { + return "Implement rate limiting in your bot to avoid hitting Discord's rate limits. Consider using a queue system for commands."; + } + if (error.message.includes('WebSocket')) { + return "Check your internet connection and Discord's status. If the issue persists, implement a reconnection strategy."; + } + if (error.message.includes('ECONNREFUSED')) { + return "Connection refused. Check your firewall settings and ensure Discord's servers are accessible."; + } + if (error.message.includes('timeout')) { + return 'Request timed out. Increase the timeout duration or ensure the server is responding promptly.'; + } + if (error.message.includes('ENOTFOUND')) { + return 'DNS lookup failed. Check your internet connection and DNS settings.'; + } + if (error.message.includes('ECONNRESET')) { + return 'Connection was reset. This may be due to network issues or server problems. Try reconnecting.'; + } + if (error.message.includes('EHOSTUNREACH')) { + return 'Host unreachable. Verify the server address and your network connection.'; + } + if (error.message.includes('ENETUNREACH')) { + return 'Network unreachable. Ensure your network is properly configured and connected.'; + } + if (error.message.includes('EAI_AGAIN')) { + return 'DNS lookup timeout. Try again later or check your DNS settings.'; + } + if (error.message.includes('Configuration Error')) { + return 'Configuration error: Ensure all configuration parameters are set correctly.'; + } + if (error.message.includes('Deployment Failed')) { + return 'Deployment error: Check deployment logs and ensure all dependencies are correctly installed.'; + } + } - // fallback message - return 'An unexpected error occurred. Review your code for potential issues. Consider adding more error handling and logging to identify the root cause.'; + // fallback message + return 'An unexpected error occurred. Review your code for potential issues. Consider adding more error handling and logging to identify the root cause.'; } diff --git a/src/utils/getAllFiles.js b/src/utils/getAllFiles.js index 5907f0d..a41993b 100644 --- a/src/utils/getAllFiles.js +++ b/src/utils/getAllFiles.js @@ -2,35 +2,41 @@ import fs from 'fs'; import path from 'path'; /** - * Get a list of files and/or directories within a specified directory. + * Retrieves a list of files and/or directories within a specified directory. + * This function recursively traverses the directory tree to gather all files and directories. * - * @param {string} directory - The directory to read. - * @param {boolean} [foldersOnly=false] - If true, only directories will be included in the result. - * @returns {string[]} - List of file or directory paths. + * @param {string} directory - The directory to read. This is the starting point for the file search. + * @param {boolean} [foldersOnly=false] - Optional parameter to filter the results. If set to true, only directories will be included in the result. + * @returns {string[]} - An array of strings representing the paths of files or directories found within the specified directory. */ const getAllFiles = (directory, foldersOnly = false) => { - const stack = [directory]; - const result = []; + const stack = [directory]; // Initialize a stack with the starting directory + const result = []; // Initialize an empty array to store the results - while (stack.length > 0) { - const currentPath = stack.pop(); - const items = fs.readdirSync(currentPath, { withFileTypes: true }); + while (stack.length > 0) { + // Continue until all directories have been processed + const currentPath = stack.pop(); // Pop the next directory from the stack + const items = fs.readdirSync(currentPath, { withFileTypes: true }); // Read the contents of the current directory - for (const item of items) { - const fullPath = path.join(currentPath, item.name); + for (const item of items) { + // Iterate through each item in the directory + const fullPath = path.join(currentPath, item.name); // Construct the full path of the item - if (item.isDirectory()) { - if (foldersOnly) { - result.push(fullPath); - } - stack.push(fullPath); - } else if (!foldersOnly && item.isFile()) { - result.push(fullPath); - } + if (item.isDirectory()) { + // If the item is a directory + if (foldersOnly) { + // If foldersOnly is true, add the directory to the result + result.push(fullPath); + } + stack.push(fullPath); // Add the directory to the stack to be processed + } else if (!foldersOnly && item.isFile()) { + // If the item is a file and foldersOnly is false, add the file to the result + result.push(fullPath); } - } + } + } - return result; + return result; // Return the array of file and directory paths }; export default getAllFiles; diff --git a/src/utils/getApplicationCommands.js b/src/utils/getApplicationCommands.js index ffd8f83..2bf6ba1 100644 --- a/src/utils/getApplicationCommands.js +++ b/src/utils/getApplicationCommands.js @@ -1,19 +1,27 @@ /** - * Fetches and caches the commands for a given client and guild ID. - * @param {Client} client - The Discord client instance. - * @param {string} [guildId] - The guild ID to fetch commands for. If not provided, fetches global commands. - * @returns {Promise} The fetched commands. + * Fetches and caches the application commands for a given client and guild ID. + * This function dynamically determines whether to fetch guild-specific or global commands based on the presence of a guild ID. + * + * @param {Client} client - The Discord client instance, which is used to interact with the Discord API. + * @param {string} [guildId] - The guild ID to fetch commands for. If not provided, the function fetches global commands. + * @returns {Promise} A promise that resolves to an array of fetched application commands. */ export default async (client, guildId) => { - let applicationCommands; - if (guildId) { - const guild = await client.guilds.fetch(guildId); - applicationCommands = guild.commands; - } else { - applicationCommands = await client.application.commands; - } + let applicationCommands; + // Determine whether to fetch guild-specific or global commands + if (guildId) { + // Fetch the guild instance for the given guild ID + const guild = await client.guilds.fetch(guildId); + // Get the commands manager for the guild + applicationCommands = guild.commands; + } else { + // Fetch the global commands for the client's application + applicationCommands = await client.application.commands; + } - await applicationCommands.fetch(); - return applicationCommands; + // Fetch the commands from the API and cache them + await applicationCommands.fetch(); + // Return the fetched and cached commands + return applicationCommands; }; diff --git a/src/utils/getButtons.js b/src/utils/getButtons.js index 8b8c1df..77dd844 100644 --- a/src/utils/getButtons.js +++ b/src/utils/getButtons.js @@ -2,41 +2,50 @@ import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import getAllFiles from './getAllFiles.js'; +/** + * This function dynamically imports and returns an array of button objects from files in the 'buttons' directory. + * It filters out any files that do not export a valid button object or are explicitly excluded. + * + * @param {Array} exceptions - An array of customId strings to exclude from the returned buttons. + * @returns {Promise>} A promise that resolves to an array of button objects. + */ const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default async (exceptions = []) => { - const buttons = []; - - // Get button files - const buttonFiles = getAllFiles(path.join(__dirname, '..', 'buttons')); - - // Import button file - for (const buttonFile of buttonFiles) { - const buttonFileURL = pathToFileURL(buttonFile).href; - - try { - const { default: buttonObject } = await import(buttonFileURL); - - // Check object is valid - if ( - !buttonObject || - typeof buttonObject !== 'object' || - !buttonObject.customId - ) { - console.warn( - `Skipped importing ${buttonFileURL} as it does not export a valid button object.` - ); - continue; - } - - // Skip - if (exceptions.includes(buttonObject.customId)) continue; - - buttons.push(buttonObject); - } catch (error) { - console.error(`Failed to import ${buttonFileURL}: ${error.message}`); + const buttons = []; + + // Retrieve all files in the 'buttons' directory + const buttonFiles = getAllFiles(path.join(__dirname, '..', 'buttons')); + + // Iterate through each button file + for (const buttonFile of buttonFiles) { + const buttonFileURL = pathToFileURL(buttonFile).href; + + try { + // Dynamically import the button file + const { default: buttonObject } = await import(buttonFileURL); + + // Validate the imported object + if ( + !buttonObject || + typeof buttonObject !== 'object' || + !buttonObject.customId + ) { + console.warn( + `Skipped importing ${buttonFileURL} as it does not export a valid button object.` + ); + continue; } - } - return buttons; + // Skip the button if its customId is in the exceptions array + if (exceptions.includes(buttonObject.customId)) continue; + + // Add the valid button object to the buttons array + buttons.push(buttonObject); + } catch (error) { + console.error(`Failed to import ${buttonFileURL}: ${error.message}`); + } + } + + return buttons; }; diff --git a/src/utils/getLocalCommands.js b/src/utils/getLocalCommands.js index b7fad26..ca72bd6 100644 --- a/src/utils/getLocalCommands.js +++ b/src/utils/getLocalCommands.js @@ -2,55 +2,64 @@ import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import getAllFiles from './getAllFiles.js'; +/** + * Retrieves the directory path of the current file. + * This is used to resolve relative paths within the project. + */ const __dirname = path.dirname(fileURLToPath(import.meta.url)); /** - * Imports a single command file and returns the command object if valid. - * @param {string} commandFile - Path to the command file. - * @param {string[]} exceptions - List of command names to exclude. - * @returns {Object|null} The command object or null if invalid. - * @throws {Error} If there's an error importing or processing the file. + * Dynamically imports a single command file and returns the command object if it is valid. + * This function checks if the command file is valid by ensuring it exports a default object with a 'name' property. + * It also checks if the command name is in the list of exceptions provided. + * + * @param {string} commandFile - The path to the command file. + * @param {string[]} exceptions - An array of command names to exclude from the import process. + * @returns {(Object|null)} The command object if valid, otherwise null. + * @throws {Error} Throws an error if there's a problem importing or processing the file, or if the command is in the exception list. */ async function importCommandFile(commandFile, exceptions) { - const commandFileURL = pathToFileURL(commandFile).href; - const commandModule = await import(commandFileURL); - const commandObject = commandModule.default; + const commandFileURL = pathToFileURL(commandFile).href; + const commandModule = await import(commandFileURL); + const commandObject = commandModule.default; - if (!commandObject?.data?.name) { - throw new Error(`Command file ${commandFile} is invalid.`); - } + // Validate the command file by checking if it exports a default object with a 'name' property. + if (!commandObject?.data?.name) { + throw new Error(`Command file ${commandFile} is invalid.`); + } - if (exceptions.includes(commandObject.data.name)) { - throw new Error( - `Command ${commandObject.data.name} is in the exception list.` - ); - } + // Check if the command name is in the list of exceptions provided. + if (exceptions.includes(commandObject.data.name)) { + throw new Error( + `Command ${commandObject.data.name} is in the exception list.` + ); + } - return commandObject; + return commandObject; } /** - * Loads all valid command files from the commands directory. - * @param {string[]} exceptions - List of command names to exclude. - * @returns {Promise} Array of valid command objects. + * Loads all valid command files from the 'commands' directory, excluding any files specified in the exceptions list. + * This function recursively retrieves all files in the 'commands' directory, filters out non-JavaScript files, and then imports each command file. + * + * @param {string[]} exceptions - An array of command names to exclude from the loading process. + * @returns {Promise} A promise that resolves to an array of valid command objects. */ export default async function loadCommands(exceptions = []) { - const commandsDir = path.resolve(__dirname, '..', 'commands'); - const allCommandFiles = getAllFiles(commandsDir).filter((file) => - file.endsWith('.js') - ); - - const commands = []; - for (const file of allCommandFiles) { - try { - const command = await importCommandFile(file, exceptions); - commands.push(command); - } catch (error) { - console.error( - `Error processing command file ${file}: ${error.message}` - ); - } - } - - return commands; + const commandsDir = path.resolve(__dirname, '..', 'commands'); + const allCommandFiles = getAllFiles(commandsDir).filter( + (file) => file.endsWith('.js') // Filter out files that do not end with '.js' + ); + + const commands = []; + for (const file of allCommandFiles) { + try { + const command = await importCommandFile(file, exceptions); + commands.push(command); + } catch (error) { + console.error(`Error processing command file ${file}: ${error.message}`); + } + } + + return commands; } diff --git a/src/utils/getLocalContextMenus.js b/src/utils/getLocalContextMenus.js index b6eb6c8..4b3e649 100644 --- a/src/utils/getLocalContextMenus.js +++ b/src/utils/getLocalContextMenus.js @@ -2,63 +2,85 @@ import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import getAllFiles from './getAllFiles.js'; +/** + * Retrieves the directory path of the current file. + * @returns {string} The directory path of the current file. + */ const __dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * Retrieves all local context menus excluding specified exceptions. + * + * This function recursively searches for all context menu files within the 'contextmenus' directory, + * imports and validates each file, and returns an array of valid context menus excluding those + * specified in the exceptions array. + * + * @param {Array} exceptions - An array of context menu names to exclude. + * @returns {Promise>} A promise that resolves to an array of valid context menus. + */ export default async (exceptions = []) => { - const localContextMenus = []; - const contextmenuCategories = getAllFiles( - path.join(__dirname, '..', 'contextmenus'), - true - ); + const localContextMenus = []; + const contextmenuCategories = getAllFiles( + path.join(__dirname, '..', 'contextmenus'), + true + ); - // Function to import and validate a single context menu file - const importAndValidateContextMenu = async (contextmenuFile) => { - try { - const contextmenuFileURL = pathToFileURL(contextmenuFile).href; - const { default: contextmenuModule } = await import( - contextmenuFileURL - ); + /** + * Imports and validates a single context menu file. + * + * This function attempts to import a context menu file, checks if the import is successful and + * if the imported module has a valid 'data' property with a 'name' property. If the file is valid + * and its name is not in the exceptions array, it returns the module. Otherwise, it logs a warning + * or error message and returns null. + * + * @param {string} contextmenuFile - The path to the context menu file. + * @returns {Promise} A promise that resolves to the validated context menu module or null. + */ + const importAndValidateContextMenu = async (contextmenuFile) => { + try { + const contextmenuFileURL = pathToFileURL(contextmenuFile).href; + const { default: contextmenuModule } = await import(contextmenuFileURL); - if ( - contextmenuModule && - contextmenuModule.data && - contextmenuModule.data.name && - !exceptions.includes(contextmenuModule.data.name) - ) { - return contextmenuModule; - } else { - console.warn( - `Context menu file ${contextmenuFile} does not have a valid export or name property.` - ); - return null; - } - } catch (error) { - console.error( - `Error importing context menu file ${contextmenuFile}: ${error}` - ); - return null; + if ( + contextmenuModule && + contextmenuModule.data && + contextmenuModule.data.name && + !exceptions.includes(contextmenuModule.data.name) + ) { + return contextmenuModule; + } else { + console.warn( + `Context menu file ${contextmenuFile} does not have a valid export or name property.` + ); + return null; } - }; + } catch (error) { + console.error( + `Error importing context menu file ${contextmenuFile}: ${error}` + ); + return null; + } + }; - // Process all context menu categories in parallel - const allContextMenuPromises = contextmenuCategories.map( - async (contextmenuCategory) => { - const contextmenuFiles = getAllFiles(contextmenuCategory); + // Process all context menu categories in parallel + const allContextMenuPromises = contextmenuCategories.map( + async (contextmenuCategory) => { + const contextmenuFiles = getAllFiles(contextmenuCategory); - // Import all files in the category in parallel - return Promise.all(contextmenuFiles.map(importAndValidateContextMenu)); - } - ); + // Import all files in the category in parallel + return Promise.all(contextmenuFiles.map(importAndValidateContextMenu)); + } + ); - // Wait for all context menu imports to complete - const allContextMenus = (await Promise.all(allContextMenuPromises)).flat(); + // Wait for all context menu imports to complete + const allContextMenus = (await Promise.all(allContextMenuPromises)).flat(); - // Filter out null values - allContextMenus.forEach((contextmenu) => { - if (contextmenu) { - localContextMenus.push(contextmenu); - } - }); + // Filter out null values + allContextMenus.forEach((contextmenu) => { + if (contextmenu) { + localContextMenus.push(contextmenu); + } + }); - return localContextMenus; + return localContextMenus; }; diff --git a/src/utils/getModals.js b/src/utils/getModals.js index a04ff59..d8b0137 100644 --- a/src/utils/getModals.js +++ b/src/utils/getModals.js @@ -2,39 +2,62 @@ import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import getAllFiles from './getAllFiles.js'; -// Get the directory name of the current module's file +/** + * Retrieves the directory path of the current file. + * This is used to resolve relative paths within the project. + */ const __dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * Dynamically imports and validates all modal files from the 'modals' directory, excluding specified exceptions. + * + * This function recursively searches for all modal files within the 'modals' directory, + * imports and validates each file, and returns an array of valid modal objects excluding those + * specified in the exceptions array. + * + * @param {Array} exceptions - An array of modal names to exclude. + * @returns {Promise>} A promise that resolves to an array of valid modal objects. + */ export default async (exceptions = []) => { - const modalFiles = getAllFiles(path.join(__dirname, '..', 'modals')); - - // Function to import and validate a single modal file - const importAndValidateModal = async (modalFile) => { - try { - // Convert the modal file path to a file URL - const modalFileURL = pathToFileURL(modalFile).href; - - // Dynamically import the module using the file URL - const { default: modalObject } = await import(modalFileURL); - - // Check if the modal name is in the exceptions list - if (exceptions.includes(modalObject.name)) return null; - - return modalObject; - } catch (error) { - console.error( - `Error importing modal file ${modalFile}: ${error.message}` - ); - return null; - } - }; - - // Import all modal files in parallel - const modalPromises = modalFiles.map(importAndValidateModal); - const modalObjects = await Promise.all(modalPromises); - - // Filter out any null values (failed imports or exceptions) - const modals = modalObjects.filter((modalObject) => modalObject !== null); - - return modals; + const modalFiles = getAllFiles(path.join(__dirname, '..', 'modals')); + + /** + * Imports and validates a single modal file. + * + * This function attempts to import a modal file, checks if the import is successful and + * if the imported module has a valid 'name' property. If the file is valid + * and its name is not in the exceptions array, it returns the module. Otherwise, it logs a warning + * or error message and returns null. + * + * @param {string} modalFile - The path to the modal file. + * @returns {Promise} A promise that resolves to the validated modal module or null. + */ + const importAndValidateModal = async (modalFile) => { + try { + // Convert the modal file path to a file URL + const modalFileURL = pathToFileURL(modalFile).href; + + // Dynamically import the module using the file URL + const { default: modalObject } = await import(modalFileURL); + + // Check if the modal name is in the exceptions list + if (exceptions.includes(modalObject.name)) return null; + + return modalObject; + } catch (error) { + console.error( + `Error importing modal file ${modalFile}: ${error.message}` + ); + return null; + } + }; + + // Import all modal files in parallel + const modalPromises = modalFiles.map(importAndValidateModal); + const modalObjects = await Promise.all(modalPromises); + + // Filter out any null values (failed imports or exceptions) + const modals = modalObjects.filter((modalObject) => modalObject !== null); + + return modals; }; diff --git a/src/utils/getSelects.js b/src/utils/getSelects.js index 23b6d3d..94521aa 100644 --- a/src/utils/getSelects.js +++ b/src/utils/getSelects.js @@ -2,40 +2,53 @@ import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import getAllFiles from './getAllFiles.js'; -// Get the directory name of the current module's file +/** + * Get the directory name of the current module's file + * @returns {string} The directory name of the current module's file + */ const __dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * Retrieves all select files, imports them, and validates them against a list of exceptions. + * + * @param {Array} exceptions - An array of custom IDs to exclude from the selection. + * @returns {Promise>} A promise that resolves to an array of validated select objects. + */ export default async (exceptions = []) => { - const selectsFiles = getAllFiles(path.join(__dirname, '..', 'selects')); - - // Function to import and validate a single select file - const importAndValidateSelect = async (selectsFile) => { - try { - // Convert the select file path to a file URL - const selectsFileURL = pathToFileURL(selectsFile).href; - - // Dynamically import the module using the file URL - const { default: selectObject } = await import(selectsFileURL); - - if (exceptions.includes(selectObject.customId)) return null; - - return selectObject; - } catch (error) { - console.error( - `Error importing select file ${selectsFile}: ${error.message}` - ); - return null; - } - }; - - // Import all select files in parallel - const selectPromises = selectsFiles.map(importAndValidateSelect); - const selectObjects = await Promise.all(selectPromises); - - // Filter out any null values (failed imports or exceptions) - const selects = selectObjects.filter( - (selectObject) => selectObject !== null - ); - - return selects; + const selectsFiles = getAllFiles(path.join(__dirname, '..', 'selects')); + + /** + * Imports and validates a single select file. + * + * @param {string} selectsFile - The path to the select file. + * @returns {Promise} A promise that resolves to the select object if valid, otherwise null. + */ + const importAndValidateSelect = async (selectsFile) => { + try { + // Convert the select file path to a file URL + const selectsFileURL = pathToFileURL(selectsFile).href; + + // Dynamically import the module using the file URL + const { default: selectObject } = await import(selectsFileURL); + + // Check if the select object's custom ID is in the exceptions list + if (exceptions.includes(selectObject.customId)) return null; + + return selectObject; + } catch (error) { + console.error( + `Error importing select file ${selectsFile}: ${error.message}` + ); + return null; + } + }; + + // Import all select files in parallel + const selectPromises = selectsFiles.map(importAndValidateSelect); + const selectObjects = await Promise.all(selectPromises); + + // Filter out any null values (failed imports or exceptions) + const selects = selectObjects.filter((selectObject) => selectObject !== null); + + return selects; }; diff --git a/src/utils/join-to-system/checkChannelOwnership.js b/src/utils/join-to-system/checkChannelOwnership.js index 5220dd7..6f1f50a 100644 --- a/src/utils/join-to-system/checkChannelOwnership.js +++ b/src/utils/join-to-system/checkChannelOwnership.js @@ -9,54 +9,54 @@ import JoinToSystemChannel from '../../schemas/joinToSystemSchema.js'; * @returns {Promise} - Returns an object with check results and channel info */ export async function comprehensiveVoiceCheck(userId, member) { - if (!member.voice.channel) { - return { - inVoice: false, - isManaged: false, - isOwner: false, - channel: null, - message: 'You need to be in a voice channel to use this command.', - }; - } - - const channelId = member.voice.channel.id; - - try { - const managedChannel = await JoinToSystemChannel.findOne({ - channelId: channelId, - }); + if (!member.voice.channel) { + return { + inVoice: false, + isManaged: false, + isOwner: false, + channel: null, + message: 'You need to be in a voice channel to use this command.', + }; + } - if (!managedChannel) { - return { - inVoice: true, - isManaged: false, - isOwner: false, - channel: member.voice.channel, - message: - 'This voice channel is not managed by the join-to-create system.', - }; - } + const channelId = member.voice.channel.id; - const isOwner = managedChannel.ownerId === userId; + try { + const managedChannel = await JoinToSystemChannel.findOne({ + channelId: channelId, + }); + if (!managedChannel) { return { - inVoice: true, - isManaged: true, - isOwner: isOwner, - channel: member.voice.channel, - managedChannel: managedChannel, - message: isOwner ? null : "You don't have ownership of this channel.", + inVoice: true, + isManaged: false, + isOwner: false, + channel: member.voice.channel, + message: + 'This voice channel is not managed by the join-to-create system.', }; - } catch (error) { - console.error('Error during voice channel checks:', error); - return { - inVoice: true, - isManaged: false, - isOwner: false, - channel: member.voice.channel, - message: 'An error occurred while checking channel ownership.', - }; - } + } + + const isOwner = managedChannel.ownerId === userId; + + return { + inVoice: true, + isManaged: true, + isOwner: isOwner, + channel: member.voice.channel, + managedChannel: managedChannel, + message: isOwner ? null : "You don't have ownership of this channel.", + }; + } catch (error) { + console.error('Error during voice channel checks:', error); + return { + inVoice: true, + isManaged: false, + isOwner: false, + channel: member.voice.channel, + message: 'An error occurred while checking channel ownership.', + }; + } } /** @@ -67,27 +67,27 @@ export async function comprehensiveVoiceCheck(userId, member) { * @returns {function} - Returns a middleware function */ export function requireVoiceChecks( - commandFunction, - options = { requireOwnership: true } + commandFunction, + options = { requireOwnership: true } ) { - return async (client, interaction) => { - const checkResult = await comprehensiveVoiceCheck( - interaction.user.id, - interaction.member - ); + return async (client, interaction) => { + const checkResult = await comprehensiveVoiceCheck( + interaction.user.id, + interaction.member + ); - if ( - !checkResult.inVoice || - !checkResult.isManaged || - (options.requireOwnership && !checkResult.isOwner) - ) { - return interaction.reply({ - content: checkResult.message, - ephemeral: true, - }); - } + if ( + !checkResult.inVoice || + !checkResult.isManaged || + (options.requireOwnership && !checkResult.isOwner) + ) { + return interaction.reply({ + content: checkResult.message, + ephemeral: true, + }); + } - // All checks passed, execute the command - return commandFunction(client, interaction, checkResult); - }; + // All checks passed, execute the command + return commandFunction(client, interaction, checkResult); + }; } diff --git a/src/utils/join-to-system/createVoiceChannel.js b/src/utils/join-to-system/createVoiceChannel.js index 96bd5bc..6b363ca 100644 --- a/src/utils/join-to-system/createVoiceChannel.js +++ b/src/utils/join-to-system/createVoiceChannel.js @@ -3,118 +3,115 @@ import JoinToSystemChannel from '../../schemas/joinToSystemSchema.js'; import JoinToSystem from '../../schemas/joinToSystemSetup.js'; async function createVoiceChannel(member, guild, client, options = {}) { - try { - const setup = await JoinToSystem.findOne({ guildId: guild.id }); - if (!setup) { - throw new Error('Join-to-Create system is not set up for this guild.'); - } + try { + const setup = await JoinToSystem.findOne({ guildId: guild.id }); + if (!setup) { + throw new Error('Join-to-Create system is not set up for this guild.'); + } - const { - nameTemplate = "${username}'s Channel", - userLimit = 0, - bitrate = 64000, - rtcRegion = null, - } = { ...setup.defaultChannelSettings, ...options }; + const { + nameTemplate = "${username}'s Channel", + userLimit = 0, + bitrate = 64000, + rtcRegion = null, + } = { ...setup.defaultChannelSettings, ...options }; - const channelName = nameTemplate.replace( - /\${username}/g, - member.user.username - ); + const channelName = nameTemplate.replace( + /\${username}/g, + member.user.username + ); - const newChannel = await guild.channels.create({ - name: channelName, - type: ChannelType.GuildVoice, - parent: setup.categoryId, - userLimit, - bitrate, - rtcRegion, - permissionOverwrites: [ - { - id: guild.id, - allow: [PermissionFlagsBits.Connect], - }, - { - id: member.id, - allow: [ - PermissionFlagsBits.Connect, - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.MuteMembers, - PermissionFlagsBits.DeafenMembers, - PermissionFlagsBits.MoveMembers, - ], - }, - ], - }); + const newChannel = await guild.channels.create({ + name: channelName, + type: ChannelType.GuildVoice, + parent: setup.categoryId, + userLimit, + bitrate, + rtcRegion, + permissionOverwrites: [ + { + id: guild.id, + allow: [PermissionFlagsBits.Connect], + }, + { + id: member.id, + allow: [ + PermissionFlagsBits.Connect, + PermissionFlagsBits.ManageChannels, + PermissionFlagsBits.MuteMembers, + PermissionFlagsBits.DeafenMembers, + PermissionFlagsBits.MoveMembers, + ], + }, + ], + }); - // Check if a channel with this ID already exists - let channelDoc = await JoinToSystemChannel.findOne({ - channelId: newChannel.id, - }); + // Check if a channel with this ID already exists + let channelDoc = await JoinToSystemChannel.findOne({ + channelId: newChannel.id, + }); - if (channelDoc) { - // If it exists, update it - channelDoc.name = channelName; - channelDoc.ownerId = member.id; - channelDoc.userLimit = userLimit; - channelDoc.bitrate = bitrate; - channelDoc.rtcRegion = rtcRegion; - } else { - // If it doesn't exist, create a new one - channelDoc = new JoinToSystemChannel({ - guildId: guild.id, - channelId: newChannel.id, - ownerId: member.id, - name: channelName, - userLimit, - bitrate, - rtcRegion, - }); - } + if (channelDoc) { + // If it exists, update it + channelDoc.name = channelName; + channelDoc.ownerId = member.id; + channelDoc.userLimit = userLimit; + channelDoc.bitrate = bitrate; + channelDoc.rtcRegion = rtcRegion; + } else { + // If it doesn't exist, create a new one + channelDoc = new JoinToSystemChannel({ + guildId: guild.id, + channelId: newChannel.id, + ownerId: member.id, + name: channelName, + userLimit, + bitrate, + rtcRegion, + }); + } - await channelDoc.save(); + await channelDoc.save(); - if (member.voice.channel) { - await member.voice.setChannel(newChannel); - } + if (member.voice.channel) { + await member.voice.setChannel(newChannel); + } - return newChannel; - } catch (error) { - console.error('Error creating voice channel:', error); + return newChannel; + } catch (error) { + console.error('Error creating voice channel:', error); - if (error.code === 11000) { - // Duplicate key error, try updating the existing document - try { - const existingChannelDoc = await JoinToSystemChannel.findOne({ - channelId: error.keyValue.channelId, - }); + if (error.code === 11000) { + // Duplicate key error, try updating the existing document + try { + const existingChannelDoc = await JoinToSystemChannel.findOne({ + channelId: error.keyValue.channelId, + }); - if (existingChannelDoc) { - existingChannelDoc.name = `${member.user.username}'s Channel`; - existingChannelDoc.ownerId = member.id; - existingChannelDoc.userLimit = options.userLimit || 0; - existingChannelDoc.bitrate = options.bitrate || 64000; - existingChannelDoc.rtcRegion = options.rtcRegion || null; + if (existingChannelDoc) { + existingChannelDoc.name = `${member.user.username}'s Channel`; + existingChannelDoc.ownerId = member.id; + existingChannelDoc.userLimit = options.userLimit || 0; + existingChannelDoc.bitrate = options.bitrate || 64000; + existingChannelDoc.rtcRegion = options.rtcRegion || null; - await existingChannelDoc.save(); - console.log( - 'Updated existing channel document due to duplicate key error.' - ); + await existingChannelDoc.save(); + console.log( + 'Updated existing channel document due to duplicate key error.' + ); - if (member.voice.channel) { - await member.voice.setChannel(existingChannelDoc.channelId); - } + if (member.voice.channel) { + await member.voice.setChannel(existingChannelDoc.channelId); + } - return existingChannelDoc.channelId; - } - } catch (updateError) { - console.error( - 'Error updating existing channel document:', - updateError - ); - } + return existingChannelDoc.channelId; + } + } catch (updateError) { + console.error('Error updating existing channel document:', updateError); } - throw error; - } + } + throw error; + } } export default createVoiceChannel; diff --git a/src/utils/join-to-system/deleteVoiceChannel.js b/src/utils/join-to-system/deleteVoiceChannel.js index dc973ad..1a8c7bf 100644 --- a/src/utils/join-to-system/deleteVoiceChannel.js +++ b/src/utils/join-to-system/deleteVoiceChannel.js @@ -2,47 +2,45 @@ import { PermissionFlagsBits } from 'discord.js'; import JoinToSystemChannel from '../../schemas/joinToSystemChannel.js'; async function deleteVoiceChannel(channel, user) { - try { - // Fetch the channel document from the database - const channelDoc = await JoinToSystemChannel.findOne({ - channelId: channel.id, - }); - - if (!channelDoc) { - throw new Error( - 'This channel is not part of the Join-to-Create system.' - ); - } - - // Check if the user is the owner or has admin permissions - const isOwner = channelDoc.ownerId === user.id; - const hasAdminPermission = - channel.permissionsFor(user)?.has(PermissionFlagsBits.Administrator) ?? - false; - - if (!isOwner && !hasAdminPermission) { - throw new Error('You do not have permission to delete this channel.'); - } - - // Delete the channel - await channel.delete(); - - // Remove the channel document from the database - const result = await JoinToSystemChannel.deleteOne({ - channelId: channel.id, - }); - - if (result.deletedCount === 0) { - console.warn( - `Channel document for ${channel.id} was not found in the database during deletion.` - ); - } - - return true; - } catch (error) { - console.error('Error deleting voice channel:', error); - throw error; - } + try { + // Fetch the channel document from the database + const channelDoc = await JoinToSystemChannel.findOne({ + channelId: channel.id, + }); + + if (!channelDoc) { + throw new Error('This channel is not part of the Join-to-Create system.'); + } + + // Check if the user is the owner or has admin permissions + const isOwner = channelDoc.ownerId === user.id; + const hasAdminPermission = + channel.permissionsFor(user)?.has(PermissionFlagsBits.Administrator) ?? + false; + + if (!isOwner && !hasAdminPermission) { + throw new Error('You do not have permission to delete this channel.'); + } + + // Delete the channel + await channel.delete(); + + // Remove the channel document from the database + const result = await JoinToSystemChannel.deleteOne({ + channelId: channel.id, + }); + + if (result.deletedCount === 0) { + console.warn( + `Channel document for ${channel.id} was not found in the database during deletion.` + ); + } + + return true; + } catch (error) { + console.error('Error deleting voice channel:', error); + throw error; + } } export default deleteVoiceChannel; diff --git a/src/utils/pluginSystem.js b/src/utils/pluginSystem.js new file mode 100644 index 0000000..d4f0bf2 --- /dev/null +++ b/src/utils/pluginSystem.js @@ -0,0 +1,13 @@ +import fs from 'fs/promises'; +import path from 'path'; + +class pluginSystem { + constructor(client) { + this.client = client; + this.plugin = new Map(); + this.pluginDir = path.join(process.cwd(), '..', 'plugins'); + } + getPlugin(name) { + return this.plugin.get(name); + } +} diff --git a/src/utils/ticket/ticketClose.js b/src/utils/ticket/ticketClose.js index 71fb302..b663504 100644 --- a/src/utils/ticket/ticketClose.js +++ b/src/utils/ticket/ticketClose.js @@ -1,10 +1,10 @@ // src/utils/ticket/ticketClose.js import { - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, } from 'discord.js'; import ticketSchema from '../../schemas/ticketSchema.js'; import ticketSetupSchema from '../../schemas/ticketSetupSchema.js'; @@ -12,223 +12,216 @@ import axios from 'axios'; import dht from 'discord-html-transcripts'; export async function closeTicket(client, guild, channel, member, reason) { - try { - const ticket = await ticketSchema.findOne({ - ticketChannelID: channel.id, - }); - - if (!ticket) return { success: false, message: 'Ticket not found.' }; - - ticket.status = 'closed'; - ticket.closedBy = member.id; - ticket.reason = reason; - ticket.actionLog.push( - `Ticket closed by ${member.user.tag} at ${new Date().toISOString()}: ${reason}` - ); - await ticket.save(); - - const setupTicket = await ticketSetupSchema.findOne({ - guildID: guild.id, - }); - - if (!setupTicket) - return { success: false, message: 'Ticket setup not found.' }; - - const logChannel = guild.channels.cache.get(setupTicket.logChannelID); - - // Generate the transcript - const transcript = await dht.createTranscript(channel, { - returnType: 'buffer', - poweredBy: false, - }); - - const transcriptURL = `https://transcript.clienterr.com/api/transcript/${channel.id}`; - - if (logChannel) { - const logEmbed = new EmbedBuilder() - .setTitle('Ticket Close') - .setColor('Red') - .addFields( - { - name: '📝 Subject', - value: ticket.subject || 'No subject provided', - }, - { - name: '🗒️ Description', - value: ticket.description || 'No description provided', - }, - { - name: '🆔 Ticket ID', - value: ticket.ticketChannelID.toString(), - inline: true, - }, - { - name: '👤 Opened By', - value: `<@${ticket.ticketMemberID}>`, - inline: true, - }, - { - name: '🔒 Closed By', - value: ticket.closedBy - ? `<@${ticket.closedBy}>` - : 'Not closed', - inline: true, - }, - { - name: '📅 Open Time', - value: ``, - inline: true, - }, - { - name: '📆 Close Time', - value: ``, - inline: true, - }, - { - name: '🔖 Claimed By', - value: ticket.claimedBy - ? `<@${ticket.claimedBy}>` - : 'Not claimed', - inline: true, - }, - { - name: '📝 Reason', - value: reason || 'No reason specified', - inline: false, - } - ) - .setTimestamp(); - - const transcriptButton = new ButtonBuilder() - .setLabel('View Transcript') - .setStyle(ButtonStyle.Link) - .setURL(transcriptURL) - .setEmoji('<:website:1162289689290620929>'); - - const row = new ActionRowBuilder().addComponents(transcriptButton); - - await logChannel.send({ embeds: [logEmbed], components: [row] }); + try { + const ticket = await ticketSchema.findOne({ + ticketChannelID: channel.id, + }); + + if (!ticket) return { success: false, message: 'Ticket not found.' }; + + ticket.status = 'closed'; + ticket.closedBy = member.id; + ticket.reason = reason; + ticket.actionLog.push( + `Ticket closed by ${member.user.tag} at ${new Date().toISOString()}: ${reason}` + ); + await ticket.save(); + + const setupTicket = await ticketSetupSchema.findOne({ + guildID: guild.id, + }); + + if (!setupTicket) + return { success: false, message: 'Ticket setup not found.' }; + + const logChannel = guild.channels.cache.get(setupTicket.logChannelID); + + // Generate the transcript + const transcript = await dht.createTranscript(channel, { + returnType: 'buffer', + poweredBy: false, + }); + + const transcriptURL = `https://transcript.clienterr.com/api/transcript/${channel.id}`; + + if (logChannel) { + const logEmbed = new EmbedBuilder() + .setTitle('Ticket Close') + .setColor('Red') + .addFields( + { + name: '📝 Subject', + value: ticket.subject || 'No subject provided', + }, + { + name: '🗒️ Description', + value: ticket.description || 'No description provided', + }, + { + name: '🆔 Ticket ID', + value: ticket.ticketChannelID.toString(), + inline: true, + }, + { + name: '👤 Opened By', + value: `<@${ticket.ticketMemberID}>`, + inline: true, + }, + { + name: '🔒 Closed By', + value: ticket.closedBy ? `<@${ticket.closedBy}>` : 'Not closed', + inline: true, + }, + { + name: '📅 Open Time', + value: ``, + inline: true, + }, + { + name: '📆 Close Time', + value: ``, + inline: true, + }, + { + name: '🔖 Claimed By', + value: ticket.claimedBy ? `<@${ticket.claimedBy}>` : 'Not claimed', + inline: true, + }, + { + name: '📝 Reason', + value: reason || 'No reason specified', + inline: false, + } + ) + .setTimestamp(); + + const transcriptButton = new ButtonBuilder() + .setLabel('View Transcript') + .setStyle(ButtonStyle.Link) + .setURL(transcriptURL) + .setEmoji('<:website:1162289689290620929>'); + + const row = new ActionRowBuilder().addComponents(transcriptButton); + + await logChannel.send({ embeds: [logEmbed], components: [row] }); + } + + const ticketMember = await guild.members.fetch(ticket.ticketMemberID); + if (ticketMember) { + const userDM = await ticketMember.createDM(); + + const dmEmbed = new EmbedBuilder() + .setTitle('Ticket Closed') + .setColor('#FF5555') // A soft red color + .setDescription(`Your ticket in ${guild.name} has been closed.`) + .addFields( + { + name: '📝 Subject', + value: ticket.subject || 'No subject provided', + }, + { + name: '🗒️ Description', + value: ticket.description || 'No description provided', + }, + { + name: '🆔 Ticket ID', + value: ticket.ticketChannelID.toString(), + inline: true, + }, + { + name: '🔒 Closed By', + value: ticket.closedBy ? `<@${ticket.closedBy}>` : 'Not closed', + inline: true, + }, + { name: '📝 Reason', value: reason || 'No reason specified' }, + { + name: '📜 Transcript', + value: transcriptURL + ? `[Click here to view](${transcriptURL})` + : 'No transcript available', + } + ) + .setFooter({ text: 'Thank you for using our ticket system!' }) + .setTimestamp(); + + try { + await userDM.send({ + content: "Here's a summary of your closed ticket:", + embeds: [dmEmbed], + }); + } catch (error) { + throw error; } - - const ticketMember = await guild.members.fetch(ticket.ticketMemberID); - if (ticketMember) { - const userDM = await ticketMember.createDM(); - - const dmEmbed = new EmbedBuilder() - .setTitle('Ticket Closed') - .setColor('#FF5555') // A soft red color - .setDescription(`Your ticket in ${guild.name} has been closed.`) - .addFields( - { - name: '📝 Subject', - value: ticket.subject || 'No subject provided', - }, - { - name: '🗒️ Description', - value: ticket.description || 'No description provided', - }, - { - name: '🆔 Ticket ID', - value: ticket.ticketChannelID.toString(), - inline: true, - }, - { - name: '🔒 Closed By', - value: ticket.closedBy - ? `<@${ticket.closedBy}>` - : 'Not closed', - inline: true, - }, - { name: '📝 Reason', value: reason || 'No reason specified' }, - { - name: '📜 Transcript', - value: transcriptURL - ? `[Click here to view](${transcriptURL})` - : 'No transcript available', - } - ) - .setFooter({ text: 'Thank you for using our ticket system!' }) - .setTimestamp(); - - try { - await userDM.send({ - content: "Here's a summary of your closed ticket:", - embeds: [dmEmbed], - }); - } catch (error) { - throw error; - } + } + + await uploadTranscriptToGitHub(channel.id, transcript); + + const staffRole = guild.roles.cache.get(setupTicket.staffRoleID); + if (staffRole && ticketMember) { + const hasRole = ticketMember.roles.cache.has(staffRole.id); + if (!hasRole) { + for (const memberID of ticket.membersAdded) { + const addedMember = guild.members.cache.get(memberID); + if (addedMember) + await channel.permissionOverwrites.delete(addedMember); + } + await channel.permissionOverwrites.delete(ticketMember); } + } - await uploadTranscriptToGitHub(channel.id, transcript); - - const staffRole = guild.roles.cache.get(setupTicket.staffRoleID); - if (staffRole && ticketMember) { - const hasRole = ticketMember.roles.cache.has(staffRole.id); - if (!hasRole) { - for (const memberID of ticket.membersAdded) { - const addedMember = guild.members.cache.get(memberID); - if (addedMember) - await channel.permissionOverwrites.delete(addedMember); - } - await channel.permissionOverwrites.delete(ticketMember); - } - } + await ticketSchema.findOneAndUpdate( + { guildID: guild.id, ticketChannelID: channel.id, closed: false }, + { closed: true, closeReason: reason }, + { new: true } + ); - await ticketSchema.findOneAndUpdate( - { guildID: guild.id, ticketChannelID: channel.id, closed: false }, - { closed: true, closeReason: reason }, - { new: true } - ); - - setTimeout(() => { - channel.delete().catch((error) => { - console.error('Error deleting ticket channel:', error); - }); - }, 5000); - - return { success: true, message: 'Ticket closed successfully.' }; - } catch (error) { - console.error('Error closing ticket:', error); - return { - success: false, - message: - 'There was an error closing the ticket. Please try again later.', - }; - } + setTimeout(() => { + channel.delete().catch((error) => { + console.error('Error deleting ticket channel:', error); + }); + }, 5000); + + return { success: true, message: 'Ticket closed successfully.' }; + } catch (error) { + console.error('Error closing ticket:', error); + return { + success: false, + message: 'There was an error closing the ticket. Please try again later.', + }; + } } async function uploadTranscriptToGitHub(channelId, transcript) { - const githubToken = process.env.GITHUB_TOKEN; - const owner = 'GrishMahat'; - const repo = 'discordbot-html-transcript'; - const filePath = `transcripts/transcript-${channelId}.html`; - const commitMessage = `Add transcript for ticket ${channelId}`; - - const content = transcript.toString('base64'); - const url = `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}`; - - const headers = { - Authorization: `token ${githubToken}`, - Accept: 'application/vnd.github.v3+json', - }; - const data = { - message: commitMessage, - content: content, - branch: 'main', - }; - - try { - const response = await axios.get(url, { headers }); - data.sha = response.data.sha; - } catch (error) { - if (error.response && error.response.status !== 404) { - console.error('Error checking file existence:', error.response.data); - throw error; - } - } - - await axios.put(url, data, { headers }); + const githubToken = process.env.GITHUB_TOKEN; + const owner = 'GrishMahat'; + const repo = 'discordbot-html-transcript'; + const filePath = `transcripts/transcript-${channelId}.html`; + const commitMessage = `Add transcript for ticket ${channelId}`; + + const content = transcript.toString('base64'); + const url = `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}`; + + const headers = { + Authorization: `token ${githubToken}`, + Accept: 'application/vnd.github.v3+json', + }; + const data = { + message: commitMessage, + content: content, + branch: 'main', + }; + + try { + const response = await axios.get(url, { headers }); + data.sha = response.data.sha; + } catch (error) { + if (error.response && error.response.status !== 404) { + console.error('Error checking file existence:', error.response.data); + throw error; + } + } + + await axios.put(url, data, { headers }); } // TODO List // 1. **Add Error Handling for GitHub Upload**: Improve error handling for the `uploadTranscriptToGitHub` function to provide more descriptive error messages and handle edge cases (e.g., invalid GitHub token, repository issues). diff --git a/src/utils/ticket/ticketCreate.js b/src/utils/ticket/ticketCreate.js index 39197c3..26a4629 100644 --- a/src/utils/ticket/ticketCreate.js +++ b/src/utils/ticket/ticketCreate.js @@ -1,176 +1,176 @@ /** @format */ import { - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ChannelType, - PermissionFlagsBits, + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ChannelType, + PermissionFlagsBits, } from 'discord.js'; import ticketSchema from '../../schemas/ticketSchema.js'; export async function createTicket( - guild, - member, - staffRole, - category, - subject = 'No subject provided', - description = 'No description provided', - parentChannelId + guild, + member, + staffRole, + category, + subject = 'No subject provided', + description = 'No description provided', + parentChannelId ) { - try { - const username = member.user.username; - - // Check for existing open tickets - const existingTicket = await ticketSchema.findOne({ - guildID: guild.id, - ticketMemberID: member.id, - closed: false, - }); - - if (existingTicket) { - return { - success: false, - message: `You already have an open ticket! <#${existingTicket.ticketChannelID}>`, - }; - } - - // Get previous tickets - const closedTickets = await ticketSchema - .find({ - guildID: guild.id, - ticketMemberID: member.id, - closed: true, - }) - .sort({ closedAt: -1 }) - .limit(3); - - let previousTicketsField = 'No previous tickets'; - if (closedTickets.length > 0) { - previousTicketsField = closedTickets - .map((ticket, index) => { - const claimedBy = ticket.claimedBy - ? `<@${ticket.claimedBy}>` - : 'Unclaimed'; - const closeReason = ticket.closeReason - ? ticket.closeReason - : 'No reason provided'; - return `Ticket ${ - index + 1 - }:\n- Claimed by: ${claimedBy}\n- Close reason: ${closeReason}`; - }) - .join('\n\n'); - } - - // Create ticket embed - const ticketEmbed = new EmbedBuilder() - .setColor('#9861FF') - .setAuthor({ - name: username, - iconURL: member.user.displayAvatarURL({ dynamic: true }), - }) - .setDescription( - `**Subject:** ${subject}\n**Description:** ${description}` - ) - .addFields({ name: 'Previous Tickets', value: previousTicketsField }) - .setFooter({ - text: `${guild.name} - Ticket`, - iconURL: guild.iconURL(), - }) - .setTimestamp(); - - // Create action row with buttons - const ticketButtons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('claimTicketBtn') - .setLabel('Claim Ticket') - .setStyle(ButtonStyle.Primary), - new ButtonBuilder() - .setCustomId('closeTicketBtn') - .setLabel('Close Ticket') - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setCustomId('lockTicketBtn') - .setLabel('Lock Ticket') - .setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId('requestUserInfoBtn') - .setLabel('Request User Info') - .setStyle(ButtonStyle.Secondary) - ); - - // Get ticket count for naming - const ticketCount = await ticketSchema.countDocuments({ - guildID: guild.id, - }); - - // Create ticket channel - const ticketChannel = await guild.channels.create({ - name: `ticket-${ticketCount + 1}`, - type: ChannelType.GuildText, - parent: category.id, - permissionOverwrites: [ - { - id: guild.id, - deny: [PermissionFlagsBits.ViewChannel], - }, - { - id: member.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - ], - }, - { - id: staffRole.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.ReadMessageHistory, - PermissionFlagsBits.SendMessages, - ], - }, - ], - }); - - await ticketChannel.send({ - content: `${staffRole} - Ticket created by ${username}`, - embeds: [ticketEmbed], - components: [ticketButtons], - }); - - // Create and save ticket in database - const newTicket = await ticketSchema.create({ - guildID: guild.id, - ticketMemberID: member.id, - ticketChannelID: ticketChannel.id, - parentTicketChannelID: parentChannelId, - subject: subject, - description: description, - closed: false, - membersAdded: [], - claimedBy: null, - status: 'open', - actionLog: [`Ticket created by ${member.user.tag}`], - closeReason: '', - createdAt: new Date(), - }); - - await newTicket.save(); + try { + const username = member.user.username; + // Check for existing open tickets + const existingTicket = await ticketSchema.findOne({ + guildID: guild.id, + ticketMemberID: member.id, + closed: false, + }); + + if (existingTicket) { return { - success: true, - message: `Your ticket has been created in ${ticketChannel}`, - ticketChannel: ticketChannel, - }; - } catch (error) { - console.error('Error creating ticket:', error); - return { - success: false, - message: - 'There was an error creating your ticket. Please try again later.', + success: false, + message: `You already have an open ticket! <#${existingTicket.ticketChannelID}>`, }; - } + } + + // Get previous tickets + const closedTickets = await ticketSchema + .find({ + guildID: guild.id, + ticketMemberID: member.id, + closed: true, + }) + .sort({ closedAt: -1 }) + .limit(3); + + let previousTicketsField = 'No previous tickets'; + if (closedTickets.length > 0) { + previousTicketsField = closedTickets + .map((ticket, index) => { + const claimedBy = ticket.claimedBy + ? `<@${ticket.claimedBy}>` + : 'Unclaimed'; + const closeReason = ticket.closeReason + ? ticket.closeReason + : 'No reason provided'; + return `Ticket ${ + index + 1 + }:\n- Claimed by: ${claimedBy}\n- Close reason: ${closeReason}`; + }) + .join('\n\n'); + } + + // Create ticket embed + const ticketEmbed = new EmbedBuilder() + .setColor('#9861FF') + .setAuthor({ + name: username, + iconURL: member.user.displayAvatarURL({ dynamic: true }), + }) + .setDescription( + `**Subject:** ${subject}\n**Description:** ${description}` + ) + .addFields({ name: 'Previous Tickets', value: previousTicketsField }) + .setFooter({ + text: `${guild.name} - Ticket`, + iconURL: guild.iconURL(), + }) + .setTimestamp(); + + // Create action row with buttons + const ticketButtons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('claimTicketBtn') + .setLabel('Claim Ticket') + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId('closeTicketBtn') + .setLabel('Close Ticket') + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId('lockTicketBtn') + .setLabel('Lock Ticket') + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId('requestUserInfoBtn') + .setLabel('Request User Info') + .setStyle(ButtonStyle.Secondary) + ); + + // Get ticket count for naming + const ticketCount = await ticketSchema.countDocuments({ + guildID: guild.id, + }); + + // Create ticket channel + const ticketChannel = await guild.channels.create({ + name: `ticket-${ticketCount + 1}`, + type: ChannelType.GuildText, + parent: category.id, + permissionOverwrites: [ + { + id: guild.id, + deny: [PermissionFlagsBits.ViewChannel], + }, + { + id: member.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + ], + }, + { + id: staffRole.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.ReadMessageHistory, + PermissionFlagsBits.SendMessages, + ], + }, + ], + }); + + await ticketChannel.send({ + content: `${staffRole} - Ticket created by ${username}`, + embeds: [ticketEmbed], + components: [ticketButtons], + }); + + // Create and save ticket in database + const newTicket = await ticketSchema.create({ + guildID: guild.id, + ticketMemberID: member.id, + ticketChannelID: ticketChannel.id, + parentTicketChannelID: parentChannelId, + subject: subject, + description: description, + closed: false, + membersAdded: [], + claimedBy: null, + status: 'open', + actionLog: [`Ticket created by ${member.user.tag}`], + closeReason: '', + createdAt: new Date(), + }); + + await newTicket.save(); + + return { + success: true, + message: `Your ticket has been created in ${ticketChannel}`, + ticketChannel: ticketChannel, + }; + } catch (error) { + console.error('Error creating ticket:', error); + return { + success: false, + message: + 'There was an error creating your ticket. Please try again later.', + }; + } } // Optimize Ticket Embed: Refactor the embed creation into a separate function to improve readability and reuse. diff --git a/test/bot.js b/test/bot.js index 3a4a387..51e906f 100644 --- a/test/bot.js +++ b/test/bot.js @@ -4,25 +4,25 @@ import { Client, GatewayIntentBits } from 'discord.js'; const client = new Client({ intents: [GatewayIntentBits.Guilds] }); client.once('ready', async () => { - console.log('Ready!'); + console.log('Ready!'); - try { - // Get all commands - const commands = await client.application.commands.fetch(); + try { + // Get all commands + const commands = await client.application.commands.fetch(); - // Loop through each command and delete it - for (const command of commands.values()) { - await client.application.commands.delete(command.id); - console.log(`Deleted command: ${command.name}`); - } + // Loop through each command and delete it + for (const command of commands.values()) { + await client.application.commands.delete(command.id); + console.log(`Deleted command: ${command.name}`); + } - console.log('All commands deleted.'); - } catch (error) { - console.error('Error deleting commands:', error); - } + console.log('All commands deleted.'); + } catch (error) { + console.error('Error deleting commands:', error); + } - // Close the bot - client.destroy(); + // Close the bot + client.destroy(); }); client.login(process.env.TOKEN); diff --git a/test/commandinfo.js b/test/commandinfo.js index db2daa7..19dc5d9 100644 --- a/test/commandinfo.js +++ b/test/commandinfo.js @@ -17,39 +17,39 @@ const client = new Client({ intents: [GatewayIntentBits.Guilds] }); * @throws {Error} If there's an error during the fetching process. */ async function fetchCommand(commandNameOrId, client) { - try { - // Fetch all application commands - const applicationCommands = await client.application.commands.fetch(); - - // Filter to find the command by name or ID - const filteredCommand = applicationCommands.find( - (cmd) => cmd.name === commandNameOrId || cmd.id === commandNameOrId - ); - - // Return the filtered command or null if not found - return filteredCommand || null; - } catch (error) { - console.error(`Error fetching commands: ${error}`); - throw error; - } + try { + // Fetch all application commands + const applicationCommands = await client.application.commands.fetch(); + + // Filter to find the command by name or ID + const filteredCommand = applicationCommands.find( + (cmd) => cmd.name === commandNameOrId || cmd.id === commandNameOrId + ); + + // Return the filtered command or null if not found + return filteredCommand || null; + } catch (error) { + console.error(`Error fetching commands: ${error}`); + throw error; + } } // Example usage of the fetchCommand function (async () => { - try { - await client.login(process.env.TOKEN); - - const commandNameOrId = 'triggered'; - const command = await fetchCommand(commandNameOrId, client); - - if (command) { - console.log(`Command found: ${command.name}`); - console.log(command); - } else { - console.log('Command not found.'); - } - client.destroy(); - } catch (error) { - console.error('Error:', error); - } + try { + await client.login(process.env.TOKEN); + + const commandNameOrId = 'triggered'; + const command = await fetchCommand(commandNameOrId, client); + + if (command) { + console.log(`Command found: ${command.name}`); + console.log(command); + } else { + console.log('Command not found.'); + } + client.destroy(); + } catch (error) { + console.error('Error:', error); + } })(); diff --git a/test/exampleCmd.js b/test/exampleCmd.js index e9defdd..d71ee35 100644 --- a/test/exampleCmd.js +++ b/test/exampleCmd.js @@ -5,153 +5,150 @@ import ticketSetupSchema from '../schemas/ticketSetupSchema.js'; import axios from 'axios'; export default { - customId: 'confirmCloseTicketBtn', - userPermissions: [PermissionFlagsBits.ManageThreads], - botPermissions: [], - run: async (client, interaction) => { + customId: 'confirmCloseTicketBtn', + userPermissions: [PermissionFlagsBits.ManageThreads], + botPermissions: [], + run: async (client, interaction) => { + try { + const { channel, guild, user } = interaction; + + const closingEmbed = new EmbedBuilder() + .setColor('Red') + .setTitle('Closing Ticket') + .setDescription('Closing ticket...'); + + await channel.send({ embeds: [closingEmbed] }); + + await interaction.deferReply({ ephemeral: true }); + + const closedEmbed = new EmbedBuilder() + .setColor('Red') + .setTitle('Ticket Closed') + .setDescription('This ticket has been closed.'); + + // Fetch ticket setup and ticket details from the database + const setupTicket = await ticketSetupSchema.findOne({ + guildID: guild.id, + }); + + const ticket = await ticketSchema.findOne({ + guildID: guild.id, + ticketChannelID: channel.id, + closed: false, + }); + + if (!setupTicket || !ticket) { + return await interaction.editReply({ + content: 'Ticket setup or ticket not found.', + ephemeral: true, + }); + } + + // Generate the transcript + const transcript = await dht.createTranscript(channel, { + returnType: 'buffer', + poweredBy: false, // Whether to include the "Powered by discord-html-transcripts" footer + }); + + // Send transcript to the user via DM + const dmChannel = await user.createDM(); + await dmChannel.send({ + files: [ + { + attachment: transcript, + name: `transcript-${channel.id}.html`, + }, + ], + }); + + // Send transcript to the log channel + const logChannel = guild.channels.cache.get(setupTicket.logChannelID); + if (logChannel) { + await logChannel.send({ + files: [ + { + attachment: transcript, + name: `transcript-${channel.id}.html`, + }, + ], + }); + } + + // Upload the transcript to GitHub + const githubToken = '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'; + const owner = 'GrishMahat'; + const repo = 'discordbot-html-transcript'; + const filePath = `transcripts/transcript-${channel.id}.html`; + const commitMessage = `Add transcript for ticket ${channel.id}`; + + const content = transcript.toString('base64'); + + const url = `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}`; + + const headers = { + Authorization: `token ${githubToken}`, + Accept: 'application/vnd.github.v3+json', + }; + const data = { + message: commitMessage, + content: content, + branch: 'main', // or the branch you want to upload to + }; + + // Check if the file already exists + let sha; try { - const { channel, guild, user } = interaction; - - const closingEmbed = new EmbedBuilder() - .setColor('Red') - .setTitle('Closing Ticket') - .setDescription('Closing ticket...'); - - await channel.send({ embeds: [closingEmbed] }); - - await interaction.deferReply({ ephemeral: true }); - - const closedEmbed = new EmbedBuilder() - .setColor('Red') - .setTitle('Ticket Closed') - .setDescription('This ticket has been closed.'); - - // Fetch ticket setup and ticket details from the database - const setupTicket = await ticketSetupSchema.findOne({ - guildID: guild.id, - }); - - const ticket = await ticketSchema.findOne({ - guildID: guild.id, - ticketChannelID: channel.id, - closed: false, - }); - - if (!setupTicket || !ticket) { - return await interaction.editReply({ - content: 'Ticket setup or ticket not found.', - ephemeral: true, - }); - } - - // Generate the transcript - const transcript = await dht.createTranscript(channel, { - returnType: 'buffer', - poweredBy: false, // Whether to include the "Powered by discord-html-transcripts" footer - }); - - // Send transcript to the user via DM - const dmChannel = await user.createDM(); - await dmChannel.send({ - files: [ - { - attachment: transcript, - name: `transcript-${channel.id}.html`, - }, - ], - }); - - // Send transcript to the log channel - const logChannel = guild.channels.cache.get(setupTicket.logChannelID); - if (logChannel) { - await logChannel.send({ - files: [ - { - attachment: transcript, - name: `transcript-${channel.id}.html`, - }, - ], - }); - } - - // Upload the transcript to GitHub - const githubToken = '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'; - const owner = 'GrishMahat'; - const repo = 'discordbot-html-transcript'; - const filePath = `transcripts/transcript-${channel.id}.html`; - const commitMessage = `Add transcript for ticket ${channel.id}`; - - const content = transcript.toString('base64'); - - const url = `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}`; - - const headers = { - Authorization: `token ${githubToken}`, - Accept: 'application/vnd.github.v3+json', - }; - const data = { - message: commitMessage, - content: content, - branch: 'main', // or the branch you want to upload to - }; - - // Check if the file already exists - let sha; - try { - const response = await axios.get(url, { headers }); - sha = response.data.sha; - } catch (error) { - if (error.response && error.response.status !== 404) { - console.error( - 'Error checking file existence:', - error.response.data - ); - throw error; - } - } - - if (sha) { - data.sha = sha; // Include SHA if updating an existing file - } - - await axios.put(url, data, { headers }); - - const staffRole = guild.roles.cache.get(setupTicket.staffRoleID); - const hasRole = guild.members.cache - .get(ticket.ticketMemberID) - .roles.cache.has(staffRole.id); - - if (!hasRole) { - for (const memberID of ticket.membersAdded) { - const member = guild.members.cache.get(memberID); - if (member) await channel.permissionOverwrites.delete(member); - } - const ticketMember = guild.members.cache.get(ticket.ticketMemberID); - if (ticketMember) - await channel.permissionOverwrites.delete(ticketMember); - } - - // Update the ticket to closed in the database - await ticketSchema.findOneAndUpdate( - { guildID: guild.id, ticketChannelID: channel.id, closed: false }, - { closed: true } - ); - - await interaction.editReply({ embeds: [closedEmbed] }); - - // Delete the ticket channel after a short delay - setTimeout(() => { - channel.delete().catch((error) => { - console.error('Error deleting ticket channel:', error); - }); - }, 5000); + const response = await axios.get(url, { headers }); + sha = response.data.sha; } catch (error) { - console.error('Error closing ticket:', error); - await interaction.editReply({ - content: - 'There was an error closing the ticket. Please try again later.', - ephemeral: true, - }); + if (error.response && error.response.status !== 404) { + console.error('Error checking file existence:', error.response.data); + throw error; + } } - }, + + if (sha) { + data.sha = sha; // Include SHA if updating an existing file + } + + await axios.put(url, data, { headers }); + + const staffRole = guild.roles.cache.get(setupTicket.staffRoleID); + const hasRole = guild.members.cache + .get(ticket.ticketMemberID) + .roles.cache.has(staffRole.id); + + if (!hasRole) { + for (const memberID of ticket.membersAdded) { + const member = guild.members.cache.get(memberID); + if (member) await channel.permissionOverwrites.delete(member); + } + const ticketMember = guild.members.cache.get(ticket.ticketMemberID); + if (ticketMember) + await channel.permissionOverwrites.delete(ticketMember); + } + + // Update the ticket to closed in the database + await ticketSchema.findOneAndUpdate( + { guildID: guild.id, ticketChannelID: channel.id, closed: false }, + { closed: true } + ); + + await interaction.editReply({ embeds: [closedEmbed] }); + + // Delete the ticket channel after a short delay + setTimeout(() => { + channel.delete().catch((error) => { + console.error('Error deleting ticket channel:', error); + }); + }, 5000); + } catch (error) { + console.error('Error closing ticket:', error); + await interaction.editReply({ + content: + 'There was an error closing the ticket. Please try again later.', + ephemeral: true, + }); + } + }, }; diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..4ef9d8f --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3763 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.25.4", "@babel/runtime@^7.7.2": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" + integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== + dependencies: + regenerator-runtime "^0.14.0" + +"@derockdev/discord-components-core@^3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@derockdev/discord-components-core/-/discord-components-core-3.6.1.tgz#451f2c79539d540536f1c6e739f22c1912790ef9" + integrity sha512-qLcoab2Olui1IzJavnPzMgZzopWU21D3VDthkFgzZyiID4C5+OiSWx6ZNxz6wnMKfv/253AsXg8opdCwoRJKgg== + dependencies: + "@stencil/core" "^3.4.1" + clsx "^1.2.1" + hex-to-rgba "^2.0.1" + highlight.js "^11.6.0" + +"@derockdev/discord-components-react@^3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@derockdev/discord-components-react/-/discord-components-react-3.6.1.tgz#5e0c444910a0617c2a41bbfa73bab462cb946083" + integrity sha512-+EIHAo5wgXbVwJVgsRohi5/ZcWwrzzCPlV45c1lDL5iOvuuHDZKuPXJdUCdxUJBUpd2zxhcvjBXEZIlJqTe+sA== + dependencies: + "@derockdev/discord-components-core" "^3.6.1" + tslib "^2.6.0" + +"@discordjs/builders@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.9.0.tgz#71fa6de91132bd1deaff2a9daea7aa5d5c9f124a" + integrity sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg== + dependencies: + "@discordjs/formatters" "^0.5.0" + "@discordjs/util" "^1.1.1" + "@sapphire/shapeshift" "^4.0.0" + discord-api-types "0.37.97" + fast-deep-equal "^3.1.3" + ts-mixer "^6.0.4" + tslib "^2.6.3" + +"@discordjs/collection@1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.5.3.tgz#5a1250159ebfff9efa4f963cfa7e97f1b291be18" + integrity sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ== + +"@discordjs/collection@^2.1.0", "@discordjs/collection@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-2.1.1.tgz#901917bc538c12b9c3613036d317847baee08cae" + integrity sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg== + +"@discordjs/formatters@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@discordjs/formatters/-/formatters-0.5.0.tgz#2d284c4271bc41984339936df1d0164e470f3b7a" + integrity sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g== + dependencies: + discord-api-types "0.37.97" + +"@discordjs/rest@^2.3.0", "@discordjs/rest@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-2.4.0.tgz#63bfc816af58af844914e3589d7eae609cd199b5" + integrity sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw== + dependencies: + "@discordjs/collection" "^2.1.1" + "@discordjs/util" "^1.1.1" + "@sapphire/async-queue" "^1.5.3" + "@sapphire/snowflake" "^3.5.3" + "@vladfrangu/async_event_emitter" "^2.4.6" + discord-api-types "0.37.97" + magic-bytes.js "^1.10.0" + tslib "^2.6.3" + undici "6.19.8" + +"@discordjs/util@^1.1.0", "@discordjs/util@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-1.1.1.tgz#bafcde0faa116c834da1258d78ec237080bbab29" + integrity sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g== + +"@discordjs/ws@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@discordjs/ws/-/ws-1.1.1.tgz#bffbfd46838258ab09054ed98ddef1a36f6507a3" + integrity sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA== + dependencies: + "@discordjs/collection" "^2.1.0" + "@discordjs/rest" "^2.3.0" + "@discordjs/util" "^1.1.0" + "@sapphire/async-queue" "^1.5.2" + "@types/ws" "^8.5.10" + "@vladfrangu/async_event_emitter" "^2.2.4" + discord-api-types "0.37.83" + tslib "^2.6.2" + ws "^8.16.0" + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.6.1": + version "4.11.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" + integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/eslintrc@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6" + integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@eslint/js@^9.10.0": + version "9.11.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.11.1.tgz#8bcb37436f9854b3d9a561440daf916acd940986" + integrity sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA== + +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@jimp/bmp@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.16.13.tgz#57ffa5b17417b5a181f6f184bdabc8218e8448ef" + integrity sha512-9edAxu7N2FX7vzkdl5Jo1BbACfycUtBQX+XBMcHA2bk62P8R0otgkHg798frgAk/WxQIzwxqOH6wMiCwrlAzdQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + bmp-js "^0.1.0" + +"@jimp/core@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.16.13.tgz#7171745a912b5b847f8bf53e70b0672c5ca92744" + integrity sha512-qXpA1tzTnlkTku9yqtuRtS/wVntvE6f3m3GNxdTdtmc+O+Wcg9Xo2ABPMh7Nc0AHbMKzwvwgB2JnjZmlmJEObg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + any-base "^1.1.0" + buffer "^5.2.0" + exif-parser "^0.1.12" + file-type "^16.5.4" + load-bmfont "^1.3.1" + mkdirp "^0.5.1" + phin "^2.9.1" + pixelmatch "^4.0.2" + tinycolor2 "^1.4.1" + +"@jimp/custom@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.16.13.tgz#2e4ed447b7410b81fe9103682b4166af904daf84" + integrity sha512-LTATglVUPGkPf15zX1wTMlZ0+AU7cGEGF6ekVF1crA8eHUWsGjrYTB+Ht4E3HTrCok8weQG+K01rJndCp/l4XA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/core" "^0.16.13" + +"@jimp/gif@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.16.13.tgz#fa72f35d8ad67d6ce3a3d7ef6c8d04a462afaaf9" + integrity sha512-yFAMZGv3o+YcjXilMWWwS/bv1iSqykFahFMSO169uVMtfQVfa90kt4/kDwrXNR6Q9i6VHpFiGZMlF2UnHClBvg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + gifwrap "^0.9.2" + omggif "^1.0.9" + +"@jimp/jpeg@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.16.13.tgz#e1c128a591bd7f8a26c8731fd0bc65d32d4ba32a" + integrity sha512-BJHlDxzTlCqP2ThqP8J0eDrbBfod7npWCbJAcfkKqdQuFk0zBPaZ6KKaQKyKxmWJ87Z6ohANZoMKEbtvrwz1AA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + jpeg-js "^0.4.2" + +"@jimp/plugin-blit@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.16.13.tgz#370303edef02b75aa3e316726c5a3aac3e92f5d0" + integrity sha512-8Z1k96ZFxlhK2bgrY1JNWNwvaBeI/bciLM0yDOni2+aZwfIIiC7Y6PeWHTAvjHNjphz+XCt01WQmOYWCn0ML6g== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-blur@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.16.13.tgz#27b82295a3dee88d6e029d4d62f5de8118b845e6" + integrity sha512-PvLrfa8vkej3qinlebyhLpksJgCF5aiysDMSVhOZqwH5nQLLtDE9WYbnsofGw4r0VVpyw3H/ANCIzYTyCtP9Cg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-circle@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-0.16.13.tgz#d7af61a95b17e67c7fd4361cd1d588e00b58b6b6" + integrity sha512-RNave7EFgZrb5V5EpdvJGAEHMnDAJuwv05hKscNfIYxf0kR3KhViBTDy+MoTnMlIvaKFULfwIgaZWzyhuINMzA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-color@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.16.13.tgz#825227e7e6f32d227740ad1bd97c389083c1d0d1" + integrity sha512-xW+9BtEvoIkkH/Wde9ql4nAFbYLkVINhpgAE7VcBUsuuB34WUbcBl/taOuUYQrPEFQJ4jfXiAJZ2H/rvKjCVnQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + tinycolor2 "^1.4.1" + +"@jimp/plugin-contain@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.16.13.tgz#7a42ed1ce580bf910f812ba2f35e0fa2cfe501ac" + integrity sha512-QayTXw4tXMwU6q6acNTQrTTFTXpNRBe+MgTGMDU0lk+23PjlFCO/9sacflelG8lsp7vNHhAxFeHptDMAksEYzg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-cover@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.16.13.tgz#9c964be05b163e0f0e06866a9afcebe775dff246" + integrity sha512-BSsP71GTNaqWRcvkbWuIVH+zK7b3TSNebbhDkFK0fVaUTzHuKMS/mgY4hDZIEVt7Rf5FjadAYtsujHN9w0iSYA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-crop@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.16.13.tgz#80c6ae4d401a8de6cc11b265f3cdecd80425b9a9" + integrity sha512-WEl2tPVYwzYL8OKme6Go2xqiWgKsgxlMwyHabdAU4tXaRwOCnOI7v4021gCcBb9zn/oWwguHuKHmK30Fw2Z/PA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-displace@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.16.13.tgz#fd72aa93b3fe97a1c3da729e6b26399661ce8ce5" + integrity sha512-qt9WKq8vWrcjySa9DyQ0x/RBMHQeiVjdVSY1SJsMjssPUf0pS74qorcuAkGi89biN3YoGUgPkpqECnAWnYwgGA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-dither@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.16.13.tgz#430750f73d528df7ebe21bb508fb80f9f515305d" + integrity sha512-5/N3yJggbWQTlGZHQYJPmQXEwR52qaXjEzkp1yRBbtdaekXE3BG/suo0fqeoV/csf8ooI78sJzYmIrxNoWVtgQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-fisheye@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.13.tgz#caf69851ab25c44d13c952880a8e43c928abd3f1" + integrity sha512-2rZmTdFbT/cF9lEZIkXCYO0TsT114Q27AX5IAo0Sju6jVQbvIk1dFUTnwLDadTo8wkJlFzGqMQ24Cs8cHWOliA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-flip@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.16.13.tgz#3dd167e14d03d62410c519990728ac3c247c0692" + integrity sha512-EmcgAA74FTc5u7Z+hUO/sRjWwfPPLuOQP5O64x5g4j0T12Bd29IgsYZxoutZo/rb3579+JNa/3wsSEmyVv1EpA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-gaussian@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.13.tgz#79879d9371aff3e1714c54be0771418573ac2954" + integrity sha512-A1XKfGQD0iDdIiKqFYi8nZMv4dDVYdxbrmgR7y/CzUHhSYdcmoljLIIsZZM3Iks/Wa353W3vtvkWLuDbQbch1w== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-invert@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.16.13.tgz#7449283d5b0f405ce2cd1b93a6d79169c970e431" + integrity sha512-xFMrIn7czEZbdbMzZWuaZFnlLGJDVJ82y5vlsKsXRTG2kcxRsMPXvZRWHV57nSs1YFsNqXSbrC8B98n0E32njQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-mask@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.16.13.tgz#70b4bef4a598e41571f9a3e0c33fcc730eeae24d" + integrity sha512-wLRYKVBXql2GAYgt6FkTnCfE+q5NomM7Dlh0oIPGAoMBWDyTx0eYutRK6PlUrRK2yMHuroAJCglICTbxqGzowQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-normalize@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.16.13.tgz#fd7c802c3f6be8d34abf0dbeadfe1d783e531d67" + integrity sha512-3tfad0n9soRna4IfW9NzQdQ2Z3ijkmo21DREHbE6CGcMIxOSvfRdSvf1qQPApxjTSo8LTU4MCi/fidx/NZ0GqQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-print@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.16.13.tgz#595fb6db6677ac3d2b6bfe7144658019791bf288" + integrity sha512-0m6i3p01PGRkGAK9r53hDYrkyMq+tlhLOIbsSTmZyh6HLshUKlTB7eXskF5OpVd5ZUHoltlNc6R+ggvKIzxRFw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + load-bmfont "^1.4.0" + +"@jimp/plugin-resize@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.16.13.tgz#6267087f724d47e7bb8824c5b842d9315f50b8e7" + integrity sha512-qoqtN8LDknm3fJm9nuPygJv30O3vGhSBD2TxrsCnhtOsxKAqVPJtFVdGd/qVuZ8nqQANQmTlfqTiK9mVWQ7MiQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-rotate@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.16.13.tgz#9981f24631b1a0ad486d2b75a0163918ff912491" + integrity sha512-Ev+Jjmj1nHYw897z9C3R9dYsPv7S2/nxdgfFb/h8hOwK0Ovd1k/+yYS46A0uj/JCKK0pQk8wOslYBkPwdnLorw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-scale@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.16.13.tgz#36b1b7d70819591901339926a91dae4864cc1b92" + integrity sha512-05POQaEJVucjTiSGMoH68ZiELc7QqpIpuQlZ2JBbhCV+WCbPFUBcGSmE7w4Jd0E2GvCho/NoMODLwgcVGQA97A== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-shadow@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-shadow/-/plugin-shadow-0.16.13.tgz#f5b58122c0a6e1307efcddfc165ce1291415d553" + integrity sha512-nmu5VSZ9hsB1JchTKhnnCY+paRBnwzSyK5fhkhtQHHoFD5ArBQ/5wU8y6tCr7k/GQhhGq1OrixsECeMjPoc8Zw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugin-threshold@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-0.16.13.tgz#8de7500b03342b251201bc0feb84955dd3e410f0" + integrity sha512-+3zArBH0OE3Rhjm4HyAokMsZlIq5gpQec33CncyoSwxtRBM2WAhUVmCUKuBo+Lr/2/4ISoY4BWpHKhMLDix6cA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + +"@jimp/plugins@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.16.13.tgz#cf441ee13204dd9474bc0e67e41c50afc910de4f" + integrity sha512-CJLdqODEhEVs4MgWCxpWL5l95sCBlkuSLz65cxEm56X5akIsn4LOlwnKoSEZioYcZUBvHhCheH67AyPTudfnQQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/plugin-blit" "^0.16.13" + "@jimp/plugin-blur" "^0.16.13" + "@jimp/plugin-circle" "^0.16.13" + "@jimp/plugin-color" "^0.16.13" + "@jimp/plugin-contain" "^0.16.13" + "@jimp/plugin-cover" "^0.16.13" + "@jimp/plugin-crop" "^0.16.13" + "@jimp/plugin-displace" "^0.16.13" + "@jimp/plugin-dither" "^0.16.13" + "@jimp/plugin-fisheye" "^0.16.13" + "@jimp/plugin-flip" "^0.16.13" + "@jimp/plugin-gaussian" "^0.16.13" + "@jimp/plugin-invert" "^0.16.13" + "@jimp/plugin-mask" "^0.16.13" + "@jimp/plugin-normalize" "^0.16.13" + "@jimp/plugin-print" "^0.16.13" + "@jimp/plugin-resize" "^0.16.13" + "@jimp/plugin-rotate" "^0.16.13" + "@jimp/plugin-scale" "^0.16.13" + "@jimp/plugin-shadow" "^0.16.13" + "@jimp/plugin-threshold" "^0.16.13" + timm "^1.6.1" + +"@jimp/png@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.16.13.tgz#8b130cc5e1e754c074c42fa3fe2609897cefdf7c" + integrity sha512-8cGqINvbWJf1G0Her9zbq9I80roEX0A+U45xFby3tDWfzn+Zz8XKDF1Nv9VUwVx0N3zpcG1RPs9hfheG4Cq2kg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.16.13" + pngjs "^3.3.3" + +"@jimp/tiff@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.16.13.tgz#9cf8d19f2b0b0c46758e81acfc7d656835ee6da1" + integrity sha512-oJY8d9u95SwW00VPHuCNxPap6Q1+E/xM5QThb9Hu+P6EGuu6lIeLaNBMmFZyblwFbwrH+WBOZlvIzDhi4Dm/6Q== + dependencies: + "@babel/runtime" "^7.7.2" + utif "^2.0.1" + +"@jimp/types@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.16.13.tgz#39be1886cbfa4fb5e77e17441a046a1f961d3046" + integrity sha512-mC0yVNUobFDjoYLg4hoUwzMKgNlxynzwt3cDXzumGvRJ7Kb8qQGOWJQjQFo5OxmGExqzPphkirdbBF88RVLBCg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/bmp" "^0.16.13" + "@jimp/gif" "^0.16.13" + "@jimp/jpeg" "^0.16.13" + "@jimp/png" "^0.16.13" + "@jimp/tiff" "^0.16.13" + timm "^1.6.1" + +"@jimp/utils@^0.16.13": + version "0.16.13" + resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.16.13.tgz#afde41b9c6cdadfb45d83cb5e16deb65f369bf99" + integrity sha512-VyCpkZzFTHXtKgVO35iKN0sYR10psGpV6SkcSeV4oF7eSYlR8Bl6aQLCzVeFjvESF7mxTmIiI3/XrMobVrtxDA== + dependencies: + "@babel/runtime" "^7.7.2" + regenerator-runtime "^0.13.3" + +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@mongodb-js/saslprep@^1.1.5": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz#e974bab8eca9faa88677d4ea4da8d09a52069004" + integrity sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw== + dependencies: + sparse-bitfield "^3.0.3" + +"@napi-rs/canvas-android-arm64@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.56.tgz#9002c2562d3473b9272d5124803d7e8183ff8ecc" + integrity sha512-xBGqW2RZMAupkzar9t3gpbok9r524f3Wlk4PG2qnQdxbsiEND06OB8VxVtTcql6R02uJpXJGnyIhN02Te+GMVQ== + +"@napi-rs/canvas-darwin-arm64@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.56.tgz#98084813294393296fda5560be9bba9a4c2f7c66" + integrity sha512-Pvuz6Ib9YZTB5MlGL9WSu9a2asUC0DZ1zBHozDiBXr/6Zurs9l/ZH5NxFYTM829BpkdkO8kuI8b8Rz7ek30zzQ== + +"@napi-rs/canvas-darwin-x64@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.56.tgz#591f41bad39bff925dded34bf18da6068de93dbb" + integrity sha512-O393jWt7G6rg0X1ralbsbBeskSG0iwlkD7mEHhMLJxqRqe+eQn0/xnwhs9l6dUNFC+5dM8LOvfFca4o9Vs2Vww== + +"@napi-rs/canvas-linux-arm-gnueabihf@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.56.tgz#29a2678468c456c45106f61cced08533f7ad9eaf" + integrity sha512-30NFb5lrF3YEwAO5XuATxpWDSXaBAgaFVswPJ+hYcAUyE3IkPPIFRY4ijQEh4frcSBvrzFGGYdNSoC18oLLWaQ== + +"@napi-rs/canvas-linux-arm64-gnu@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.56.tgz#c1f63aba93ac371acb109928dde331573fef8a16" + integrity sha512-ODbWH9TLvba+39UxFwPn2Hm1ImALmWOZ0pEv5do/pz0439326Oz49hlfGot4KmkSBeKK81knWxRj9EXMSPwXPg== + +"@napi-rs/canvas-linux-arm64-musl@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.56.tgz#f98bb429ff70030a63c7338ce9ffb0c008a25bb4" + integrity sha512-zqE4nz8CWiJJ0q5By7q9CDPicNkc0oyErgavK3ZV279zJL7Aapd3cIqayT6ynECArg7GgBl2WYSvr5AaRFmYgg== + +"@napi-rs/canvas-linux-x64-gnu@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.56.tgz#2babefcdd281d13caa9d009787f8aeb9fbc35759" + integrity sha512-JTnGAtJBQMhfSpN8/rbMnf5oxuO/juUNa0n4LA0LlW0JS9UBpmsS2BwFNCakFqOeAPaqIM6sFFsK3M4hve+Esw== + +"@napi-rs/canvas-linux-x64-musl@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.56.tgz#ff583be74e6b90ffbc19982e9f5d62e6c48c03ff" + integrity sha512-mpws7DhVDIj8ZKa/qcnUVLAm0fxD9RK5ojfNNSI9TOzn2E0f+GUXx8sGsCxDpMVMtN+mtyrMwRqH3F3rTUMWXw== + +"@napi-rs/canvas-win32-x64-msvc@0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.56.tgz#7cd7e3d8dedb1d13e94d6a7f9b26bf46d1dabe71" + integrity sha512-VKAAkgXF+lbFvRFawPOtkfV/P7ogAgWTu5FMCIiBn0Gc3vnkKFG2cLo/IHIJ7FuriToKEidkJGT88iAh7W7GDA== + +"@napi-rs/canvas@^0.1.33", "@napi-rs/canvas@^0.1.56": + version "0.1.56" + resolved "https://registry.yarnpkg.com/@napi-rs/canvas/-/canvas-0.1.56.tgz#d7a610eba4a525d835f43077c5c3841488c43e0e" + integrity sha512-SujSchzG6lLc/wT+Mwxam/w30Kk2sFTiU6bLFcidecKSmlhenAhGMQhZh2iGFfKoh2+8iit0jrt99n6TqReICQ== + optionalDependencies: + "@napi-rs/canvas-android-arm64" "0.1.56" + "@napi-rs/canvas-darwin-arm64" "0.1.56" + "@napi-rs/canvas-darwin-x64" "0.1.56" + "@napi-rs/canvas-linux-arm-gnueabihf" "0.1.56" + "@napi-rs/canvas-linux-arm64-gnu" "0.1.56" + "@napi-rs/canvas-linux-arm64-musl" "0.1.56" + "@napi-rs/canvas-linux-x64-gnu" "0.1.56" + "@napi-rs/canvas-linux-x64-musl" "0.1.56" + "@napi-rs/canvas-win32-x64-msvc" "0.1.56" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@rtsao/scc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" + integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== + +"@sapphire/async-queue@^1.5.2", "@sapphire/async-queue@^1.5.3": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.3.tgz#03cd2a2f3665068f314736bdc56eee2025352422" + integrity sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w== + +"@sapphire/shapeshift@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz#86c1b41002ff5d0b2ad21cbc3418b06834b89040" + integrity sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg== + dependencies: + fast-deep-equal "^3.1.3" + lodash "^4.17.21" + +"@sapphire/snowflake@3.5.3", "@sapphire/snowflake@^3.5.3": + version "3.5.3" + resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.5.3.tgz#0c102aa2ec5b34f806e9bc8625fc6a5e1d0a0c6a" + integrity sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ== + +"@sec-ant/readable-stream@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" + integrity sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg== + +"@stencil/core@^3.4.1": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@stencil/core/-/core-3.4.2.tgz#57ce7f71fe18c2ec0967821bec667fc453cca962" + integrity sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ== + +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/node@*": + version "22.7.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.4.tgz#e35d6f48dca3255ce44256ddc05dee1c23353fcc" + integrity sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg== + dependencies: + undici-types "~6.19.2" + +"@types/node@16.9.1": + version "16.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708" + integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g== + +"@types/prop-types@*": + version "15.7.13" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451" + integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA== + +"@types/react@>=16.0.0": + version "18.3.10" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.10.tgz#6edc26dc22ff8c9c226d3c7bf8357b013c842219" + integrity sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@types/webidl-conversions@*": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz#1306dbfa53768bcbcfc95a1c8cde367975581859" + integrity sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA== + +"@types/whatwg-url@^11.0.2": + version "11.0.5" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-11.0.5.tgz#aaa2546e60f0c99209ca13360c32c78caf2c409f" + integrity sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ== + dependencies: + "@types/webidl-conversions" "*" + +"@types/ws@^8.5.10": + version "8.5.12" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== + dependencies: + "@types/node" "*" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@vladfrangu/async_event_emitter@^2.2.4", "@vladfrangu/async_event_emitter@^2.4.6": + version "2.4.6" + resolved "https://registry.yarnpkg.com/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz#508b6c45b03f917112a9008180b308ba0e4d1805" + integrity sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.12.0, acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +anime-wallpaper@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/anime-wallpaper/-/anime-wallpaper-3.2.2.tgz#6db0331c926e0301b83e607714347b6f4dbce74e" + integrity sha512-BcJ49/VISqhHO3JEWiEcyrGj79DFFso0vKm/x/2jS2gBWXbFrZ290bPuaTVuTYF0nVZsP85sxe43XxDjXhgKDw== + dependencies: + axios "^1.7.7" + cheerio "^1.0.0" + random-useragent "^0.5.0" + +ansi-escapes@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" + integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + dependencies: + environment "^1.0.0" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.0.0, ansi-styles@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +any-base@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" + integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + +array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array.prototype.findlastindex@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +axios@^1.7.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bmp-js@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233" + integrity sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +bottleneck@^2.19.5: + version "2.19.5" + resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" + integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +bson@^6.7.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/bson/-/bson-6.8.0.tgz#5063c41ba2437c2b8ff851b50d9e36cb7aaa7525" + integrity sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ== + +buffer-equal@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" + integrity sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA== + +buffer@^5.2.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +canvas@^2.10.1, canvas@^2.11.2, canvas@^2.2.0: + version "2.11.2" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860" + integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + nan "^2.17.0" + simple-get "^3.0.3" + +centra@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/centra/-/centra-2.7.0.tgz#4c8312a58436e8a718302011561db7e6a2b0ec18" + integrity sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg== + dependencies: + follow-redirects "^1.15.6" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0.tgz#1ede4895a82f26e8af71009f961a9b8cb60d6a81" + integrity sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.1.0" + encoding-sniffer "^0.2.0" + htmlparser2 "^9.1.0" + parse5 "^7.1.2" + parse5-htmlparser2-tree-adapter "^7.0.0" + parse5-parser-stream "^7.1.2" + undici "^6.19.5" + whatwg-mimetype "^4.0.0" + +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +cli-cursor@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" + integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== + dependencies: + restore-cursor "^5.0.0" + +cli-truncate@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" + integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== + dependencies: + slice-ansi "^5.0.0" + string-width "^7.0.0" + +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colorette@^2.0.20: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@~12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +complex.js@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31" + integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" + integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +date-fns@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== + +debug@4, debug@4.x, debug@^4, debug@^4.3.1, debug@^4.3.2, debug@~4.3.6: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +discord-api-types@0.37.100: + version "0.37.100" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.100.tgz#5979892d39511bc7f1dbb9660d2d2cad698b3de7" + integrity sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA== + +discord-api-types@0.37.83: + version "0.37.83" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.83.tgz#a22a799729ceded8176ea747157837ddf4708b1f" + integrity sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA== + +discord-api-types@0.37.97: + version "0.37.97" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.97.tgz#d658573f726ad179261d538dbad4e7e8eca48d11" + integrity sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA== + +discord-arts@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/discord-arts/-/discord-arts-0.6.1.tgz#a6c915f55039dde787f28f7c2209745eb4672141" + integrity sha512-XRny91vhr94UUsDS1dk8uGsLq0K1wwRXJ6qk3M9jN8TmINI+mQt6SqhEcBon6/ulRBS//PhPRrRfg5KkvGqtig== + dependencies: + "@napi-rs/canvas" "^0.1.33" + node-fetch "^2.6.7" + +discord-html-transcripts@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/discord-html-transcripts/-/discord-html-transcripts-3.2.0.tgz#781ba1e7e9cb2404bc0122dc4d8d63435aebc1fe" + integrity sha512-DG6fxZTUNmdJ2A96/4SobHM8lQ8LYsx3Je+TRbhEdHh3NGoqo12HaXg8gTKGv0XuzwoxHtLDSHschwKUyjeJpA== + dependencies: + "@derockdev/discord-components-core" "^3.6.1" + "@derockdev/discord-components-react" "^3.6.1" + discord-markdown-parser "~1.1.0" + react "^18.2.0" + react-dom "^18.2.0" + simple-markdown "^0.7.3" + twemoji "^14.0.2" + undici "^5.23.0" + +discord-image-generation@^1.4.25: + version "1.4.25" + resolved "https://registry.yarnpkg.com/discord-image-generation/-/discord-image-generation-1.4.25.tgz#391c5e21d555716a679872c5261823f3ce42c8f7" + integrity sha512-V6IyhxF1dk3ya98eKdoATAjJi680cY53S4AWnhfJ2JgUPvi+qW+nHrbz9FiYC5fq0GI3OfA/wmk/LzRKYy9QDA== + dependencies: + canvas "^2.10.1" + discord.js "^14.7.1" + gifencoder "^2.0.1" + jimp "^0.16.0" + +discord-markdown-parser@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/discord-markdown-parser/-/discord-markdown-parser-1.1.0.tgz#bfc5c3c2963690a6b21fdedb49039e96b0ca91cd" + integrity sha512-o2+iFgt5qer6UYY5hVTPGq2mGzleKRGYKcvymg67FdKg4AMJ061KbebKunCERWKjx79dmNHMDnGV2F0DRGCNkw== + dependencies: + simple-markdown "^0.7.3" + +discord.js@^14.16.2, discord.js@^14.7.1: + version "14.16.3" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.16.3.tgz#9553366953c992469f47a55af2a11c2054a9babe" + integrity sha512-EPCWE9OkA9DnFFNrO7Kl1WHHDYFXu3CNVFJg63bfU7hVtjZGyhShwZtSBImINQRWxWP2tgo2XI+QhdXx28r0aA== + dependencies: + "@discordjs/builders" "^1.9.0" + "@discordjs/collection" "1.5.3" + "@discordjs/formatters" "^0.5.0" + "@discordjs/rest" "^2.4.0" + "@discordjs/util" "^1.1.1" + "@discordjs/ws" "1.1.1" + "@sapphire/snowflake" "3.5.3" + discord-api-types "0.37.100" + fast-deep-equal "3.1.3" + lodash.snakecase "4.1.1" + tslib "^2.6.3" + undici "6.19.8" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1, domutils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + +emoji-regex@^10.3.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encoding-sniffer@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz#799569d66d443babe82af18c9f403498365ef1d5" + integrity sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg== + dependencies: + iconv-lite "^0.6.3" + whatwg-encoding "^3.1.1" + +entities@^4.2.0, entities@^4.4.0, entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-latex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" + integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.9.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b" + integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.30.0: + version "2.30.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz#21ceea0fc462657195989dd780e50c92fe95f449" + integrity sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw== + dependencies: + "@rtsao/scc" "^1.1.0" + array-includes "^3.1.8" + array.prototype.findlastindex "^1.2.5" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.9.0" + hasown "^2.0.2" + is-core-module "^2.15.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + object.groupby "^1.0.3" + object.values "^1.2.0" + semver "^6.3.1" + tsconfig-paths "^3.15.0" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz#1f785cc5e81eb7534523d85922248232077d2f8c" + integrity sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg== + +eslint@^8.50.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^10.0.1: + version "10.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.2.0.tgz#f4bcead9e05b0615c968e85f83816bc386a45df6" + integrity sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g== + dependencies: + acorn "^8.12.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.1.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +execa@~8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + +exif-parser@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922" + integrity sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw== + +fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +file-type@^16.5.4: + version "16.5.4" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" + integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== + dependencies: + readable-web-to-node-stream "^3.0.0" + strtok3 "^6.2.4" + token-types "^4.1.1" + +file-type@^19.5.0: + version "19.5.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-19.5.0.tgz#c13c5eca9c1c7270f6d5fbff70331b3c976f92b5" + integrity sha512-dMuq6WWnP6BpQY0zYJNpTtQWgeCImSMG0BTIzUBXvxbwc1HWP/E7AE4UWU9XSCOPGJuOHda0HpDnwM2FW+d90A== + dependencies: + get-stream "^9.0.1" + strtok3 "^8.1.0" + token-types "^6.0.0" + uint8array-extras "^1.3.0" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fs-extra@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +get-east-asian-width@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e" + integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + +get-stream@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-9.0.1.tgz#95157d21df8eb90d1647102b63039b1df60ebd27" + integrity sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA== + dependencies: + "@sec-ant/readable-stream" "^0.4.1" + is-stream "^4.0.1" + +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + +gifencoder@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/gifencoder/-/gifencoder-2.0.1.tgz#fd363402e506529494757856aa66196697ee6bd4" + integrity sha512-x19DcyWY10SkshBpokqFOo/HBht9GB75evRYvaLMbez9p+yB/o+kt0fK9AwW59nFiAMs2UUQsjv1lX/hvu9Ong== + dependencies: + canvas "^2.2.0" + +gifwrap@^0.9.2: + version "0.9.4" + resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.9.4.tgz#f4eb6169ba027d61df64aafbdcb1f8ae58ccc0c5" + integrity sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ== + dependencies: + image-q "^4.0.0" + omggif "^1.0.10" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globals@^15.9.0: + version "15.9.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.9.0.tgz#e9de01771091ffbc37db5714dab484f9f69ff399" + integrity sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA== + +globalthis@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hex-to-rgba@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/hex-to-rgba/-/hex-to-rgba-2.0.1.tgz#4176977882a1cb32b83ce5ab1db6828ab84d5a13" + integrity sha512-5XqPJBpsEUMsseJUi2w2Hl7cHFFi3+OO10M2pzAvKB1zL6fc+koGMhmBqoDOCB4GemiRM/zvDMRIhVw6EkB8dQ== + +highlight.js@^11.6.0: + version "11.10.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.10.0.tgz#6e3600dc4b33d6dc23d5bd94fbf72405f5892b92" + integrity sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ== + +htmlparser2@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" + integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.1.0" + entities "^4.5.0" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + +iconv-lite@0.6.3, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13, ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +image-q@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/image-q/-/image-q-4.0.0.tgz#31e075be7bae3c1f42a85c469b4732c358981776" + integrity sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw== + dependencies: + "@types/node" "16.9.1" + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0, is-core-module@^2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + +is-fullwidth-code-point@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz#9609efced7c2f97da7b60145ef481c787c7ba704" + integrity sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA== + dependencies: + get-east-asian-width "^1.0.0" + +is-function@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" + integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +is-stream@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-4.0.1.tgz#375cf891e16d2e4baec250b85926cffc14720d9b" + integrity sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +javascript-natural-sort@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== + +jimp@^0.16.0: + version "0.16.13" + resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.16.13.tgz#944b6368183235afc5d077429e2a7f34834acb18" + integrity sha512-Bxz8q7V4rnCky9A0ktTNGA9SkNFVWRHodddI/DaAWZJzF7sVUlFYKQ60y9JGqrKpi48ECA/TnfMzzc5C70VByA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/custom" "^0.16.13" + "@jimp/plugins" "^0.16.13" + "@jimp/types" "^0.16.13" + regenerator-runtime "^0.13.3" + +jpeg-js@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" + integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-5.0.0.tgz#e6b718f73da420d612823996fdf14a03f6ff6922" + integrity sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w== + dependencies: + universalify "^0.1.2" + optionalDependencies: + graceful-fs "^4.1.6" + +kareem@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.6.3.tgz#23168ec8ffb6c1abfd31b7169a6fb1dd285992ac" + integrity sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" + integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== + +lint-staged@^15.2.10: + version "15.2.10" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.10.tgz#92ac222f802ba911897dcf23671da5bb80643cd2" + integrity sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg== + dependencies: + chalk "~5.3.0" + commander "~12.1.0" + debug "~4.3.6" + execa "~8.0.1" + lilconfig "~3.1.2" + listr2 "~8.2.4" + micromatch "~4.0.8" + pidtree "~0.6.0" + string-argv "~0.3.2" + yaml "~2.5.0" + +listr2@~8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.4.tgz#486b51cbdb41889108cb7e2c90eeb44519f5a77f" + integrity sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g== + dependencies: + cli-truncate "^4.0.0" + colorette "^2.0.20" + eventemitter3 "^5.0.1" + log-update "^6.1.0" + rfdc "^1.4.1" + wrap-ansi "^9.0.0" + +load-bmfont@^1.3.1, load-bmfont@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.2.tgz#e0f4516064fa5be8439f9c3696c01423a64e8717" + integrity sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog== + dependencies: + buffer-equal "0.0.1" + mime "^1.3.4" + parse-bmfont-ascii "^1.0.3" + parse-bmfont-binary "^1.0.5" + parse-bmfont-xml "^1.1.4" + phin "^3.7.1" + xhr "^2.0.1" + xtend "^4.0.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.snakecase@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" + integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-update@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" + integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== + dependencies: + ansi-escapes "^7.0.0" + cli-cursor "^5.0.0" + slice-ansi "^7.1.0" + strip-ansi "^7.1.0" + wrap-ansi "^9.0.0" + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +magic-bytes.js@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz#c41cf4bc2f802992b05e64962411c9dd44fdef92" + integrity sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ== + +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +mathjs@^13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-13.1.1.tgz#72318c390ec3314e817b584a80152f6f10d7405d" + integrity sha512-duaSAy7m4F+QtP1Dyv8MX2XuxcqpNDDlGly0SdVTCqpAmwdOFWilDdQKbLdo9RfD6IDNMOdo9tIsEaTXkconlQ== + dependencies: + "@babel/runtime" "^7.25.4" + complex.js "^2.1.1" + decimal.js "^10.4.3" + escape-latex "^1.2.0" + fraction.js "^4.3.7" + javascript-natural-sort "^0.7.1" + seedrandom "^3.0.5" + tiny-emitter "^2.1.0" + typed-function "^4.2.1" + +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@~4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@^1.3.4: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +mimic-function@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" + integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== + dependencies: + dom-walk "^0.1.0" + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mongodb-connection-string-url@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz#c13e6ac284ae401752ebafdb8cd7f16c6723b141" + integrity sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg== + dependencies: + "@types/whatwg-url" "^11.0.2" + whatwg-url "^13.0.0" + +mongodb@6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.9.0.tgz#743ebfff6b3c14b04ac6e00a55e30d4127d3016d" + integrity sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA== + dependencies: + "@mongodb-js/saslprep" "^1.1.5" + bson "^6.7.0" + mongodb-connection-string-url "^3.0.0" + +mongoose@^8.6.3: + version "8.7.0" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.7.0.tgz#0f5377a4c3099702a5a00f5dccde2a092ca1c3b0" + integrity sha512-rUCSF1mMYQXjXYdqEQLLlMD3xbcj2j1/hRn+9VnVj7ipzru/UoUZxlj/hWmteKMAh4EFnDZ+BIrmma9l/0Hi1g== + dependencies: + bson "^6.7.0" + kareem "2.6.3" + mongodb "6.9.0" + mpath "0.9.0" + mquery "5.0.0" + ms "2.1.3" + sift "17.1.3" + +mpath@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904" + integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew== + +mquery@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-5.0.0.tgz#a95be5dfc610b23862df34a47d3e5d60e110695d" + integrity sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg== + dependencies: + debug "4.x" + +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nan@^2.17.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +nodemon@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.7.tgz#07cb1f455f8bece6a499e0d72b5e029485521a54" + integrity sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-check-updates@^17.1.3: + version "17.1.3" + resolved "https://registry.yarnpkg.com/npm-check-updates/-/npm-check-updates-17.1.3.tgz#7d7aefd073c95a2f29ccd1db9eea39e9f3be343a" + integrity sha512-4uDLBWPuDHT5KLieIJ20FoAB8yqJejmupI42wPyfObgQOBbPAikQSwT73afDwREvhuxYrRDqlRvxTMSfvO+L8A== + +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +omggif@^1.0.10, omggif@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" + integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== + +once@^1.3.0, once@^1.3.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +onetime@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" + integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + dependencies: + mimic-function "^5.0.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +os@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/os/-/os-0.1.2.tgz#f29a50c62908516ba42652de42f7038600cadbc2" + integrity sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ== + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +pako@^1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-bmfont-ascii@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz#11ac3c3ff58f7c2020ab22769079108d4dfa0285" + integrity sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA== + +parse-bmfont-binary@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz#d038b476d3e9dd9db1e11a0b0e53a22792b69006" + integrity sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA== + +parse-bmfont-xml@^1.1.4: + version "1.1.6" + resolved "https://registry.yarnpkg.com/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz#016b655da7aebe6da38c906aca16bf0415773767" + integrity sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA== + dependencies: + xml-parse-from-string "^1.0.0" + xml2js "^0.5.0" + +parse-headers@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" + integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + +parse5-parser-stream@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz#d7c20eadc37968d272e2c02660fff92dd27e60e1" + integrity sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow== + dependencies: + parse5 "^7.0.0" + +parse5@^7.0.0, parse5@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +peek-readable@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" + integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== + +peek-readable@^5.1.4: + version "5.2.0" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.2.0.tgz#7458f18126217c154938c32a185f5d05f3df3710" + integrity sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw== + +perf_hooks@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/perf_hooks/-/perf_hooks-0.0.1.tgz#253e7e18b71fcc0390fd3afb2cd7cf1685df040c" + integrity sha512-qG/D9iA4KDme+KF4vCObJy6Bouu3BlQnmJ8jPydVPm32NJBD9ZK1ZNgXSYaZKHkVC1sKSqUiLgFvAZPUiIEnBw== + +phin@^2.9.1: + version "2.9.3" + resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c" + integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA== + +phin@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/phin/-/phin-3.7.1.tgz#bf841da75ee91286691b10e41522a662aa628fd6" + integrity sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ== + dependencies: + centra "^2.7.0" + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pidtree@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + +pixelmatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854" + integrity sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA== + dependencies: + pngjs "^3.0.0" + +pngjs@^3.0.0, pngjs@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" + integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +punycode@^2.1.0, punycode@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +random-anime@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/random-anime/-/random-anime-1.0.6.tgz#022a3cf45f03b29cff1a0b9d21bfcecabc4e053d" + integrity sha512-wJzg6/51AfVQBZgIPEl0qcaFPacA2vq43o+iAn1AYt7b3i9r7fiIHbhbgFmN8YbzPhODZ91II0Eq8CkjcatBBw== + +random-seed@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd" + integrity sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA== + dependencies: + json-stringify-safe "^5.0.1" + +random-useragent@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/random-useragent/-/random-useragent-0.5.0.tgz#83a92dfdfb2366972d0a1f81fc93d5ef85807ae8" + integrity sha512-FUMkqVdZeoSff5tErNL3FFGYXElDWZ1bEuedhm5u9MdCFwANriJWbHvDRYrLTOzp/fBsBGu5J1cWtDgifa97aQ== + dependencies: + random-seed "^0.3.0" + +react-dom@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + +react@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-web-to-node-stream@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +regenerator-runtime@^0.13.3: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" + integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== + dependencies: + onetime "^7.0.0" + signal-exit "^4.1.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rfdc@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@>=0.6.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + +seedrandom@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + +semver@^6.0.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.5, semver@^7.5.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +sift@17.1.3: + version "17.1.3" + resolved "https://registry.yarnpkg.com/sift/-/sift-17.1.3.tgz#9d2000d4d41586880b0079b5183d839c7a142bf7" + integrity sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ== + +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55" + integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-markdown@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/simple-markdown/-/simple-markdown-0.7.3.tgz#e32150b2ec6f8287197d09869fd928747a9c5640" + integrity sha512-uGXIc13NGpqfPeFJIt/7SHHxd6HekEJYtsdoCM06mEBPL9fQH/pSD7LRM6PZ7CKchpSvxKL4tvwMamqAaNDAyg== + dependencies: + "@types/react" ">=16.0.0" + +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + +slice-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9" + integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg== + dependencies: + ansi-styles "^6.2.1" + is-fullwidth-code-point "^5.0.0" + +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + +string-argv@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strtok3@^6.2.4: + version "6.3.0" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" + integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^4.1.0" + +strtok3@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-8.1.0.tgz#9234a6f42ee03bf8569c7ae0788d5fd4e67e095b" + integrity sha512-ExzDvHYPj6F6QkSNe/JxSlBxTh3OrI6wrAIz53ulxo1c4hBJ1bT9C/JrAthEKHWG9riVH3Xzg7B03Oxty6S2Lw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^5.1.4" + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tar@^6.1.11: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +timm@^1.6.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/timm/-/timm-1.7.1.tgz#96bab60c7d45b5a10a8a4d0f0117c6b7e5aff76f" + integrity sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw== + +tiny-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + +tinycolor2@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +token-types@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" + integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + +token-types@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.0.0.tgz#1ab26be1ef9c434853500c071acfe5c8dd6544a3" + integrity sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + +touch@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== + +tr46@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" + integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== + dependencies: + punycode "^2.3.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-mixer@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" + integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.6.0, tslib@^2.6.2, tslib@^2.6.3: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + +twemoji-parser@14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62" + integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA== + +twemoji@^14.0.2: + version "14.0.2" + resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-14.0.2.tgz#c53adb01dab22bf4870f648ca8cc347ce99ee37e" + integrity sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA== + dependencies: + fs-extra "^8.0.1" + jsonfile "^5.0.0" + twemoji-parser "14.0.0" + universalify "^0.1.2" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + +typed-function@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.2.1.tgz#19aa51847aa2dea9ef5e7fb7641c060179a74426" + integrity sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA== + +uint8array-extras@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.4.0.tgz#e42a678a6dd335ec2d21661333ed42f44ae7cc74" + integrity sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +undici@6.19.8, undici@>=6.19.8, undici@^6.19.5: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.19.8.tgz#002d7c8a28f8cc3a44ff33c3d4be4d85e15d40e1" + integrity sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g== + +undici@^5.23.0: + version "5.28.4" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" + integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== + dependencies: + "@fastify/busboy" "^2.0.0" + +universalify@^0.1.0, universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +utif@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759" + integrity sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg== + dependencies: + pako "^1.0.5" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-13.0.0.tgz#b7b536aca48306394a34e44bda8e99f332410f8f" + integrity sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig== + dependencies: + tr46 "^4.1.1" + webidl-conversions "^7.0.0" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.14, which-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrap-ansi@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e" + integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.16.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +xhr@^2.0.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" + integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== + dependencies: + global "~4.4.0" + is-function "^1.0.1" + parse-headers "^2.0.0" + xtend "^4.0.0" + +xml-parse-from-string@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" + integrity sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g== + +xml2js@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" + integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@~2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" + integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==