├── .gitignore ├── config └── config.example.json ├── models ├── dataTicket.js └── dataGuild.js ├── package.json ├── LICENSE ├── events ├── interactionCreate.js ├── ticketPanel.js └── ticketButtons.js ├── commands ├── tickets │ ├── rename.js │ ├── open.js │ ├── claim.js │ ├── giveto.js │ ├── remove.js │ ├── add.js │ ├── alert.js │ ├── close.js │ └── ticket-manage.js └── general │ └── config.js ├── controllers ├── logger.js ├── ticketChecks.js └── paginationEmbed.js ├── handler └── index.js ├── README.md ├── index.js └── locales ├── en.json └── es.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config/config.json -------------------------------------------------------------------------------- /config/config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "TOKEN": "TOKEN_HERE", 3 | "GUILD-ID": "SERVER_ID", 4 | "MONGO_URI": "MONGO_URI_HERE", 5 | "LANGUAGE": "en" 6 | } -------------------------------------------------------------------------------- /models/dataTicket.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const dataTicket = new mongoose.Schema({ 4 | guildID: String, 5 | ownerID: String, 6 | channelName: String, 7 | channelID: String, 8 | ticketPanel: String, 9 | parentID: String, 10 | dateCreated: Date, 11 | isClosed: Boolean, 12 | isClaimed: Boolean, 13 | staffClaimed: String, 14 | staffRoles: Array, 15 | usersInTicket: Array, 16 | }); 17 | 18 | module.exports = mongoose.model('dataTicket', dataTicket, 'dataTicket'); -------------------------------------------------------------------------------- /models/dataGuild.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const guildData = new mongoose.Schema({ 4 | guildID: String, 5 | tickets: Array, 6 | ticketCounter: { 7 | type: Number, 8 | default: 0 9 | }, 10 | usersBlacklisted: Array, 11 | transcriptChannel: String, 12 | mentionStaff: String, 13 | staffRole: String, 14 | maxTickets: { 15 | type: Number, 16 | default: 1 17 | } 18 | }) 19 | 20 | module.exports = mongoose.model('guildData', guildData, "guildData") -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ticket-system", 3 | "version": "1.0.0", 4 | "description": "a simple ticket system using button :)", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "keywords": [], 10 | "author": "Jhoan#6969", 11 | "license": "ISC", 12 | "engines": { 13 | "node": "16.x" 14 | }, 15 | "dependencies": { 16 | "chalk": "^4.1.2", 17 | "discord-html-transcripts": "^1.0.0", 18 | "discord.js": "^13.6.0", 19 | "fs": "^0.0.1-security", 20 | "glob": "^7.1.7", 21 | "i18n": "^0.14.2", 22 | "mongoose": "^6.4.6" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jhoan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /events/interactionCreate.js: -------------------------------------------------------------------------------- 1 | const { error } = require("../controllers/logger"); 2 | const { havePerms } = require("../controllers/ticketChecks"); 3 | const client = require("../index"); 4 | 5 | client.on("interactionCreate", async (interaction) => { 6 | // Slash Command Handling 7 | if (interaction.isCommand()) { 8 | const command = client.slashCommands.get(interaction.commandName); 9 | if (!command) { 10 | error(`Command ${interaction.commandName} not found`); 11 | return interaction.reply({ content: client.languages.__mf("errors.command_not_found",{ 12 | command: interaction.commandName 13 | })}); 14 | } 15 | 16 | const args = []; 17 | 18 | for (let option of interaction.options.data) { 19 | if (option.type === "SUB_COMMAND") { 20 | if (option.name) args.push(option.name); 21 | option.options?.forEach((x) => { 22 | if (x.value) args.push(x.value); 23 | }); 24 | } else if (option.value) args.push(option.value); 25 | } 26 | 27 | interaction.member = interaction.guild.members.cache.get(interaction.user.id); 28 | if (!(await havePerms(interaction))) return; 29 | command.run(client, interaction, args); 30 | } 31 | }); -------------------------------------------------------------------------------- /commands/tickets/rename.js: -------------------------------------------------------------------------------- 1 | const { Client, CommandInteraction, MessageEmbed } = require("discord.js"); 2 | const { error } = require("../../controllers/logger"); 3 | const { isTicket } = require("../../controllers/ticketChecks"); 4 | 5 | module.exports = { 6 | name: "rename", 7 | description: "Renames a ticket chnanel.", 8 | type: 'CHAT_INPUT', 9 | options: [ 10 | { 11 | name: 'name', 12 | description: 'The new name of the ticket.', 13 | type: 'STRING', 14 | required: true 15 | } 16 | ], 17 | /** 18 | * 19 | * @param {Client} client 20 | * @param {CommandInteraction} interaction 21 | * @param {String[]} args 22 | */ 23 | run: async (client, interaction, args) => { 24 | const name = interaction.options.getString('name'); 25 | const ticketData = await isTicket(interaction); 26 | 27 | if (!ticketData) { 28 | return interaction.reply({embeds: [ 29 | new MessageEmbed() 30 | .setTitle("Ticket System \❌") 31 | .setDescription(client.languages.__("errors.channel_without_ticket")) 32 | .setColor("RED") 33 | ], ephemeral: true}); 34 | } 35 | 36 | interaction.channel.setName(name); 37 | 38 | ticketData.name = name; 39 | await ticketData.save(); 40 | 41 | return interaction.reply({embeds: [ 42 | new MessageEmbed() 43 | .setTitle("Ticket System \✅") 44 | .setDescription(client.languages.__mf("commands.rename.success", { 45 | new_name: name, 46 | old_name: interaction.channel.name, 47 | user_mention: `<@${interaction.user.id}>`, 48 | })) 49 | .setColor("GREEN") 50 | ], ephemeral: true}); 51 | }, 52 | }; -------------------------------------------------------------------------------- /controllers/logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | /** 4 | * 5 | * @param {String} message 6 | * @returns {void} 7 | */ 8 | function debug(message) { 9 | const date = new Date(); 10 | const dateString = `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; 11 | console.log(chalk.blue(`[${dateString}] [DEBUG] ${message}`)); 12 | } 13 | 14 | /** 15 | * 16 | * @param {String} message 17 | * @returns {void} 18 | */ 19 | function warn(message) { 20 | const date = new Date(); 21 | const dateString = `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; 22 | console.log(chalk.yellow(`[${dateString}] [WARN] ${message}`)); 23 | } 24 | 25 | /** 26 | * 27 | * @param {String} message 28 | * @returns {void} 29 | */ 30 | function error(message) { 31 | const date = new Date(); 32 | const dateString = `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; 33 | console.log(chalk.red(`[${dateString}] [ERROR] ${message}`)); 34 | } 35 | 36 | /** 37 | * 38 | * @param {String} message 39 | * @returns {void} 40 | */ 41 | function success(message) { 42 | const date = new Date(); 43 | const dateString = `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; 44 | console.log(chalk.green(`[${dateString}] [SUCCESS] ${message}`)); 45 | } 46 | 47 | module.exports = { 48 | debug, 49 | warn, 50 | error, 51 | success 52 | } -------------------------------------------------------------------------------- /commands/tickets/open.js: -------------------------------------------------------------------------------- 1 | const { CommandInteraction, MessageEmbed } = require("discord.js"); 2 | const { isTicket } = require("../../controllers/ticketChecks"); 3 | 4 | module.exports = { 5 | name: "open", 6 | description: "Opens a ticket", 7 | type: 'CHAT_INPUT', 8 | /** 9 | * 10 | * @param {import("../..").Bot} client 11 | * @param {CommandInteraction} interaction 12 | * @param {String[]} args 13 | */ 14 | run: async (client, interaction, args) => { 15 | const ticketData = await isTicket(interaction); 16 | if (!ticketData) { 17 | return interaction.reply({embeds: [ 18 | new MessageEmbed() 19 | .setTitle("Ticket System \❌") 20 | .setDescription(client.languages.__("errors.channel_without_ticket")) 21 | .setColor("RED") 22 | ], ephemeral: true}); 23 | } 24 | 25 | if (!ticketData.isClosed) { 26 | return interaction.reply({embeds: [ 27 | new MessageEmbed() 28 | .setTitle("Ticket System \❌") 29 | .setDescription(client.languages.__("errors.ticket_already_open")) 30 | .setColor("RED") 31 | ], ephemeral: true}); 32 | } 33 | 34 | interaction.channel.permissionOverwrites.edit(ticketData.ownerID, { 35 | VIEW_CHANNEL: true 36 | }); 37 | 38 | ticketData.usersInTicket.forEach((id) => { 39 | interaction.channel.permissionOverwrites.edit(id, { 40 | VIEW_CHANNEL: true 41 | }); 42 | }); 43 | 44 | ticketData.isClosed = false; 45 | await ticketData.save(); 46 | 47 | interaction.reply({embeds: [ 48 | new MessageEmbed() 49 | .setTitle("Ticket System \✅") 50 | .setDescription(client.languages.__mf("commands.open.opened_by", { 51 | user_mention: `<@${interaction.user.id}>`, 52 | user_tag: interaction.user.tag 53 | })) 54 | .setColor("GREEN") 55 | ]}); 56 | }, 57 | }; -------------------------------------------------------------------------------- /commands/tickets/claim.js: -------------------------------------------------------------------------------- 1 | const { CommandInteraction, MessageEmbed } = require("discord.js"); 2 | const { isTicket } = require("../../controllers/ticketChecks"); 3 | 4 | module.exports = { 5 | name: "claim", 6 | description: "Claim a ticket", 7 | type: 'CHAT_INPUT', 8 | /** 9 | * 10 | * @param {import("../..").Bot} client 11 | * @param {CommandInteraction} interaction 12 | * @param {String[]} args 13 | */ 14 | run: async (client, interaction, args) => { 15 | const ticketData = await isTicket(interaction); 16 | if (!ticketData) { 17 | return interaction.reply({embeds: [ 18 | new MessageEmbed() 19 | .setTitle("Ticket System \❌") 20 | .setDescription(client.languages.__("errors.channel_without_ticket")) 21 | .setColor("RED") 22 | ], ephemeral: true}); 23 | } 24 | 25 | if (ticketData.isClaimed) { 26 | return interaction.reply({embeds: [ 27 | new MessageEmbed() 28 | .setTitle("Ticket System \❌") 29 | .setDescription(client.languages.__mf("commands.claim.already_claimed", { 30 | user_mention: `<@!${ticketData.staffClaimed}>` 31 | })) 32 | .setColor("RED") 33 | ], ephemeral: true}); 34 | } 35 | 36 | ticketData.staffRoles.forEach((role) => { 37 | const staffRole = interaction.guild.roles.cache.get(role); 38 | if (staffRole) { 39 | interaction.channel.permissionOverwrites.edit(role, { 40 | VIEW_CHANNEL: false 41 | }); 42 | } 43 | }); 44 | 45 | ticketData.staffClaimed = interaction.user.id; 46 | ticketData.isClaimed = true; 47 | await ticketData.save(); 48 | 49 | interaction.reply({embeds: [ 50 | new MessageEmbed() 51 | .setTitle("Ticket System \✅") 52 | .setDescription(client.languages.__mf("commands.claim.claimed", { 53 | user_mention: `<@!${interaction.user.id}>`, 54 | user_tag: interaction.user.tag 55 | })) 56 | .setColor("GREEN") 57 | ], ephemeral: false}); 58 | }, 59 | }; -------------------------------------------------------------------------------- /handler/index.js: -------------------------------------------------------------------------------- 1 | const { glob } = require("glob"); 2 | const { promisify } = require("util"); 3 | const mongoose = require("mongoose"); 4 | const { success, error } = require("../controllers/logger"); 5 | 6 | const globPromise = promisify(glob); 7 | 8 | /** 9 | * @param {import("..").Bot} client 10 | */ 11 | module.exports = async (client) => { 12 | // Events 13 | const eventFiles = await globPromise(`${process.cwd()}/events/*.js`); 14 | eventFiles.map((value) => require(value)); 15 | 16 | // Slash Commands 17 | const slashCommands = await globPromise( 18 | `${process.cwd()}/commands/*/*.js` 19 | ); 20 | 21 | const arrayOfSlashCommands = []; 22 | slashCommands.map((value) => { 23 | const file = require(value); 24 | if (!file?.name) return; 25 | client.slashCommands.set(file.name, file); 26 | 27 | if (["MESSAGE", "USER"].includes(file.type)) delete file.description; 28 | arrayOfSlashCommands.push(file); 29 | }); 30 | 31 | client.on("ready", async () => { 32 | if (client.config["GUILD-ID"] === "SERVER_ID") { 33 | error("Please set the GUILD-ID in the config.json file"); 34 | process.exit(1); 35 | } 36 | const guild = client.guilds.cache.get(client.config["GUILD-ID"]); 37 | try { await guild.commands.set(arrayOfSlashCommands) } 38 | catch (error_) { 39 | error(error_); 40 | process.exit(1); 41 | } 42 | success(`Successfully loaded ${arrayOfSlashCommands.length} slash commands`); 43 | success(client.languages.__("system.bot_ready")); 44 | }); 45 | 46 | // mongoose 47 | const { MONGO_URI } = client.config; 48 | if (!MONGO_URI || MONGO_URI === "MONGO-CONNECTION-STRING-HERE") { 49 | return error(client.languages.__("errors.bad_mongo_uri")); 50 | } 51 | 52 | mongoose.connect(MONGO_URI).then(() => { 53 | success(client.languages.__("system.mongo_connected")); 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /commands/tickets/giveto.js: -------------------------------------------------------------------------------- 1 | const { Client, CommandInteraction, MessageEmbed } = require("discord.js"); 2 | const { isTicket } = require("../../controllers/ticketChecks"); 3 | 4 | module.exports = { 5 | name: "giveto", 6 | description: "Give a ticket to another staff member.", 7 | type: 'CHAT_INPUT', 8 | options: [ 9 | { 10 | name: 'user', 11 | description: 'The staff to give the ticket to.', 12 | type: 'USER', 13 | required: true 14 | } 15 | ], 16 | /** 17 | * 18 | * @param {import("../..").Bot} client 19 | * @param {CommandInteraction} interaction 20 | * @param {String[]} args 21 | */ 22 | run: async (client, interaction, args) => { 23 | const user = interaction.options.getUser('user'); 24 | const ticketData = await isTicket(interaction); 25 | if (!ticketData) { 26 | return interaction.reply({embeds: [ 27 | new MessageEmbed() 28 | .setTitle("Ticket System \❌") 29 | .setDescription(client.languages.__("errors.channel_without_ticket")) 30 | .setColor("RED") 31 | ], ephemeral: true}); 32 | } 33 | 34 | if (!ticketData.isClaimed) { 35 | return interaction.reply({embeds: [ 36 | new MessageEmbed() 37 | .setTitle("Ticket System \❌") 38 | .setDescription(client.languages.__("commands.giveto.ticket_not_claimed")) 39 | .setColor("RED") 40 | ], ephemeral: true}); 41 | } 42 | 43 | if (ticketData.staffClaimed !== interaction.user.id) { 44 | return interaction.reply({embeds: [ 45 | new MessageEmbed() 46 | .setTitle("Ticket System \❌") 47 | .setDescription(client.languages.__("commands.giveto.ticket_not_claimed_by_you")) 48 | .setColor("RED") 49 | ], ephemeral: true}); 50 | } 51 | 52 | interaction.channel.permissionOverwrites.edit(user.id, { 53 | VIEW_CHANNEL: true, 54 | MANAGE_CHANNELS: true, 55 | }); 56 | 57 | interaction.reply({embeds: [ 58 | new MessageEmbed() 59 | .setTitle("Ticket System \✅") 60 | .setDescription(client.languages.__mf("commands.giveto.ticket_given_to", { 61 | user_mention: `<@${user.id}>`, 62 | author_mention: `<@${interaction.user.id}>` 63 | })) 64 | .setColor("GREEN") 65 | ]}) 66 | }, 67 | }; -------------------------------------------------------------------------------- /commands/tickets/remove.js: -------------------------------------------------------------------------------- 1 | const { CommandInteraction, MessageEmbed } = require("discord.js"); 2 | const { isTicket } = require("../../controllers/ticketChecks"); 3 | 4 | module.exports = { 5 | name: "remove", 6 | description: "Remove a user from a ticket channel.", 7 | type: 'CHAT_INPUT', 8 | options: [ 9 | { 10 | name: "user", 11 | description: "The user to remove from the ticket.", 12 | type: "USER", 13 | required: true 14 | } 15 | ], 16 | /** 17 | * 18 | * @param {import("../..").Bot} client 19 | * @param {CommandInteraction} interaction 20 | * @param {String[]} args 21 | */ 22 | run: async (client, interaction, args) => { 23 | const user = interaction.options.getUser('user'); 24 | const ticketData = await isTicket(interaction); 25 | if (!ticketData) { 26 | return interaction.reply({embeds: [ 27 | new MessageEmbed() 28 | .setTitle("Ticket System \❌") 29 | .setDescription(client.languages.__("errors.channel_without_ticket")) 30 | .setColor("RED") 31 | ], ephemeral: true}); 32 | } 33 | if (!ticketData.usersInTicket.includes(user.id)) { 34 | return interaction.reply({embeds: [ 35 | new MessageEmbed() 36 | .setTitle("Ticket System \❌") 37 | .setDescription(client.languages.__mf("commands.remove.user_not_in_ticket", { 38 | user_mention: `<@${user.id}>`, 39 | user_tag: user.tag 40 | })) 41 | .setColor("RED") 42 | ], ephemeral: true}); 43 | } 44 | 45 | try { 46 | await interaction.channel.permissionOverwrites.delete(user.id); 47 | } catch { 48 | return interaction.reply({embeds: [ 49 | new MessageEmbed() 50 | .setTitle("Ticket System \❌") 51 | .setDescription("An error occured while removing the user from the ticket." + "```" + error + "```") 52 | .setColor("RED") 53 | ], ephemeral: true}); 54 | } 55 | 56 | ticketData.usersInTicket.splice(ticketData.usersInTicket.indexOf(user.id), 1); 57 | await ticketData.save(); 58 | 59 | return interaction.reply({embeds: [ 60 | new MessageEmbed() 61 | .setTitle("Ticket System \✅") 62 | .setDescription(client.languages.__mf("commands.remove.user_removed", { 63 | user_mention: `<@${user.id}>`, 64 | user_tag: user.tag 65 | })) 66 | .setColor("GREEN") 67 | ]}); 68 | }, 69 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Ticket Bot 🎫

