├── .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 | };
--------------------------------------------------------------------------------