2 | 3 | Ticket Bot is a discord bot focused on managing tickets channels, yes, it is free and 'open source', I have created Ticket Bot as an alternative to discord bots that charge you for premium functions! 4 | 5 | ## Features 🔥 6 | 7 | Here are some of the features of this system. 8 | 9 | 1. **Highly customisable** 10 | You can customize everything you want about the bot, its messages, embeds, buttons and more. 11 | 12 | 2. **Easy to configure** 13 | Very easy to configure, just use the /config command and it will display an embed with the buttons to configure the bot 14 | 15 | 3. **Multi server system** 16 | The bot is optimized to work with many servers at the same time, so if you own multiple servers you won't have to host a bot for each one 17 | 18 | 4. **Transcription system** 19 | When you close the ticket the bot will ask you if you want to save the transcript in a channel defined by you, if so the bot will send a ".html" file which will have a summary of all the messages that were sent in the channel 20 | 21 | ## Dependencies 🔗 22 | 23 | - VPS (Ubuntu, Debian, CentOS) 🐧 24 | - NodeJS V16+ 🛠 25 | - Discord Bot Token 🤖 26 | - MongoDB 🥭 27 | 28 | ## Installation 📦 29 | 30 | To install the bot, you must have a vps with the above requirements. (If you don't have a mongodb database, you can create one for free in [Mongo Atlas](https://www.mongodb.com/cloud/atlas).) 31 | 32 | Make sure you have **NodeJS v16+** installed. 33 | Remember to rename the file `config.example.json` to `config.json` 34 | 35 | ```sh 36 | git clone https://github.com/DevJhoan/ticket-bot.git 37 | cd ticket-bot 38 | npm install 39 | npm start 40 | ``` 41 | 42 | ## To-Do 🚧 43 | 44 | - [ ] Add support for more databases like (MySQL, FlatFile, etc) 45 | 46 | ## Support? 💁🏻 47 | If you don't understand something and/or want to ask something about the ticekt bot, you can enter our [Discord](https://strider.cloud/discord) 48 | 49 | ## Show your support 💙 50 | 51 | Give a ⭐️ if this project helped you! 52 | 53 | ## License 📄 54 | **Ticket Bot** is licensed under the [MIT License](https://github.com/DevJhoan/ticket-bot/blob/master/LICENSE) 55 | 56 | This is not an official Discord product. It is not affiliated with nor endorsed by Discord Inc. 57 | 58 | © 2022 - Jhoan M. 59 | -------------------------------------------------------------------------------- /commands/tickets/add.js: -------------------------------------------------------------------------------- 1 | const { CommandInteraction, MessageEmbed } = require("discord.js"); 2 | const { isTicket } = require("../../controllers/ticketChecks"); 3 | 4 | module.exports = { 5 | name: "add", 6 | description: "Add a user to a ticket channel.", 7 | type: 'CHAT_INPUT', 8 | options: [ 9 | { 10 | name: "user", 11 | description: "The user to add to the ticket.", 12 | type: "USER", 13 | required: true 14 | } 15 | ], 16 | /** 17 | * 18 | * @param {import("../..").Bot} client 19 | * @param {CommandInteraction} interaction 20 | * @param {String[]} args 21 | */ 22 | run: async (client, interaction, args) => { 23 | const user = interaction.options.getUser('user'); 24 | const ticketData = await isTicket(interaction); 25 | if (!ticketData) { 26 | return interaction.reply({embeds: [ 27 | new MessageEmbed() 28 | .setTitle("Ticket System \❌") 29 | .setDescription(client.languages.__("errors.channel_without_ticket")) 30 | .setColor("RED") 31 | ], ephemeral: true}); 32 | } 33 | if (ticketData.usersInTicket.includes(user.id)) { 34 | return interaction.reply({embeds: [ 35 | new MessageEmbed() 36 | .setTitle("Ticket System \❌") 37 | .setDescription(client.languages.__mf("commands.add.user_already_in_ticket", { 38 | user_mention: `<@${user.id}>`, 39 | user_tag: user.tag 40 | })) 41 | .setColor("RED") 42 | ], ephemeral: true}); 43 | } 44 | 45 | try { 46 | await interaction.channel.permissionOverwrites.edit(user.id, { 47 | VIEW_CHANNEL: true, 48 | SEND_MESSAGES: true, 49 | ADD_REACTIONS: true, 50 | ATTACH_FILES: true, 51 | EMBED_LINKS: true 52 | }); 53 | } catch (error) { 54 | return interaction.reply({embeds: [ 55 | new MessageEmbed() 56 | .setTitle("Ticket System \❌") 57 | .setDescription("An error occured while adding the user to the ticket.\n" + "```" + error + "```") 58 | .setColor("RED") 59 | ], ephemeral: true}); 60 | } 61 | 62 | ticketData.usersInTicket.push(user.id); 63 | ticketData.save(); 64 | 65 | return interaction.reply({embeds: [ 66 | new MessageEmbed() 67 | .setTitle("Ticket System \✅") 68 | .setDescription(client.languages.__mf("commands.add.success", { 69 | user_mention: `<@${user.id}>`, 70 | user_tag: user.tag 71 | })) 72 | .setColor("GREEN") 73 | ]}); 74 | }, 75 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { warn, error, debug } = require("./controllers/logger"); 2 | const { Client, Collection } = require("discord.js"); 3 | const { version } = require('./package.json'); 4 | const { readdirSync } = require("fs"); 5 | const { join } = require("path"); 6 | 7 | if (process.version.slice(1).split(".")[0] < 16) { 8 | error(`Please update to Node 16 or higher.`); 9 | process.exit(1); 10 | } 11 | 12 | /** 13 | * The Discord client instance 14 | * @typedef {Bot} Bot 15 | * @extends {Client} 16 | */ 17 | class Bot extends Client { 18 | constructor() { 19 | super({ 20 | intents: 32767 21 | }); 22 | 23 | const locales = []; 24 | readdirSync(join(__dirname, 'locales')) 25 | .filter(file => file.endsWith('.json')) 26 | .forEach((file) => { 27 | locales.push(file.replace('.json', '')) 28 | }); 29 | 30 | this.commands = new Collection(); 31 | debug(`Successfully loaded ${locales.length} locales`); 32 | this.slashCommands = new Collection(); 33 | this.config = require('./config/config.json'); 34 | debug(`Successfully loaded config`); 35 | this.languages = require('i18n'); 36 | debug(`Successfully loaded languages`); 37 | 38 | this.languages.configure({ 39 | locales: locales, 40 | directory: join(__dirname, 'locales'), 41 | defaultLocale: 'en', 42 | retryInDefaultLocale: true, 43 | objectNotation: true, 44 | register: global, 45 | 46 | logWarnFn: function(msg) { 47 | warn(msg); 48 | }, 49 | 50 | logErrorFn: function(msg) { 51 | error(msg); 52 | }, 53 | 54 | missingKeyFn: function(locale, key) { 55 | return key; 56 | }, 57 | 58 | mustacheConfig: { 59 | tags: ["{{", "}}"], 60 | disable: false 61 | } 62 | }); 63 | this.languages.setLocale(this.config.LANGUAGE); 64 | debug(`Successfully set language to ${this.config.LANGUAGE}`); 65 | this.version = version; 66 | } 67 | }; 68 | 69 | const client = new Bot(); 70 | module.exports = client; 71 | 72 | // Initializing the project 73 | require("./handler")(client); 74 | 75 | client.login(client.config.TOKEN); -------------------------------------------------------------------------------- /controllers/ticketChecks.js: -------------------------------------------------------------------------------- 1 | const { CommandInteraction, MessageEmbed } = require("discord.js"); 2 | const dataGuild = require("../models/dataGuild"); 3 | const dataTicket = require("../models/dataTicket"); 4 | 5 | /** 6 | * 7 | * @param {CommandInteraction} interaction 8 | * @returns {Promise<{ 9 | * guildID: String, 10 | * ownerID: String, 11 | * channelName: String, 12 | * channelID: String, 13 | * ticketPanel: String, 14 | * parentID: String, 15 | * dateCreated: Date, 16 | * isClosed: Boolean, 17 | * isClaimed: Boolean, 18 | * staffClaimed: String, 19 | * staffRoles: Array, 20 | * usersInTicket: Array, 21 | * save: Function, 22 | * remove: Function, 23 | * delete: Function 24 | * }> | boolean} 25 | */ 26 | async function isTicket(interaction) { 27 | const userData = await dataTicket.findOne({ 28 | channelID: interaction.channel.id 29 | }); 30 | if (!userData) { 31 | return false; 32 | } else { 33 | return userData; 34 | } 35 | } 36 | 37 | /** 38 | * 39 | * @param {CommandInteraction} interaction 40 | * @returns {Promise<{Boolean}>} 41 | */ 42 | async function havePerms(interaction) { 43 | const guildData = await dataGuild.findOne({ 44 | guildID: interaction.guild.id 45 | }); 46 | if (!guildData && !interaction.member.permissions.has("ADMINISTRATOR")) { 47 | interaction.reply({embeds: [ 48 | new MessageEmbed() 49 | .setTitle("Ticket System \❌") 50 | .setDescription(interaction.client.languages.__("errors.server_without_tickets")) 51 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: interaction.client.user.displayAvatarURL()}) 52 | .setColor("RED") 53 | ]}); 54 | return false; 55 | } 56 | if (!guildData?.staffRole && !interaction.member.permissions.has("ADMINISTRATOR")) { 57 | interaction.reply({embeds: [ 58 | new MessageEmbed() 59 | .setTitle("Ticket System \❌") 60 | .setDescription(interaction.client.languages.__("errors.no_staff_role")) 61 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: interaction.client.user.displayAvatarURL()}) 62 | .setColor("RED") 63 | ]}); 64 | return false; 65 | } 66 | 67 | if (!interaction.member.roles.cache.has(guildData?.staffRole) && !interaction.member.permissions.has("ADMINISTRATOR")) { 68 | interaction.reply({embeds: [ 69 | new MessageEmbed() 70 | .setTitle("Ticket System \❌") 71 | .setDescription(interaction.client.languages.__("errors.no_permission")) 72 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: interaction.client.user.displayAvatarURL()}) 73 | .setColor("RED") 74 | ]}); 75 | return false; 76 | } 77 | return true; 78 | } 79 | 80 | module.exports = { 81 | isTicket, 82 | havePerms 83 | } 84 | -------------------------------------------------------------------------------- /commands/tickets/alert.js: -------------------------------------------------------------------------------- 1 | const { Client, CommandInteraction, MessageEmbed } = require("discord.js"); 2 | const { error } = require("../../controllers/logger"); 3 | const { isTicket } = require("../../controllers/ticketChecks"); 4 | 5 | module.exports = { 6 | name: "alert", 7 | description: "Alert a user to respond to their ticket", 8 | type: 'CHAT_INPUT', 9 | /** 10 | * 11 | * @param {Client} client 12 | * @param {CommandInteraction} interaction 13 | * @param {String[]} args 14 | */ 15 | run: async (client, interaction, args) => { 16 | const ticketData = await isTicket(interaction); 17 | if (!ticketData) { 18 | return interaction.reply({embeds: [ 19 | new MessageEmbed() 20 | .setTitle("Ticket System \❌") 21 | .setDescription(client.languages.__("errors.channel_without_ticket")) 22 | .setColor("RED") 23 | ], ephemeral: true}); 24 | } 25 | 26 | const user = interaction.guild.members.cache.get(ticketData.ownerID); 27 | if (!user) { 28 | error(`Could not find user with ID ${ticketData.ownerID}`); 29 | return interaction.reply({embeds: [ 30 | new MessageEmbed() 31 | .setTitle("Ticket System \❌") 32 | .setDescription("User not found.") 33 | .setColor("RED") 34 | ], ephemeral: true}); 35 | } 36 | 37 | try { 38 | await user.send({embeds: [ 39 | new MessageEmbed() 40 | .setTitle("Ticket System \✅") 41 | .setDescription(client.languages.__mf("commands.alert.user_message", { 42 | user_mention: `<@${ticketData.ownerID}>`, 43 | user_tag: user.user.tag, 44 | channel_id: ticketData.channelID, 45 | channel_name: interaction.channel.name, 46 | link: `https://discordapp.com/channels/${interaction.guild.id}/${ticketData.channelID}`, 47 | direct_link: `[Direct Link](https://discordapp.com/channels/${interaction.guild.id}/${ticketData.channelID})`, 48 | openSince: `` 49 | })) 50 | .setColor("GREEN") 51 | ]}); 52 | } catch (error_) { 53 | error(error_); 54 | return interaction.reply({embeds: [ 55 | new MessageEmbed() 56 | .setTitle("Ticket System \❌") 57 | .setDescription("Could not send DM to user.") 58 | .setColor("RED") 59 | ], ephemeral: true}); 60 | } 61 | 62 | return interaction.reply({embeds: [ 63 | new MessageEmbed() 64 | .setTitle("Ticket System \✅") 65 | .setDescription(client.languages.__mf("commands.alert.staff_message", { 66 | user_mention: `<@${interaction.user.id}>`, 67 | channel_id: ticketData.channelID, 68 | direct_link: `[Direct Link](https://discordapp.com/channels/${interaction.guild.id}/${ticketData.channelID})`, 69 | })) 70 | .setColor("GREEN") 71 | ]}); 72 | }, 73 | }; -------------------------------------------------------------------------------- /commands/tickets/close.js: -------------------------------------------------------------------------------- 1 | const { CommandInteraction, MessageEmbed, MessageActionRow, MessageButton } = require("discord.js"); 2 | const { isTicket } = require("../../controllers/ticketChecks"); 3 | 4 | module.exports = { 5 | name: "close", 6 | description: "Close a ticket.", 7 | type: 'CHAT_INPUT', 8 | /** 9 | * 10 | * @param {import("../..").Bot} client 11 | * @param {CommandInteraction} interaction 12 | * @param {String[]} args 13 | */ 14 | run: async (client, interaction, args) => { 15 | const ticketData = await isTicket(interaction); 16 | if (!ticketData) { 17 | return interaction.reply({embeds: [ 18 | new MessageEmbed() 19 | .setTitle("Ticket System \❌") 20 | .setDescription(client.languages.__("errors.channel_without_ticket")) 21 | .setColor("RED") 22 | ], ephemeral: true}); 23 | } 24 | 25 | if (ticketData.isClosed) { 26 | return interaction.reply({embeds: [ 27 | new MessageEmbed() 28 | .setTitle("Ticket System \❌") 29 | .setDescription(client.languages.__("errors.ticket_already_closed")) 30 | .setColor("RED") 31 | ], ephemeral: true}); 32 | } 33 | 34 | interaction.channel.permissionOverwrites.edit(ticketData.ownerID, { 35 | VIEW_CHANNEL: false 36 | }); 37 | 38 | ticketData.usersInTicket.forEach(async (id) => { 39 | interaction.channel.permissionOverwrites.edit(id, { 40 | VIEW_CHANNEL: false 41 | }); 42 | }); 43 | 44 | ticketData.isClosed = true; 45 | await ticketData.save(); 46 | 47 | interaction.reply({embeds: [ 48 | new MessageEmbed() 49 | .setTitle("Ticket System \✅") 50 | .setDescription(client.languages.__mf("commands.close.closed_by", { 51 | user_mention: `<@${interaction.user.id}>`, 52 | user_tag: interaction.user.tag 53 | })) 54 | .setColor("GREEN"), 55 | new MessageEmbed() 56 | .setDescription(client.languages.__("buttons.close.messages.closed_ticket_staff")) 57 | .setColor("#2f3136") 58 | ], components: [ 59 | new MessageActionRow().addComponents( 60 | // transcript, open, delete 61 | new MessageButton() 62 | .setCustomId("btn-transcript-ticket") 63 | .setLabel(client.languages.__("buttons.transcript.text")) 64 | .setEmoji(client.languages.__("buttons.transcript.emoji")) 65 | .setStyle(client.languages.__("buttons.transcript.style")), 66 | new MessageButton() 67 | .setCustomId("btn-open-ticket") 68 | .setLabel(client.languages.__("buttons.open.text")) 69 | .setEmoji(client.languages.__("buttons.open.emoji")) 70 | .setStyle(client.languages.__("buttons.open.style")), 71 | new MessageButton() 72 | .setCustomId("btn-delete-ticket") 73 | .setLabel(client.languages.__("buttons.delete.text")) 74 | .setEmoji(client.languages.__("buttons.delete.emoji")) 75 | .setStyle(client.languages.__("buttons.delete.style")) 76 | ) 77 | ]}); 78 | }, 79 | }; -------------------------------------------------------------------------------- /controllers/paginationEmbed.js: -------------------------------------------------------------------------------- 1 | const { 2 | MessageActionRow, 3 | MessageButton, 4 | MessageEmbed, 5 | CommandInteraction 6 | } = require("discord.js") 7 | const ms = require("ms") 8 | 9 | /** 10 | * 11 | * @param {CommandInteraction} interaction 12 | * @param {String[]} embeds 13 | * @param {ms} timeout 14 | * @param {Boolean} ephemeral 15 | * @returns Void 16 | */ 17 | module.exports = async (interaction, embeds, timeout, ephemeral = false) => { 18 | 19 | if (embeds.length <= 0) return interaction.reply({embeds: [ 20 | new MessageEmbed() 21 | .setTitle("No embeds to paginate!") 22 | .setColor("RED") 23 | ]}); 24 | 25 | if (embeds.length === 1) return interaction.reply({embeds: [embeds[0]]}); 26 | 27 | let current = 0; 28 | let emojis = ["⏪", "Previous", "Next", "⏩"]; 29 | const row = (state) => [ 30 | new MessageActionRow().addComponents( 31 | new MessageButton() 32 | .setEmoji(emojis[0]) 33 | .setDisabled(state) 34 | .setStyle("SECONDARY") 35 | .setCustomId("btn1"), 36 | new MessageButton() 37 | .setLabel(emojis[1]) 38 | .setDisabled(state) 39 | .setStyle("PRIMARY") 40 | .setCustomId("btn2"), 41 | new MessageButton() 42 | .setLabel("Delete") 43 | .setDisabled(state) 44 | .setStyle('DANGER') 45 | .setCustomId("btnx"), 46 | new MessageButton() 47 | .setLabel(emojis[2]) 48 | .setDisabled(state) 49 | .setStyle("PRIMARY") 50 | .setCustomId("btn3"), 51 | new MessageButton() 52 | .setEmoji(emojis[3]) 53 | .setDisabled(state) 54 | .setStyle("SECONDARY") 55 | .setCustomId("btn4") 56 | ) 57 | ]; 58 | 59 | const curPage = await interaction.reply({ 60 | embeds: [embeds[current]], 61 | components: row(false), 62 | fetchReply: true, 63 | ephemeral, 64 | }).catch((x) => {throw x}); 65 | 66 | const collector = curPage.createMessageComponentCollector({ 67 | filter: (m) => m.user.id === interaction.member.id, 68 | componentType: "BUTTON", 69 | time: ms(timeout) 70 | }); 71 | 72 | collector.on("collect", async (collected) => { 73 | if (collected.customId === "btn1") current = 0 74 | else if (collected.customId === "btn2") current-- 75 | else if (collected.customId === "btn3") current++ 76 | else if (collected.customId === "btn4") current = embeds.length - 1 77 | else if (collected.customId === "btnx") collector.stop(); 78 | 79 | if (current < 0) current = embeds.length - 1 80 | if (current >= embeds.length) current = 0 81 | 82 | curPage.edit({ 83 | embeds: [embeds[current]], 84 | ephemeral 85 | }).catch(() => { }); 86 | 87 | collected.deferUpdate(); 88 | }); 89 | 90 | collector.on("end", async () => { 91 | curPage.edit({ 92 | embeds: [embeds[current].setColor("RED")], 93 | components: row(true), 94 | ephemeral 95 | }).catch((x) => { throw x }); 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /events/ticketPanel.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed, TextChannel, MessageActionRow, MessageButton } = require("discord.js"); 2 | const client = require("../index"); 3 | const dataGuild = require("../models/dataGuild"); 4 | const dataTicket = require("../models/dataTicket"); 5 | 6 | client.on("interactionCreate", async (interaction) => { 7 | if (interaction.isButton()) { 8 | const isTicket = interaction.customId.split("-")[0] === "ticket"; 9 | if (isTicket) { 10 | const buttonID = interaction.customId.split("-")[1]; 11 | const guildData = await dataGuild.findOne({ guildID: interaction.guild.id }); 12 | if (!guildData) { 13 | return interaction.followUp({embeds: [ 14 | new MessageEmbed() 15 | .setTitle("Ticket System \❌") 16 | .setDescription(client.languages.__("errors.server_without_tickets")) 17 | .setColor("RED") 18 | ]}); 19 | } 20 | if (!guildData.tickets || guildData.tickets.length <= 0) { 21 | return interaction.followUp({embeds: [ 22 | new MessageEmbed() 23 | .setTitle("Ticket System \❌") 24 | .setDescription(client.languages.__("errors.server_without_tickets")) 25 | .setColor("RED") 26 | ]}); 27 | } 28 | const guildTickets = guildData.tickets.map(ticket => ticket.customID); 29 | if (!guildTickets.includes(buttonID)) return; 30 | const ticketData = guildData.tickets.find((x) => { 31 | return x.customID === buttonID; 32 | }); 33 | const ticketRoles = ticketData.panelRoles.map((role_id) => { 34 | return { 35 | id: role_id, 36 | allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ADD_REACTIONS", "ATTACH_FILES", "EMBED_LINKS"] 37 | } 38 | }); 39 | 40 | await interaction.reply({embeds: [ 41 | new MessageEmbed() 42 | .setTitle("Ticket System \✅") 43 | .setDescription(client.languages.__("embeds.message_ticket.creating")) 44 | .setColor("ORANGE") 45 | ], ephemeral: true}); 46 | 47 | const userTickets = await dataTicket.find({ 48 | guildID: interaction.guild.id, 49 | ownerID: interaction.user.id 50 | }); 51 | 52 | if (userTickets.length >= guildData.maxTickets) { 53 | return interaction.editReply({embeds: [ 54 | new MessageEmbed() 55 | .setTitle("Ticket System \❌") 56 | .setDescription(client.languages.__("errors.reached_max_tickets")) 57 | .setColor("RED") 58 | ]}); 59 | } 60 | const ticketNumber = await getTicketNumber(guildData.ticketCounter, dataGuild, interaction.guild.id); 61 | await interaction.guild.channels.create(`ticket-${ticketNumber}`, { 62 | type: "text", 63 | parent: ticketData.panelCategory, 64 | permissionOverwrites: [ 65 | { 66 | id: interaction.guild.id, 67 | deny: ["VIEW_CHANNEL"] 68 | }, 69 | { 70 | id: interaction.user.id, 71 | allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ADD_REACTIONS", "ATTACH_FILES", "EMBED_LINKS"] 72 | }, 73 | ...ticketRoles 74 | ] 75 | }).then(async (channel) => { 76 | channel.send({embeds: [ 77 | new MessageEmbed() 78 | .setTitle(client.languages.__mf("embeds.message_ticket.title", { 79 | ticket_number: ticketNumber, 80 | panel_name: ticketData.panelName 81 | })) 82 | .setDescription(client.languages.__mf("embeds.message_ticket.description", { 83 | ticket_number: ticketNumber, 84 | ticket_date: ``, 85 | panel_name: ticketData.panelName, 86 | member_username: interaction.user.username, 87 | member_mention: `<@${interaction.user.id}>`, 88 | })) 89 | .setColor(client.languages.__("embeds.message_ticket.color")) 90 | ], components: [ 91 | new MessageActionRow().addComponents( 92 | new MessageButton() 93 | .setLabel(client.languages.__("buttons.close.text")) 94 | .setEmoji(client.languages.__("buttons.close.emoji")) 95 | .setStyle(client.languages.__("buttons.close.style")) 96 | .setCustomId("btn-close-ticket-opn"), 97 | new MessageButton() 98 | .setLabel(client.languages.__("buttons.claim.text")) 99 | .setEmoji(client.languages.__("buttons.claim.emoji")) 100 | .setStyle(client.languages.__("buttons.claim.style")) 101 | .setCustomId("btn-claim-ticket-opn") 102 | ) 103 | ], content: guildData.mentionStaff ? `<@!${interaction.user.id}> | <@&${guildData.mentionStaff}>` : `<@!${interaction.user.id}>`}); 104 | 105 | const newTicket = new dataTicket({ 106 | guildID: interaction.guild.id, 107 | ownerID: interaction.user.id, 108 | channelName: channel.name, 109 | channelID: channel.id, 110 | ticketPanel: ticketData.panelName, 111 | parentID: ticketData.panelCategory, 112 | dateCreated: Date.now(), 113 | isClosed: false, 114 | isClaimed: false, 115 | staffClaimed: null, 116 | staffRoles: ticketRoles.map(x => x.id), 117 | }); 118 | await newTicket.save(); 119 | 120 | interaction.editReply({embeds: [ 121 | new MessageEmbed() 122 | .setTitle("Ticket System \✅") 123 | .setDescription(client.languages.__mf("embeds.message_ticket.created", { 124 | channel_mention: `<#${channel.id}>`, 125 | channel_id: channel.id 126 | })) 127 | .setColor("GREEN") 128 | ]}) 129 | }); 130 | } 131 | } 132 | }); 133 | 134 | async function getTicketNumber(ticketCounter, guildData, guildID) { 135 | await guildData.findOneAndUpdate({ guildID: guildID }, { $inc: { ticketCounter: 1 } }); 136 | const data = await dataGuild.findOne({ guildID: guildID }); 137 | const zeroPad = (num, places) => String(num).padStart(places, '0'); 138 | return zeroPad(data.ticketCounter, 4); 139 | } -------------------------------------------------------------------------------- /locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "ticket_manage": { 4 | "no_specify": "Please specify a sub-command.", 5 | "sub_commands": { 6 | "setup": { 7 | "already_exists": "A ticket panel with that `{custom_id}` ID already exists.", 8 | "created": "Ticket panel setup successfully.\n\n{panel_info}" 9 | }, 10 | "delete": { 11 | "not_exists": "A ticket panel with that `{custom_id}` ID does not exist.", 12 | "deleted": "Ticket panel deleted successfully." 13 | }, 14 | "list": { 15 | "no_panels": "There are no ticket panels setup.", 16 | "description": "**Here is the list of all the panels:**", 17 | "footer": "Currently on page {page} of {pages}." 18 | }, 19 | "send": { 20 | "no_panels": "There are no ticket panels setup.", 21 | "send_success": "The ticket panel was successfully sent!", 22 | "embed_config": { 23 | "separator": "{counter}: {name} - {emoji}", 24 | "title": "Support System | Create Ticket", 25 | "description": "By clicking one of the corresponding button categories on this message, you will create a ticket with direct and private access to the staff.\n\n__**Support Categories**__\n{separator}\n\n__**Ticket Information**__\n`•` Before creating a ticket read the proper **FAQ** Channels.\n`•` Abuse of the ticket system will lead to **punishment(s)**", 26 | "footer": "Please use the buttons below to create a ticket.", 27 | "color": "AQUA" 28 | } 29 | } 30 | } 31 | }, 32 | "add": { 33 | "success": "The user {user_mention} was added to the ticket.", 34 | "user_already_in_ticket": "{user_mention} is already in this ticket." 35 | }, 36 | "remove": { 37 | "user_removed": "The user {user_mention} was removed from the ticket.", 38 | "user_not_in_ticket": "{user_mention} is not in this ticket." 39 | }, 40 | "close": { 41 | "closed_by": "{user_mention} has closed the ticket." 42 | }, 43 | "open": { 44 | "opened_by": "{user_mention} has opened the ticket." 45 | }, 46 | "rename": { 47 | "success": "{user_mention} has renamed the ticket to {new_name}." 48 | }, 49 | "alert": { 50 | "user_message": "Hello {user_mention} you have an open [ticket]({link}) and a staff member needs you to respond to it immediately\n\n**Ticket Information**\n » Ticket Name: {channel_name}\n » Ticket Since: {openSince}\n » Ticket Link: {direct_link}", 51 | "staff_message": "I just sent a message to the user, please wait for him to reply" 52 | }, 53 | "claim": { 54 | "already_claimed": "This ticket is already claimed by {user_mention}.", 55 | "claimed": "This ticket is now claimed by {user_mention}." 56 | }, 57 | "giveto": { 58 | "ticket_not_claimed": "This ticket is not claimed.", 59 | "ticket_not_claimed_by_you": "This ticket is not claimed by you.", 60 | "ticket_given_to": "{author_mention} has given the ticket to {user_mention}." 61 | } 62 | }, 63 | "embeds": { 64 | "message_ticket": { 65 | "creating": "Creating ticket...", 66 | "created": "Ticket created successfully {channel_mention}!", 67 | "title": "Ticket Created | {panel_name} #{ticket_number}", 68 | "description": "Hello {member_username}, our staff will answer you as quickly as possible.\nIn the meantime, please describe your problem here in as much detail as possible.\n\n**Ticket Panel:** {panel_name}\n**Ticket Owner:** {member_mention}\n**Ticket Date:** {ticket_date}", 69 | "color": "AQUA" 70 | } 71 | }, 72 | "buttons": { 73 | "close": { 74 | "text": "Close", 75 | "emoji": "🔒", 76 | "style": "SECONDARY", 77 | "messages": { 78 | "closed_ticket": "{user_mention} has closed the ticket.", 79 | "closed_ticket_staff": "```Support team ticket controls```" 80 | } 81 | }, 82 | "claim": { 83 | "text": "Claim", 84 | "emoji": "👋", 85 | "style": "SECONDARY", 86 | "messages": { 87 | "claimed_ticket": "{user_mention} has claimed the ticket." 88 | } 89 | }, 90 | "transcript": { 91 | "text": "Transcript", 92 | "emoji": "📑", 93 | "style": "SECONDARY", 94 | "messages": { 95 | "saving_transcript": "Saving transcript...", 96 | "transcript_saved": "Transcript saved successfully!" 97 | } 98 | }, 99 | "open": { 100 | "text": "Open", 101 | "emoji": "🔓", 102 | "style": "SECONDARY", 103 | "messages" : { 104 | "ticket_opened": "{user_mention} has opened the ticket." 105 | } 106 | }, 107 | "delete": { 108 | "text": "Delete", 109 | "emoji": "⛔", 110 | "style": "SECONDARY", 111 | "messages": { 112 | "deleting_ticket": "This ticket will be deleted in {time} seconds." 113 | } 114 | } 115 | }, 116 | "system": { 117 | "bot_ready": "Bot is ready", 118 | "mongo_connected": "MongoDB connected" 119 | }, 120 | "errors": { 121 | "bad_mongo_uri": "MONGO_URI is not set in config.json", 122 | "command_not_found": "The {command} command was not found", 123 | "server_without_tickets": "This server no have data for the ticket system.\nPlease create a ticket panel using the command `ticket-manage setup`", 124 | "reached_max_tickets": "You have reached the maximum amount of tickets opened at the same time.", 125 | "channel_without_ticket": "This channel is not a ticket channel.", 126 | "ticket_already_closed": "This ticket is already closed.", 127 | "ticket_already_claimed": "This ticket is already claimed by someone else.", 128 | "transcript_channel_not_found": "The transcript channel was not found.", 129 | "ticket_already_open": "This ticket is already open.", 130 | "ticket_not_closed": "This ticket is not closed.", 131 | "no_staff_role": "This server has no set up a staff role.\nPlease use `config` command to setup the staff role.", 132 | "no_permission": "You don't have permission to use this command." 133 | } 134 | } -------------------------------------------------------------------------------- /locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "ticket_manage": { 4 | "no_specify": "No se especificó ningún comando.", 5 | "sub_commands": { 6 | "setup": { 7 | "already_exists": "Ya existe un ticket panel con el ID `{custom_id}`.", 8 | "created": "Configuración del panel de tickets con éxito.\n\n{panel_info}" 9 | }, 10 | "delete": { 11 | "not_exists": "No existe un ticket panel con el ID `{custom_id}`.", 12 | "deleted": "El ticket panel se eliminó con éxito." 13 | }, 14 | "list": { 15 | "no_panels": "No hay paneles de tickets configurados.", 16 | "description": "**Aquí está una lista de todos los paneles:**", 17 | "footer": "Actualmente está en la página {page} de {pages}." 18 | }, 19 | "send": { 20 | "no_panels": "No hay paneles de tickets configurados.", 21 | "send_success": "El ticket panel se envió con éxito.", 22 | "embed_config": { 23 | "separator": "{counter}: {name} - {emoji}", 24 | "title": "Sistema de soporte | Crear un ticket", 25 | "description": "Al hacer clic en una de las categorías de botones correspondientes en este mensaje, creará un ticket con acceso directo y privado al personal.\n\n__**Categorias de Soporte**__\n{separator}\n\n__**Informacion de Tickets**__\n`•` Antes de crear un ticket, lea los cnales **FAQ** correspondientes.\n`•` El abuso del sistema de tickets dará lugar a **castigo(s)**", 26 | "footer": "Utilice los botones a continuación para crear un boleto.", 27 | "color": "AQUA" 28 | } 29 | } 30 | } 31 | }, 32 | "add": { 33 | "success": "El usuario {user_mention} fue agregado con éxito.", 34 | "user_already_in_ticket": "{user_mention} ya está en el ticket." 35 | }, 36 | "remove": { 37 | "user_removed": "El usuario {user_mention} fue removido con éxito.", 38 | "user_not_in_ticket": "{user_mention} no está en el ticket." 39 | }, 40 | "close": { 41 | "closed_by": "{user_mention} ha cerrado el ticket." 42 | }, 43 | "open": { 44 | "opened_by": "{user_mention} ha abierto el ticket." 45 | }, 46 | "rename": { 47 | "success": "{user_mention} has renamed the ticket to {new_name}." 48 | }, 49 | "alert": { 50 | "user_message": "Hola, {user_mention}, tienes un [ticket]({link}) abierto y un miembro del personal necesita que lo respondas de inmediato\n\n**Información del ticket**\n » Nombre del ticket: {channel_name}\n » Ticket Desde: {openSince}\n » Enlace del ticket: {direct_link}", 51 | "staff_message": "Acabo de enviar un mensaje al usuario, espere a que responda" 52 | }, 53 | "claim": { 54 | "already_claimed": "El ticket ya ha sido reclamado por {user_mention}.", 55 | "claimed": "El ticket fue reclamado por {user_mention}." 56 | }, 57 | "giveto": { 58 | "ticket_not_claimed": "El ticket no ha sido reclamado.", 59 | "ticket_not_claimed_by_you": "El ticket no ha sido reclamado por ti.", 60 | "ticket_given_to": "{author_mention} ha dado el ticket a {user_mention}." 61 | } 62 | }, 63 | "embeds": { 64 | "message_ticket": { 65 | "creating": "Creando ticket...", 66 | "created": "Ticket creado con éxito {channel_mention}.", 67 | "title": "Ticket creado | {panel_name} #{ticket_number}", 68 | "description": "Hola {member_username}, nuestro personal le responderá lo más rápido posible.\nMientras tanto, describa su problema aquí con el mayor detalle posible.\n\n**Ticket Panel:** {panel_name}\n**Dueño del ticket::** {member_mention}\n**Fecha del Ticket:** {ticket_date}", 69 | "color": "AQUA" 70 | } 71 | }, 72 | "buttons": { 73 | "close": { 74 | "text": "Cerrar", 75 | "emoji": "🔒", 76 | "style": "SECONDARY", 77 | "messages": { 78 | "closed_ticket": "{user_mention} ha cerrado el ticket.", 79 | "closed_ticket_staff": "```Controles del equipo de Soporte```" 80 | } 81 | }, 82 | "claim": { 83 | "text": "Claim", 84 | "emoji": "👋", 85 | "style": "SECONDARY", 86 | "messages": { 87 | "claimed_ticket": "{user_mention} ha reclamado el ticket." 88 | } 89 | }, 90 | "transcript": { 91 | "text": "Transcript", 92 | "emoji": "📑", 93 | "style": "SECONDARY", 94 | "messages": { 95 | "saving_transcript": "Guardando transcripción...", 96 | "transcript_saved": "Transcripción guardada con éxito." 97 | } 98 | }, 99 | "open": { 100 | "text": "Abrir", 101 | "emoji": "🔓", 102 | "style": "SECONDARY", 103 | "messages": { 104 | "ticket_opened": "{user_mention} ha abierto el ticket." 105 | } 106 | }, 107 | "delete": { 108 | "text": "Borrar", 109 | "emoji": "⛔", 110 | "style": "SECONDARY", 111 | "messages": { 112 | "deleting_ticket": "Este ticket será borrado en {time} segundos." 113 | } 114 | } 115 | }, 116 | "system": { 117 | "bot_ready": "El bot esta listo", 118 | "mongo_connected": "MongoDB conectada" 119 | }, 120 | "errors": { 121 | "bad_mongo_uri": "MONGO_URI no está configurado en config.json", 122 | "command_not_found": "No se encontró el comando {comando}", 123 | "server_without_tickets": "Este servidor no tiene datos para el sistema de tickets.\nPor favor, crea un panel de tickets usando el comando `ticket-manage setup`", 124 | "reached_max_tickets": "Has alcanzado el número máximo de tickets permitidos.", 125 | "channel_without_ticket": "Este canal no es un canal de ticket.", 126 | "ticket_already_closed": "El ticket ya está cerrado.", 127 | "ticket_already_claimed": "El ticket ya está reclamado por otro usuario.", 128 | "transcript_channel_not_found": "No se encontró el canal de transcripción.", 129 | "ticket_already_open": "El ticket ya está abierto.", 130 | "ticket_not_closed": "El ticket no está cerrado.", 131 | "no_staff_role": "Este servidor no tiene configurado un rol de personal.\nUtilice el comando `config` para configurar el rol de personal.", 132 | "no_permission": "Usted no tiene permiso para ejecutar este comando." 133 | } 134 | } -------------------------------------------------------------------------------- /events/ticketButtons.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed, MessageActionRow, MessageButton } = require("discord.js") 2 | const { createTranscript } = require("discord-html-transcripts"); 3 | const dataTicket = require("../models/dataTicket"); 4 | const dataGuild = require("../models/dataGuild"); 5 | const client = require(".."); 6 | 7 | client.on("interactionCreate", async (interaction) => { 8 | if (interaction.isButton()) { 9 | const buttonID = interaction.customId.split("btn-")[1]; 10 | if (buttonID === "close-ticket-opn") { 11 | await interaction.deferUpdate(); 12 | const userData = await dataTicket.findOne({ 13 | guildID: interaction.guild.id, 14 | channelID: interaction.channel.id 15 | }); 16 | if (!userData) { 17 | return interaction.followUp({embeds: [ 18 | new MessageEmbed() 19 | .setTitle("Ticket System \❌") 20 | .setDescription(client.languages.__("errors.channel_without_ticket")) 21 | .setColor("RED") 22 | ], ephemeral: true}); 23 | } 24 | if (userData.isClosed) { 25 | return interaction.followUp({embeds: [ 26 | new MessageEmbed() 27 | .setTitle("Ticket System \❌") 28 | .setDescription(client.languages.__("errors.ticket_already_closed")) 29 | .setColor("RED") 30 | ], ephemeral: true}); 31 | } 32 | interaction.channel.permissionOverwrites.edit(userData.ownerID, { 33 | VIEW_CHANNEL: false, 34 | }); 35 | userData.usersInTicket.forEach((user) => { 36 | interaction.channel.permissionOverwrites.edit(user, { 37 | VIEW_CHANNEL: false, 38 | }); 39 | }); 40 | userData.isClosed = true; 41 | await userData.save(); 42 | 43 | return interaction.channel.send({embeds: [ 44 | new MessageEmbed() 45 | .setTitle("Ticket System \✅") 46 | .setDescription(client.languages.__mf("buttons.close.messages.closed_ticket", { 47 | user_mention: `<@${interaction.user.id}>`, 48 | user_id: interaction.user.id, 49 | channel_mention: `<#${interaction.channel.id}>`, 50 | channel_id: interaction.channel.id 51 | })) 52 | .setColor("GREEN"), 53 | new MessageEmbed() 54 | .setDescription(client.languages.__("buttons.close.messages.closed_ticket_staff")) 55 | .setColor("#2f3136") 56 | ], components: [ 57 | new MessageActionRow().addComponents( 58 | // transcript, open, delete 59 | new MessageButton() 60 | .setCustomId("btn-transcript-ticket") 61 | .setLabel(client.languages.__("buttons.transcript.text")) 62 | .setEmoji(client.languages.__("buttons.transcript.emoji")) 63 | .setStyle(client.languages.__("buttons.transcript.style")), 64 | new MessageButton() 65 | .setCustomId("btn-open-ticket") 66 | .setLabel(client.languages.__("buttons.open.text")) 67 | .setEmoji(client.languages.__("buttons.open.emoji")) 68 | .setStyle(client.languages.__("buttons.open.style")), 69 | new MessageButton() 70 | .setCustomId("btn-delete-ticket") 71 | .setLabel(client.languages.__("buttons.delete.text")) 72 | .setEmoji(client.languages.__("buttons.delete.emoji")) 73 | .setStyle(client.languages.__("buttons.delete.style")) 74 | ) 75 | ]}); 76 | } else if (buttonID === "claim-ticket-opn") { 77 | await interaction.deferUpdate(); 78 | const userData = await dataTicket.findOne({ 79 | guildID: interaction.guild.id, 80 | channelID: interaction.channel.id 81 | }); 82 | if (!userData) { 83 | return interaction.followUp({embeds: [ 84 | new MessageEmbed() 85 | .setTitle("Ticket System \❌") 86 | .setDescription(client.languages.__("errors.channel_without_ticket")) 87 | .setColor("RED") 88 | ], ephemeral: true}); 89 | } 90 | if (userData.isClaimed) { 91 | return interaction.followUp({embeds: [ 92 | new MessageEmbed() 93 | .setTitle("Ticket System \❌") 94 | .setDescription(client.languages.__("errors.ticket_already_claimed")) 95 | .setColor("RED") 96 | ], ephemeral: true}); 97 | } 98 | 99 | interaction.channel.permissionOverwrites.edit(interaction.user.id, { 100 | MANAGE_CHANNELS: true, 101 | VIEW_CHANNEL: true 102 | }); 103 | 104 | userData.staffRoles.forEach((user) => { 105 | interaction.channel.permissionOverwrites.edit((user), { 106 | VIEW_CHANNEL: false 107 | }); 108 | }); 109 | 110 | interaction.channel.send({embeds: [ 111 | new MessageEmbed() 112 | .setTitle("Ticket System \✅") 113 | .setDescription(client.languages.__mf("buttons.claim.messages.claimed_ticket", { 114 | user_mention: `<@${interaction.user.id}>`, 115 | user_id: interaction.user.id 116 | })) 117 | .setColor("GREEN") 118 | ]}); 119 | 120 | userData.isClaimed = true; 121 | userData.staffClaimed = interaction.user.id; 122 | await userData.save(); 123 | } else if (buttonID === "transcript-ticket") { 124 | await interaction.deferUpdate(); 125 | const userData = await dataTicket.findOne({ 126 | guildID: interaction.guild.id, 127 | channelID: interaction.channel.id 128 | }); 129 | const guildData = await dataGuild.findOne({ 130 | guildID: interaction.guild.id 131 | }); 132 | if (!userData) { 133 | return interaction.followUp({embeds: [ 134 | new MessageEmbed() 135 | .setTitle("Ticket System \❌") 136 | .setDescription(client.languages.__("errors.channel_without_ticket")) 137 | .setColor("RED") 138 | ], ephemeral: true}); 139 | } 140 | const transcriptChannel = interaction.guild.channels.cache.get(guildData?.transcriptChannel); 141 | if (!transcriptChannel) { 142 | return interaction.followUp({embeds: [ 143 | new MessageEmbed() 144 | .setTitle("Ticket System \❌") 145 | .setDescription(client.languages.__("errors.transcript_channel_not_found")) 146 | .setColor("RED") 147 | ], ephemeral: true}); 148 | } 149 | const firstMessage = await interaction.channel.send({embeds: [ 150 | new MessageEmbed() 151 | .setTitle("Ticket System \✅") 152 | .setDescription(client.languages.__("buttons.transcript.messages.saving_transcript")) 153 | .setColor("ORANGE") 154 | ]}) 155 | const transcript = await createTranscript(interaction.channel, { 156 | fileName: `transcript-${interaction.channel.name}.html`, 157 | limit: -1, 158 | returnBuffer: false 159 | }); 160 | const member = interaction.guild.members.cache.get(userData.ownerID); 161 | await transcriptChannel.send({embeds: [ 162 | new MessageEmbed() 163 | .setAuthor({name: member.user.tag, iconURL: member.user.displayAvatarURL({dynamic: true})}) 164 | .addField("Ticket Owner", `<@${userData.ownerID}>`, true) 165 | .addField("Ticket Name", interaction.channel.name, true) 166 | .setColor("ORANGE") 167 | ], files: [transcript]}).then((msg) => { 168 | msg.edit({embeds: [ 169 | msg.embeds[0] 170 | .addField("Panel Name", `${userData.ticketPanel}`, true) 171 | .addField("Direct Transcript", `[Direct Transcript](${msg.attachments.first().url})`, true) 172 | .addField("Ticket Closed", interaction.user.tag, true) 173 | .setColor("GREEN") 174 | ]}); 175 | firstMessage.edit({embeds: [ 176 | firstMessage.embeds[0] 177 | .setDescription(client.languages.__("buttons.transcript.messages.transcript_saved")) 178 | .setColor("GREEN") 179 | ]}); 180 | }); 181 | 182 | } else if (buttonID === "open-ticket") { 183 | await interaction.deferUpdate(); 184 | const userData = await dataTicket.findOne({ 185 | guildID: interaction.guild.id, 186 | channelID: interaction.channel.id 187 | }); 188 | if (!userData) { 189 | return interaction.followUp({embeds: [ 190 | new MessageEmbed() 191 | .setTitle("Ticket System \❌") 192 | .setDescription(client.languages.__("errors.channel_without_ticket")) 193 | .setColor("RED") 194 | ], ephemeral: true}); 195 | } 196 | if (!userData.isClosed) { 197 | return interaction.followUp({embeds: [ 198 | new MessageEmbed() 199 | .setTitle("Ticket System \❌") 200 | .setDescription(client.languages.__("errors.ticket_already_open")) 201 | .setColor("RED") 202 | ], ephemeral: true}); 203 | } 204 | 205 | interaction.channel.permissionOverwrites.edit(userData.ownerID, { 206 | VIEW_CHANNEL: true 207 | }); 208 | interaction.channel.send({embeds:[ 209 | new MessageEmbed() 210 | .setTitle("Ticket System \✅") 211 | .setDescription(client.languages.__mf("buttons.open.messages.ticket_opened", { 212 | user_mention: `<@${interaction.user.id}>`, 213 | user_id: interaction.user.id, 214 | user_tag: interaction.user.tag 215 | })) 216 | .setColor("GREEN") 217 | ]}).then(() => { 218 | interaction.message.delete(); 219 | userData.isClosed = false; 220 | userData.save(); 221 | }); 222 | } else if (buttonID === "delete-ticket") { 223 | await interaction.deferUpdate(); 224 | const userData = await dataTicket.findOne({ 225 | guildID: interaction.guild.id, 226 | channelID: interaction.channel.id 227 | }); 228 | if (!userData) { 229 | return interaction.followUp({embeds: [ 230 | new MessageEmbed() 231 | .setTitle("Ticket System \❌") 232 | .setDescription(client.languages.__("errors.channel_without_ticket")) 233 | .setColor("RED") 234 | ], ephemeral: true}); 235 | } 236 | if (!userData.isClosed) { 237 | return interaction.followUp({embeds: [ 238 | new MessageEmbed() 239 | .setTitle("Ticket System \❌") 240 | .setDescription(client.languages.__("errors.ticket_not_closed")) 241 | .setColor("RED") 242 | ], ephemeral: true}); 243 | } 244 | interaction.channel.send({embeds: [ 245 | new MessageEmbed() 246 | .setTitle("Ticket System \✅") 247 | .setDescription(client.languages.__mf("buttons.delete.messages.deleting_ticket", { 248 | time: "5" 249 | })) 250 | .setColor("RED") 251 | ]}).then(() => { 252 | setTimeout(async () => { 253 | await interaction.channel.delete(); 254 | userData.delete(); 255 | }, 5000); 256 | }); 257 | } 258 | } 259 | }); -------------------------------------------------------------------------------- /commands/tickets/ticket-manage.js: -------------------------------------------------------------------------------- 1 | const { CommandInteraction, MessageEmbed, MessageButton, MessageActionRow } = require("discord.js"); 2 | const paginationEmbed = require("../../controllers/paginationEmbed"); 3 | const dataGuild = require("../../models/dataGuild"); 4 | 5 | module.exports = { 6 | name: "ticket-manage", 7 | description: "Manage the ticket system.", 8 | type: 'CHAT_INPUT', 9 | options: [ 10 | { 11 | name: 'setup', 12 | description: 'Setup ticket panel.', 13 | type: 'SUB_COMMAND', 14 | options: [ 15 | { 16 | name: 'name', 17 | description: 'The name of the ticket panel.', 18 | type: 'STRING', 19 | required: true 20 | }, 21 | { 22 | name: 'emoji', 23 | description: 'The emoji to use for the ticket panel.', 24 | type: 'STRING', 25 | required: true 26 | }, 27 | { 28 | name: 'category', 29 | description: 'The category to put the ticket panel in.', 30 | type: 'CHANNEL', 31 | channelTypes: ["GUILD_CATEGORY"], 32 | required: true 33 | }, 34 | { 35 | name: 'custom-id', 36 | description: 'The custom ID to use for the ticket panel.', 37 | type: 'STRING', 38 | required: true 39 | }, 40 | { 41 | name: 'role-1', 42 | description: 'The role necessary to view the ticket panel.', 43 | type: 'ROLE', 44 | required: true 45 | }, 46 | { 47 | name: 'role-2', 48 | description: 'The role necessary to view the ticket panel.', 49 | type: 'ROLE', 50 | required: false 51 | }, 52 | { 53 | name: 'role-3', 54 | description: 'The role necessary to view the ticket panel.', 55 | type: 'ROLE', 56 | required: false 57 | } 58 | ], 59 | }, 60 | { 61 | name: 'delete', 62 | description: 'Delete a ticket panel.', 63 | type: 'SUB_COMMAND', 64 | options: [ 65 | { 66 | name: 'custom-id', 67 | description: 'The custom ID of the ticket panel to delete.', 68 | type: 'STRING', 69 | required: true 70 | } 71 | ] 72 | }, 73 | { 74 | name: 'list', 75 | description: 'List all ticket panels.', 76 | type: 'SUB_COMMAND' 77 | }, 78 | { 79 | name: 'send', 80 | description: 'Send a ticket panel.', 81 | type: 'SUB_COMMAND', 82 | options: [ 83 | { 84 | name: 'channel', 85 | description: 'The channel to send the ticket panel to.', 86 | type: 'CHANNEL', 87 | channelTypes: ["GUILD_TEXT", "GUILD_NEWS"], 88 | required: false 89 | } 90 | ] 91 | }, 92 | ], 93 | /** 94 | * 95 | * @param {import("../..").Bot} client 96 | * @param {CommandInteraction} interaction 97 | * @param {String[]} args 98 | */ 99 | run: async (client, interaction, args) => { 100 | const Sub_Command = interaction.options.getSubcommand(false); 101 | if (!Sub_Command) { 102 | return interaction.reply({embeds: [ 103 | new MessageEmbed() 104 | .setTitle("Ticket System \❌") 105 | .setDescription(client.languages.__("commands.ticket_manage.no_specify")) 106 | .setColor("RED") 107 | ]}); 108 | } 109 | if (Sub_Command === "setup") { 110 | const name = interaction.options.getString("name"); 111 | const emoji = interaction.options.getString("emoji"); 112 | const category = interaction.options.getChannel("category"); 113 | const custom_id = interaction.options.getString("custom-id"); 114 | const role_1 = interaction.options.getRole("role-1"); 115 | const role_2 = interaction.options.getRole("role-2") || null; 116 | const role_3 = interaction.options.getRole("role-3") || null; 117 | 118 | const ticket = { 119 | customID: custom_id, 120 | panelName: name, 121 | panelEmoji: emoji, 122 | panelCategory: category.id, 123 | panelRoles: [role_1.id, role_2 ? role_2.id : null, role_3 ? role_3.id : null] 124 | } 125 | ticket.panelRoles = ticket.panelRoles.filter(x => x !== null); 126 | 127 | const guildData = await dataGuild.findOne({ guildID: interaction.guild.id }); 128 | if (guildData) { 129 | const alreadyExists = guildData.tickets.find(x => x.customID === custom_id); 130 | if (alreadyExists) { 131 | return interaction.reply({embeds: [ 132 | new MessageEmbed() 133 | .setTitle("Ticket System \❌") 134 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.setup.already_exists", { custom_id })) 135 | .setColor("RED") 136 | ]}); 137 | } 138 | guildData.tickets.push(ticket); 139 | await guildData.save(); 140 | } else { 141 | const newGuildData = new dataGuild({ 142 | guildID: interaction.guild.id, 143 | tickets: [ticket], 144 | ticketCounter: 0, 145 | usersBlacklisted: [], 146 | transcriptChannel: null, 147 | mentionStaff: null, 148 | staffRole: null, 149 | maxTickets: 1 150 | }); 151 | await newGuildData.save(); 152 | } 153 | 154 | const ticketMapped = "```yaml\n" + `CustomID: ${ticket.customID}\nName: ${ticket.panelName}\nEmoji: ${ticket.panelEmoji}\nCategory: ${ticket.panelCategory}\nRoles:\n${ticket.panelRoles.map((id, i) => { 155 | const role = interaction.guild.roles.cache.get(id); 156 | return ` ${i+1}: ${role.name}`; 157 | }).join("\n")}\n` + "```"; 158 | return interaction.reply({embeds: [ 159 | new MessageEmbed() 160 | .setTitle("Ticket System \✅") 161 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.setup.created", { panel_info: ticketMapped })) 162 | .setColor("GREEN") 163 | ]}); 164 | } else if (Sub_Command === "delete") { 165 | const custom_id = interaction.options.getString("custom-id"); 166 | 167 | const guildData = await dataGuild.findOne({ guildID: interaction.guild.id }); 168 | if (!guildData) { 169 | return interaction.reply({embeds: [ 170 | new MessageEmbed() 171 | .setTitle("Ticket System \❌") 172 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.delete.not_exists", { custom_id })) 173 | .setColor("RED") 174 | ]}); 175 | } 176 | const ticketIndex = guildData.tickets.findIndex(x => x.customID === custom_id); 177 | if (ticketIndex === -1) { 178 | return interaction.reply({embeds: [ 179 | new MessageEmbed() 180 | .setTitle("Ticket System \❌") 181 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.delete.not_exists", { custom_id })) 182 | .setColor("RED") 183 | ]}); 184 | } 185 | guildData.tickets.splice(ticketIndex, 1); 186 | await guildData.save(); 187 | 188 | return interaction.reply({embeds: [ 189 | new MessageEmbed() 190 | .setTitle("Ticket System \✅") 191 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.delete.deleted", { custom_id })) 192 | .setColor("GREEN") 193 | ]}); 194 | } else if (Sub_Command === "list") { 195 | const guildData = await dataGuild.findOne({ guildID: interaction.guild.id }); 196 | if (!guildData) { 197 | return interaction.reply({embeds: [ 198 | new MessageEmbed() 199 | .setTitle("Ticket System \❌") 200 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.list.no_panels")) 201 | .setColor("RED") 202 | ]}); 203 | } 204 | const embeds = []; 205 | for (const panel of guildData.tickets) { 206 | embeds.push( 207 | new MessageEmbed() 208 | .setTitle("Ticket System \✅") 209 | .setDescription(client.languages.__("commands.ticket_manage.sub_commands.list.description")) 210 | .setImage("https://i.stack.imgur.com/Fzh0w.png") 211 | .addField("CustomID", panel.customID, true) 212 | .addField("Name", panel.panelName, true) 213 | .addField("Emoji", panel.panelEmoji, true) 214 | .addField("Category", `<#${panel.panelCategory}>`, true) 215 | .addField("Roles", panel.panelRoles.map((id) => { 216 | const role = interaction.guild.roles.cache.get(id); 217 | return role ? `<@&${role.id}>` : id; 218 | }).join("\n"), true) 219 | .setColor("AQUA") 220 | .setFooter({text: client.languages.__mf("commands.ticket_manage.sub_commands.list.footer", { 221 | page: embeds.length + 1, 222 | pages: guildData.tickets.length 223 | }), iconURL: interaction.client.user.displayAvatarURL()}) 224 | ); 225 | } 226 | paginationEmbed(interaction, embeds, "60s", false); 227 | } else if (Sub_Command === "send") { 228 | const channel = interaction.options.getChannel("channel") || interaction.channel; 229 | const guildData = await dataGuild.findOne({ guildID: interaction.guild.id }); 230 | 231 | if (!guildData) { 232 | return interaction.reply({embeds: [ 233 | new MessageEmbed() 234 | .setTitle("Ticket System \❌") 235 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.send.no_panels")) 236 | .setColor("RED") 237 | ]}); 238 | } else if (guildData?.tickets.length === 0) { 239 | return interaction.reply({embeds: [ 240 | new MessageEmbed() 241 | .setTitle("Ticket System \❌") 242 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.send.no_panels")) 243 | .setColor("RED") 244 | ]}); 245 | } else { 246 | await interaction.deferReply({ 247 | ephemeral: true 248 | }); 249 | const tickets = guildData.tickets; 250 | const components = []; 251 | lastComponents = new MessageActionRow; 252 | const options = tickets.map((ticket) => { 253 | return { 254 | customID: ticket.customID, 255 | emoji: ticket.panelEmoji, 256 | name: ticket.panelName 257 | } 258 | }); 259 | for (const panel of options) { 260 | if (panel.emoji !== undefined) { 261 | lastComponents.addComponents( 262 | new MessageButton() 263 | .setCustomId(`ticket-${panel.customID}`) 264 | .setEmoji(panel.emoji) 265 | .setStyle("SECONDARY") 266 | ) 267 | if (lastComponents.components.length === 5) { 268 | components.push(lastComponents); 269 | lastComponents = new MessageActionRow(); 270 | } 271 | } 272 | } 273 | if (lastComponents.components.length > 0) { 274 | components.push(lastComponents); 275 | } 276 | const panels = options.map((x, i) => { 277 | return client.languages.__mf("commands.ticket_manage.sub_commands.send.embed_config.separator", { 278 | emoji: x.emoji, 279 | name: x.name, 280 | counter: i + 1 281 | }); 282 | }); 283 | channel.send({embeds: [ 284 | new MessageEmbed() 285 | .setTitle(client.languages.__("commands.ticket_manage.sub_commands.send.embed_config.title")) 286 | .setDescription(client.languages.__mf("commands.ticket_manage.sub_commands.send.embed_config.description", {separator: panels.join("\n")})) 287 | .setColor(client.languages.__("commands.ticket_manage.sub_commands.send.embed_config.color")) 288 | .setFooter({text: client.languages.__("commands.ticket_manage.sub_commands.send.embed_config.footer")}) 289 | ], components}).then(() => { 290 | interaction.followUp({embeds: [ 291 | new MessageEmbed() 292 | .setTitle("Ticket System \✅") 293 | .setDescription(client.languages.__("commands.ticket_manage.sub_commands.send.send_success")) 294 | .setColor("GREEN") 295 | ]}); 296 | }) 297 | } 298 | } else { 299 | return interaction.reply({embeds: [ 300 | new MessageEmbed() 301 | .setTitle("Ticket System \❌") 302 | .setDescription(client.languages.__("commands.ticket_manage.no_specify")) 303 | .setColor("RED") 304 | ]}); 305 | } 306 | }, 307 | }; -------------------------------------------------------------------------------- /commands/general/config.js: -------------------------------------------------------------------------------- 1 | const { CommandInteraction, MessageEmbed, MessageActionRow, MessageButton } = require("discord.js"); 2 | const dataGuild = require("../../models/dataGuild"); 3 | 4 | module.exports = { 5 | name: "config", 6 | description: "Configure the bot system", 7 | type: 'CHAT_INPUT', 8 | /** 9 | * 10 | * @param {import("../..").Bot} client 11 | * @param {CommandInteraction} interaction 12 | * @param {String[]} args 13 | */ 14 | run: async (client, interaction, args) => { 15 | let transcript_channel, staff_role, staff_mention; 16 | await interaction.reply({embeds: [ 17 | new MessageEmbed() 18 | .setTitle("Ticket System \🟠") 19 | .setDescription("Hey this is the config system!\nHere you have options for configure the bot.") 20 | .addField("Transcript Channel", "Set the channel where the transcript will be send.") 21 | .addField("Staff Role", "Set the role that can use the bot.") 22 | .addField("Staff Mention", "Set the role that can that the bot mentions every time it opens a ticket") 23 | .setColor("#2f3136") 24 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 25 | ], components: [ 26 | new MessageActionRow() 27 | .addComponents( 28 | new MessageButton() 29 | .setLabel("Transcript Channel") 30 | .setStyle("PRIMARY") 31 | .setCustomId("config-transcript-channel"), 32 | new MessageButton() 33 | .setLabel("Staff Role") 34 | .setStyle("PRIMARY") 35 | .setCustomId("config-staff-role"), 36 | new MessageButton() 37 | .setLabel("Staff Mention") 38 | .setStyle("PRIMARY") 39 | .setCustomId("config-staff-mention"), 40 | new MessageButton() 41 | .setEmoji("👀") 42 | .setStyle("PRIMARY") 43 | .setCustomId("config-show"), 44 | new MessageButton() 45 | .setEmoji("✖️") 46 | .setStyle("DANGER") 47 | .setCustomId("config-cancel") 48 | ) 49 | ], fetchReply: true}); 50 | 51 | const collector = interaction.channel.createMessageComponentCollector({ 52 | filter: (m) => m.user.id === interaction.user.id, 53 | componentType: "BUTTON", 54 | max: 2 55 | }); 56 | 57 | collector.on("collect", async (int) => { 58 | await int.deferUpdate(); 59 | const button = int.customId.split("config-")[1]; 60 | if (button === "transcript-channel") { 61 | interaction.editReply({embeds: [ 62 | new MessageEmbed() 63 | .setTitle("Ticket System \🟠") 64 | .setDescription("Hey please mention the channel where the transcript will be send.\n**Remimber** If you want to remove the data you have configured, write **remove** and send the message") 65 | .setColor("ORANGE") 66 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 67 | ], components: [ 68 | new MessageActionRow().addComponents( 69 | new MessageButton() 70 | .setEmoji("✖️") 71 | .setStyle("DANGER") 72 | .setCustomId("config-cancel") 73 | ) 74 | ]}); 75 | 76 | const messageCollector = interaction.channel.createMessageCollector({ 77 | filter: (m) => m.author.id === interaction.user.id, 78 | max: 1 79 | }); 80 | 81 | messageCollector.on("collect", async (message) => { 82 | message.delete(); 83 | collector.stop(); 84 | const mentionedChannel = message.mentions.channels.first(); 85 | if (mentionedChannel) { 86 | transcript_channel = mentionedChannel.id; 87 | try { 88 | const guildData = await dataGuild.findOne({ 89 | guildID: interaction.guild.id 90 | }); 91 | if (guildData) { 92 | guildData.transcriptChannel = transcript_channel; 93 | await guildData.save(); 94 | } else { 95 | const newGuildData = new dataGuild({ 96 | guildID: interaction.guild.id, 97 | transcriptChannel: transcript_channel 98 | }); 99 | await newGuildData.save(); 100 | } 101 | interaction.editReply({embeds: [ 102 | new MessageEmbed() 103 | .setTitle("Ticket System \✅") 104 | .setDescription("Hey the channel was setted!") 105 | .setColor("GREEN") 106 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 107 | ], components: []}); 108 | } catch (error) { 109 | interaction.editReply({embeds: [ 110 | new MessageEmbed() 111 | .setTitle("Ticket System \❌") 112 | .setDescription("Hey there was an error setting the channel!\n" + "```" + error + "```") 113 | .setColor("RED") 114 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 115 | ], components: []}); 116 | } 117 | } else if (message.content === "remove") { 118 | transcript_channel = ""; 119 | try { 120 | const guildData = await dataGuild.findOne({ 121 | guildID: interaction.guild.id 122 | }); 123 | if (guildData) { 124 | guildData.transcriptChannel = transcript_channel; 125 | await guildData.save(); 126 | } else { 127 | const newGuildData = new dataGuild({ 128 | guildID: interaction.guild.id, 129 | transcriptChannel: transcript_channel 130 | }); 131 | await newGuildData.save(); 132 | } 133 | interaction.editReply({embeds: [ 134 | new MessageEmbed() 135 | .setTitle("Ticket System \✅") 136 | .setDescription("Hey the channel was removed!") 137 | .setColor("GREEN") 138 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 139 | ], components: []}); 140 | } catch (error) { 141 | interaction.editReply({embeds: [ 142 | new MessageEmbed() 143 | .setTitle("Ticket System \❌") 144 | .setDescription("Hey there was an error removing the channel!\n" + "```" + error + "```") 145 | .setColor("RED") 146 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 147 | ], components: []}); 148 | } 149 | } else { 150 | return interaction.editReply({embeds: [ 151 | new MessageEmbed() 152 | .setTitle("Ticket System \🔴") 153 | .setDescription("You need to mention a channel!") 154 | .setColor("RED") 155 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 156 | ], components: []}); 157 | } 158 | }); 159 | } else if (button === "staff-role") { 160 | interaction.editReply({embeds: [ 161 | new MessageEmbed() 162 | .setTitle("Ticket System \🟠") 163 | .setDescription("Hey please mention the role that can use the bot.\n**Remimber** If you want to remove the data you have configured, write **remove** and send the message") 164 | .setColor("ORANGE") 165 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 166 | ], components: [ 167 | new MessageActionRow().addComponents( 168 | new MessageButton() 169 | .setEmoji("✖️") 170 | .setStyle("DANGER") 171 | .setCustomId("config-cancel") 172 | ) 173 | ]}); 174 | const messageCollector = interaction.channel.createMessageCollector({ 175 | filter: (m) => m.author.id === interaction.user.id, 176 | max: 1 177 | }); 178 | 179 | messageCollector.on("collect", async (message) => { 180 | collector.stop(); 181 | message.delete(); 182 | const mentionedRole = message.mentions.roles.first(); 183 | if (mentionedRole) { 184 | staff_role = mentionedRole.id; 185 | try { 186 | const guildData = await dataGuild.findOne({ 187 | guildID: interaction.guild.id 188 | }); 189 | if (guildData) { 190 | guildData.staffRole = staff_role; 191 | await guildData.save(); 192 | } else { 193 | const newGuildData = new dataGuild({ 194 | guildID: interaction.guild.id, 195 | staffRole: staff_role 196 | }); 197 | await newGuildData.save(); 198 | } 199 | interaction.editReply({embeds: [ 200 | new MessageEmbed() 201 | .setTitle("Ticket System \✅") 202 | .setDescription("Hey the staff-role was setted!") 203 | .setColor("GREEN") 204 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 205 | ], components: []}); 206 | } catch (error) { 207 | interaction.editReply({embeds: [ 208 | new MessageEmbed() 209 | .setTitle("Ticket System \❌") 210 | .setDescription("Hey there was an error setting the role!\n" + "```" + error + "```") 211 | .setColor("RED") 212 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 213 | ], components: []}); 214 | } 215 | } else if (message.content === "remove") { 216 | try { 217 | const guildData = await dataGuild.findOne({ 218 | guildID: interaction.guild.id 219 | }); 220 | if (guildData) { 221 | guildData.staffRole = ""; 222 | await guildData.save(); 223 | } else { 224 | const newGuildData = new dataGuild({ 225 | guildID: interaction.guild.id, 226 | staffRole: "" 227 | }); 228 | await newGuildData.save(); 229 | } 230 | interaction.editReply({embeds: [ 231 | new MessageEmbed() 232 | .setTitle("Ticket System \✅") 233 | .setDescription("Hey the staff-role was removed!") 234 | .setColor("GREEN") 235 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 236 | ], components: []}); 237 | } catch (error) { 238 | interaction.editReply({embeds: [ 239 | new MessageEmbed() 240 | .setTitle("Ticket System \❌") 241 | .setDescription("Hey there was an error removing the role!\n" + "```" + error + "```") 242 | .setColor("RED") 243 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 244 | ], components: []}); 245 | } 246 | } else { 247 | collector.stop(); 248 | return interaction.editReply({embeds: [ 249 | new MessageEmbed() 250 | .setTitle("Ticket System \🔴") 251 | .setDescription("You need to mention a role!") 252 | .setColor("RED") 253 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 254 | ], components: []}); 255 | } 256 | }); 257 | } else if (button === "staff-mention") { 258 | interaction.editReply({embeds: [ 259 | new MessageEmbed() 260 | .setTitle("Ticket System \🟠") 261 | .setDescription("Hey please mention the user that can use the bot.\n**Remimber** If you want to remove the data you have configured, write **remove** and send the message") 262 | .setColor("ORANGE") 263 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 264 | ], components: [ 265 | new MessageActionRow().addComponents( 266 | new MessageButton() 267 | .setEmoji("✖️") 268 | .setStyle("DANGER") 269 | .setCustomId("config-cancel") 270 | ) 271 | ]}); 272 | const messageCollector = interaction.channel.createMessageCollector({ 273 | filter: (m) => m.author.id === interaction.user.id, 274 | max: 1 275 | }); 276 | 277 | messageCollector.on("collect", async (message) => { 278 | collector.stop(); 279 | message.delete(); 280 | const mentionedRole = message.mentions.roles.first(); 281 | if (mentionedRole) { 282 | staff_mention = mentionedRole.id; 283 | try { 284 | const guildData = await dataGuild.findOne({ 285 | guildID: interaction.guild.id 286 | }); 287 | if (guildData) { 288 | guildData.mentionStaff = staff_mention; 289 | await guildData.save(); 290 | } else { 291 | const newGuildData = new dataGuild({ 292 | guildID: interaction.guild.id, 293 | mentionStaff: staff_mention 294 | }); 295 | await newGuildData.save(); 296 | } 297 | interaction.editReply({embeds: [ 298 | new MessageEmbed() 299 | .setTitle("Ticket System \✅") 300 | .setDescription("Hey the staff-mention role was setted!") 301 | .setColor("GREEN") 302 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 303 | ], components: []}); 304 | } catch (error) { 305 | interaction.editReply({embeds: [ 306 | new MessageEmbed() 307 | .setTitle("Ticket System \❌") 308 | .setDescription("Hey there was an error setting the staff-mention role!\n" + "```" + error + "```") 309 | .setColor("RED") 310 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 311 | ], components: []}); 312 | } 313 | } else if (message.content === "remove") { 314 | try { 315 | const guildData = await dataGuild.findOne({ 316 | guildID: interaction.guild.id 317 | }); 318 | if (guildData) { 319 | guildData.mentionStaff = ""; 320 | await guildData.save(); 321 | } else { 322 | const newGuildData = new dataGuild({ 323 | guildID: interaction.guild.id, 324 | mentionStaff: "" 325 | }); 326 | await newGuildData.save(); 327 | } 328 | interaction.editReply({embeds: [ 329 | new MessageEmbed() 330 | .setTitle("Ticket System \✅") 331 | .setDescription("Hey the staff-mention was removed!") 332 | .setColor("GREEN") 333 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 334 | ], components: []}); 335 | } catch (error) { 336 | interaction.editReply({embeds: [ 337 | new MessageEmbed() 338 | .setTitle("Ticket System \❌") 339 | .setDescription("Hey there was an error removing the staff-mention role!\n" + "```" + error + "```") 340 | .setColor("RED") 341 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 342 | ], components: []}); 343 | } 344 | } else { 345 | collector.stop(); 346 | return interaction.editReply({embeds: [ 347 | new MessageEmbed() 348 | .setTitle("Ticket System \🔴") 349 | .setDescription("You need to mention a role!") 350 | .setColor("RED") 351 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 352 | ], components: []}); 353 | } 354 | }); 355 | } else if (button === "cancel") { 356 | collector.stop(); 357 | return interaction.editReply({embeds: [ 358 | new MessageEmbed() 359 | .setTitle("Ticket System \🔴") 360 | .setDescription("You have canceled the process!") 361 | .setColor("RED") 362 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 363 | ], components: []}); 364 | } else if (button === "show") { 365 | collector.stop(); 366 | const guildData = await dataGuild.findOne({ 367 | guildID: interaction.guild.id 368 | }); 369 | if (!guildData) { 370 | return interaction.editReply({embeds: [ 371 | new MessageEmbed() 372 | .setTitle("Ticket System \🔴") 373 | .setDescription("This server doesn't have configurated the ticket system!") 374 | .setColor("RED") 375 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 376 | ], components: []}); 377 | } 378 | const data = { 379 | transcript_channel: guildData.transcriptChannel || "Not setted", 380 | staff_role: guildData.staffRole || "Not setted", 381 | staff_mention: guildData.mentionStaff || "Not setted", 382 | } 383 | return interaction.editReply({embeds: [ 384 | new MessageEmbed() 385 | .setColor("GREEN") 386 | .setTitle("Ticket System \✅") 387 | .setDescription("Here is the current configurated data:") 388 | .setFooter({text: "Ticket System by: Jhoan#6969", iconURL: client.user.displayAvatarURL({dynamic: true})}) 389 | .addFields([ 390 | { 391 | name: "Transcript Channel 📚", 392 | value: data.transcript_channel, 393 | inline: false 394 | }, 395 | { 396 | name: "Staff Role 👤", 397 | value: data.staff_role, 398 | inline: false 399 | }, 400 | { 401 | name: "Staff Mention 🗣️", 402 | value: data.staff_mention, 403 | inline: false 404 | } 405 | ]) 406 | ], components: []}); 407 | } 408 | }); 409 | }, 410 | }; --------------------------------------------------------------------------------