blob: 48d419dacf4314bee2f8aa49046ce8f1bd8a8156 [file] [log] [blame]
pineafan63fc5e22022-08-04 22:04:10 +01001import { LoadingEmbed } from "./../../utils/defaultEmbeds.js";
pineafan6702cef2022-06-13 17:52:37 +01002import getEmojiByName from "../../utils/getEmojiByName.js";
pineafan4edb7762022-06-26 19:21:04 +01003import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan6702cef2022-06-13 17:52:37 +01004import confirmationMessage from "../../utils/confirmationMessage.js";
Skyler Grey75ea9172022-08-06 10:22:23 +01005import Discord, {
6 CommandInteraction,
7 GuildChannel,
8 Message,
TheCodedProf21c08592022-09-13 14:14:43 -04009 ActionRowBuilder,
10 Component,
11 ButtonBuilder,
Skyler Grey75ea9172022-08-06 10:22:23 +010012 MessageComponentInteraction,
TheCodedProf21c08592022-09-13 14:14:43 -040013 SelectMenuBuilder,
Skyler Grey75ea9172022-08-06 10:22:23 +010014 Role,
15 SelectMenuInteraction,
TheCodedProf21c08592022-09-13 14:14:43 -040016 TextInputComponent,
17 ButtonStyle
Skyler Grey75ea9172022-08-06 10:22:23 +010018} from "discord.js";
PineaFan64486c42022-12-28 09:21:04 +000019import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
Skyler Greyc634e2b2022-08-06 17:50:48 +010020import { ChannelType } from "discord-api-types/v9";
pineafan6702cef2022-06-13 17:52:37 +010021import client from "../../utils/client.js";
Skyler Grey11236ba2022-08-08 21:13:33 +010022import { toHexInteger, toHexArray, tickets as ticketTypes } from "../../utils/calculate.js";
pineafan63fc5e22022-08-04 22:04:10 +010023import { capitalize } from "../../utils/generateKeyValueList.js";
pineafan6702cef2022-06-13 17:52:37 +010024import { modalInteractionCollector } from "../../utils/dualCollector.js";
pineafan3a02ea32022-08-11 21:35:04 +010025import type { GuildConfig } from "../../utils/database.js";
pineafan4f164f32022-02-26 22:07:12 +000026
Skyler Grey75ea9172022-08-06 10:22:23 +010027const command = (builder: SlashCommandSubcommandBuilder) =>
28 builder
29 .setName("tickets")
Skyler Grey11236ba2022-08-08 21:13:33 +010030 .setDescription("Shows settings for tickets | Use no arguments to manage custom types")
Skyler Grey75ea9172022-08-06 10:22:23 +010031 .addStringOption((option) =>
32 option
33 .setName("enabled")
34 .setDescription("If users should be able to create tickets")
35 .setRequired(false)
PineaFan64486c42022-12-28 09:21:04 +000036 .addChoices(
37 {name: "Yes", value: "yes"},
38 {name: "No",value: "no"}
39 )
Skyler Grey75ea9172022-08-06 10:22:23 +010040 )
41 .addChannelOption((option) =>
42 option
43 .setName("category")
44 .setDescription("The category where tickets are created")
PineaFan64486c42022-12-28 09:21:04 +000045 .addChannelTypes(ChannelType.GuildCategory)
Skyler Grey75ea9172022-08-06 10:22:23 +010046 .setRequired(false)
47 )
48 .addNumberOption((option) =>
49 option
50 .setName("maxticketsperuser")
Skyler Grey11236ba2022-08-08 21:13:33 +010051 .setDescription("The maximum amount of tickets a user can create | Default: 5")
Skyler Grey75ea9172022-08-06 10:22:23 +010052 .setRequired(false)
53 .setMinValue(1)
54 )
55 .addRoleOption((option) =>
56 option
57 .setName("supportrole")
58 .setDescription(
59 "This role will have view access to all tickets and will be pinged when a ticket is created"
60 )
61 .setRequired(false)
62 );
pineafan4f164f32022-02-26 22:07:12 +000063
pineafan3a02ea32022-08-11 21:35:04 +010064const callback = async (interaction: CommandInteraction): Promise<unknown> => {
Skyler Grey75ea9172022-08-06 10:22:23 +010065 let m = (await interaction.reply({
66 embeds: LoadingEmbed,
67 ephemeral: true,
68 fetchReply: true
69 })) as Message;
pineafan63fc5e22022-08-04 22:04:10 +010070 const options = {
Skyler Greyad002172022-08-16 18:48:26 +010071 enabled: interaction.options.getString("enabled")?.startsWith("yes") as boolean | null,
pineafan6702cef2022-06-13 17:52:37 +010072 category: interaction.options.getChannel("category"),
73 maxtickets: interaction.options.getNumber("maxticketsperuser"),
74 supportping: interaction.options.getRole("supportrole")
pineafan63fc5e22022-08-04 22:04:10 +010075 };
Skyler Grey11236ba2022-08-08 21:13:33 +010076 if (options.enabled !== null || options.category || options.maxtickets || options.supportping) {
pineafan6702cef2022-06-13 17:52:37 +010077 if (options.category) {
pineafan3a02ea32022-08-11 21:35:04 +010078 let channel: GuildChannel | null;
pineafan6702cef2022-06-13 17:52:37 +010079 try {
pineafan3a02ea32022-08-11 21:35:04 +010080 channel = await interaction.guild!.channels.fetch(options.category.id);
pineafan6702cef2022-06-13 17:52:37 +010081 } catch {
82 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +010083 embeds: [
84 new EmojiEmbed()
85 .setEmoji("CHANNEL.TEXT.DELETE")
86 .setTitle("Tickets > Category")
Skyler Grey11236ba2022-08-08 21:13:33 +010087 .setDescription("The channel you provided is not a valid category")
Skyler Grey75ea9172022-08-06 10:22:23 +010088 .setStatus("Danger")
pineafan6702cef2022-06-13 17:52:37 +010089 ]
pineafan63fc5e22022-08-04 22:04:10 +010090 });
pineafan6702cef2022-06-13 17:52:37 +010091 }
pineafan3a02ea32022-08-11 21:35:04 +010092 if (!channel) return;
pineafan63fc5e22022-08-04 22:04:10 +010093 channel = channel as Discord.CategoryChannel;
pineafan3a02ea32022-08-11 21:35:04 +010094 if (channel.guild.id !== interaction.guild!.id)
Skyler Grey75ea9172022-08-06 10:22:23 +010095 return interaction.editReply({
96 embeds: [
97 new EmojiEmbed()
98 .setTitle("Tickets > Category")
Skyler Grey11236ba2022-08-08 21:13:33 +010099 .setDescription("You must choose a category in this server")
Skyler Grey75ea9172022-08-06 10:22:23 +0100100 .setStatus("Danger")
101 .setEmoji("CHANNEL.TEXT.DELETE")
102 ]
103 });
pineafan6702cef2022-06-13 17:52:37 +0100104 }
105 if (options.maxtickets) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100106 if (options.maxtickets < 1)
107 return interaction.editReply({
108 embeds: [
109 new EmojiEmbed()
110 .setTitle("Tickets > Max Tickets")
Skyler Grey11236ba2022-08-08 21:13:33 +0100111 .setDescription("You must choose a number greater than 0")
Skyler Grey75ea9172022-08-06 10:22:23 +0100112 .setStatus("Danger")
113 .setEmoji("CHANNEL.TEXT.DELETE")
114 ]
115 });
pineafan6702cef2022-06-13 17:52:37 +0100116 }
pineafan3a02ea32022-08-11 21:35:04 +0100117 let role: Role | null;
pineafan6702cef2022-06-13 17:52:37 +0100118 if (options.supportping) {
119 try {
pineafan3a02ea32022-08-11 21:35:04 +0100120 role = await interaction.guild!.roles.fetch(options.supportping.id);
pineafan6702cef2022-06-13 17:52:37 +0100121 } catch {
122 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100123 embeds: [
124 new EmojiEmbed()
125 .setEmoji("GUILD.ROLE.DELETE")
126 .setTitle("Tickets > Support Ping")
Skyler Grey11236ba2022-08-08 21:13:33 +0100127 .setDescription("The role you provided is not a valid role")
Skyler Grey75ea9172022-08-06 10:22:23 +0100128 .setStatus("Danger")
pineafan6702cef2022-06-13 17:52:37 +0100129 ]
pineafan63fc5e22022-08-04 22:04:10 +0100130 });
pineafan6702cef2022-06-13 17:52:37 +0100131 }
pineafan3a02ea32022-08-11 21:35:04 +0100132 if (!role) return;
pineafan63fc5e22022-08-04 22:04:10 +0100133 role = role as Discord.Role;
pineafan3a02ea32022-08-11 21:35:04 +0100134 if (role.guild.id !== interaction.guild!.id)
Skyler Grey75ea9172022-08-06 10:22:23 +0100135 return interaction.editReply({
136 embeds: [
137 new EmojiEmbed()
138 .setTitle("Tickets > Support Ping")
Skyler Grey11236ba2022-08-08 21:13:33 +0100139 .setDescription("You must choose a role in this server")
Skyler Grey75ea9172022-08-06 10:22:23 +0100140 .setStatus("Danger")
141 .setEmoji("GUILD.ROLE.DELETE")
142 ]
143 });
pineafan6702cef2022-06-13 17:52:37 +0100144 }
145
pineafan63fc5e22022-08-04 22:04:10 +0100146 const confirmation = await new confirmationMessage(interaction)
pineafan62ce1922022-08-25 20:34:45 +0100147 .setEmoji("GUILD.TICKET.ARCHIVED", "GUILD.TICKET.CLOSE")
pineafan6702cef2022-06-13 17:52:37 +0100148 .setTitle("Tickets")
149 .setDescription(
Skyler Grey11236ba2022-08-08 21:13:33 +0100150 (options.category ? `**Category:** ${options.category.name}\n` : "") +
151 (options.maxtickets ? `**Max Tickets:** ${options.maxtickets}\n` : "") +
152 (options.supportping ? `**Support Ping:** ${options.supportping.name}\n` : "") +
Skyler Grey75ea9172022-08-06 10:22:23 +0100153 (options.enabled !== null
154 ? `**Enabled:** ${
pineafan62ce1922022-08-25 20:34:45 +0100155 options.enabled
156 ? `${getEmojiByName("CONTROL.TICK")} Yes`
157 : `${getEmojiByName("CONTROL.CROSS")} No`
158 }\n`
Skyler Grey75ea9172022-08-06 10:22:23 +0100159 : "") +
160 "\nAre you sure you want to apply these settings?"
pineafan6702cef2022-06-13 17:52:37 +0100161 )
162 .setColor("Warning")
163 .setInverted(true)
pineafan63fc5e22022-08-04 22:04:10 +0100164 .send(true);
165 if (confirmation.cancelled) return;
pineafan6702cef2022-06-13 17:52:37 +0100166 if (confirmation.success) {
pineafan3a02ea32022-08-11 21:35:04 +0100167 const toUpdate: Record<string, string | boolean | number> = {};
Skyler Grey11236ba2022-08-08 21:13:33 +0100168 if (options.enabled !== null) toUpdate["tickets.enabled"] = options.enabled;
169 if (options.category) toUpdate["tickets.category"] = options.category.id;
170 if (options.maxtickets) toUpdate["tickets.maxTickets"] = options.maxtickets;
171 if (options.supportping) toUpdate["tickets.supportRole"] = options.supportping.id;
pineafan6702cef2022-06-13 17:52:37 +0100172 try {
pineafan3a02ea32022-08-11 21:35:04 +0100173 await client.database.guilds.write(interaction.guild!.id, toUpdate);
pineafan6702cef2022-06-13 17:52:37 +0100174 } catch (e) {
175 return interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100176 embeds: [
177 new EmojiEmbed()
178 .setTitle("Tickets")
Skyler Grey11236ba2022-08-08 21:13:33 +0100179 .setDescription("Something went wrong and the staff notifications channel could not be set")
Skyler Grey75ea9172022-08-06 10:22:23 +0100180 .setStatus("Danger")
181 .setEmoji("GUILD.TICKET.DELETE")
182 ],
183 components: []
pineafan6702cef2022-06-13 17:52:37 +0100184 });
185 }
186 } else {
187 return interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100188 embeds: [
189 new EmojiEmbed()
190 .setTitle("Tickets")
191 .setDescription("No changes were made")
192 .setStatus("Success")
193 .setEmoji("GUILD.TICKET.OPEN")
194 ],
195 components: []
pineafan6702cef2022-06-13 17:52:37 +0100196 });
197 }
198 }
pineafan3a02ea32022-08-11 21:35:04 +0100199 let data = await client.database.guilds.read(interaction.guild!.id);
Skyler Grey75ea9172022-08-06 10:22:23 +0100200 data.tickets.customTypes = (data.tickets.customTypes || []).filter(
Skyler Grey11236ba2022-08-08 21:13:33 +0100201 (value: string, index: number, array: string[]) => array.indexOf(value) === index
Skyler Grey75ea9172022-08-06 10:22:23 +0100202 );
pineafan6702cef2022-06-13 17:52:37 +0100203 let lastClicked = "";
Skyler Grey1a67e182022-08-04 23:05:44 +0100204 let embed: EmojiEmbed;
pineafan6702cef2022-06-13 17:52:37 +0100205 data = {
206 enabled: data.tickets.enabled,
207 category: data.tickets.category,
208 maxTickets: data.tickets.maxTickets,
209 supportRole: data.tickets.supportRole,
210 useCustom: data.tickets.useCustom,
211 types: data.tickets.types,
212 customTypes: data.tickets.customTypes
pineafan63fc5e22022-08-04 22:04:10 +0100213 };
Skyler Greyad002172022-08-16 18:48:26 +0100214 let timedOut = false;
215 while (!timedOut) {
pineafan4edb7762022-06-26 19:21:04 +0100216 embed = new EmojiEmbed()
pineafan6702cef2022-06-13 17:52:37 +0100217 .setTitle("Tickets")
218 .setDescription(
Skyler Grey11236ba2022-08-08 21:13:33 +0100219 `${data.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${
220 data.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
Skyler Grey75ea9172022-08-06 10:22:23 +0100221 }\n` +
Skyler Grey11236ba2022-08-08 21:13:33 +0100222 `${data.category ? "" : getEmojiByName("TICKETS.REPORT")} **Category:** ${
Skyler Grey75ea9172022-08-06 10:22:23 +0100223 data.category ? `<#${data.category}>` : "*None set*"
224 }\n` +
Skyler Grey11236ba2022-08-08 21:13:33 +0100225 `**Max Tickets:** ${data.maxTickets ? data.maxTickets : "*No limit*"}\n` +
226 `**Support Ping:** ${data.supportRole ? `<@&${data.supportRole}>` : "*None set*"}\n\n` +
227 (data.useCustom && data.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") +
Skyler Grey75ea9172022-08-06 10:22:23 +0100228 `${data.useCustom ? "Custom" : "Default"} types in use` +
229 "\n\n" +
Skyler Grey11236ba2022-08-08 21:13:33 +0100230 `${getEmojiByName("TICKETS.REPORT")} *Indicates a setting stopping tickets from being used*`
pineafan6702cef2022-06-13 17:52:37 +0100231 )
232 .setStatus("Success")
pineafan63fc5e22022-08-04 22:04:10 +0100233 .setEmoji("GUILD.TICKET.OPEN");
Skyler Grey75ea9172022-08-06 10:22:23 +0100234 m = (await interaction.editReply({
235 embeds: [embed],
236 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400237 new ActionRowBuilder().addComponents([
238 new ButtonBuilder()
Skyler Grey11236ba2022-08-08 21:13:33 +0100239 .setLabel("Tickets " + (data.enabled ? "enabled" : "disabled"))
240 .setEmoji(getEmojiByName("CONTROL." + (data.enabled ? "TICK" : "CROSS"), "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400241 .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100242 .setCustomId("enabled"),
TheCodedProf21c08592022-09-13 14:14:43 -0400243 new ButtonBuilder()
Skyler Grey11236ba2022-08-08 21:13:33 +0100244 .setLabel(lastClicked === "cat" ? "Click again to confirm" : "Clear category")
Skyler Grey75ea9172022-08-06 10:22:23 +0100245 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400246 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100247 .setCustomId("clearCategory")
248 .setDisabled(data.category === null),
TheCodedProf21c08592022-09-13 14:14:43 -0400249 new ButtonBuilder()
Skyler Grey11236ba2022-08-08 21:13:33 +0100250 .setLabel(lastClicked === "max" ? "Click again to confirm" : "Reset max tickets")
Skyler Grey75ea9172022-08-06 10:22:23 +0100251 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400252 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 .setCustomId("clearMaxTickets")
254 .setDisabled(data.maxTickets === 5),
TheCodedProf21c08592022-09-13 14:14:43 -0400255 new ButtonBuilder()
Skyler Grey11236ba2022-08-08 21:13:33 +0100256 .setLabel(lastClicked === "sup" ? "Click again to confirm" : "Clear support ping")
Skyler Grey75ea9172022-08-06 10:22:23 +0100257 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400258 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100259 .setCustomId("clearSupportPing")
260 .setDisabled(data.supportRole === null)
261 ]),
TheCodedProf21c08592022-09-13 14:14:43 -0400262 new ActionRowBuilder().addComponents([
263 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100264 .setLabel("Manage types")
265 .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400266 .setStyle(ButtonStyle.Secondary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100267 .setCustomId("manageTypes"),
TheCodedProf21c08592022-09-13 14:14:43 -0400268 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100269 .setLabel("Add create ticket button")
270 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400271 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100272 .setCustomId("send")
273 ])
274 ]
275 })) as Message;
Skyler Grey1a67e182022-08-04 23:05:44 +0100276 let i: MessageComponentInteraction;
pineafan6702cef2022-06-13 17:52:37 +0100277 try {
pineafanbd02b4a2022-08-05 22:01:38 +0100278 i = await m.awaitMessageComponent({ time: 300000 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100279 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100280 timedOut = true;
281 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100282 }
pineafan63fc5e22022-08-04 22:04:10 +0100283 i.deferUpdate();
TheCodedProf21c08592022-09-13 14:14:43 -0400284 if ((i.component as Component).customId === "clearCategory") {
pineafane23c4ec2022-07-27 21:56:27 +0100285 if (lastClicked === "cat") {
pineafan6702cef2022-06-13 17:52:37 +0100286 lastClicked = "";
pineafan3a02ea32022-08-11 21:35:04 +0100287 await client.database.guilds.write(interaction.guild!.id, null, ["tickets.category"]);
pineafan6702cef2022-06-13 17:52:37 +0100288 data.category = undefined;
289 } else lastClicked = "cat";
TheCodedProf21c08592022-09-13 14:14:43 -0400290 } else if ((i.component as Component).customId === "clearMaxTickets") {
pineafane23c4ec2022-07-27 21:56:27 +0100291 if (lastClicked === "max") {
pineafan6702cef2022-06-13 17:52:37 +0100292 lastClicked = "";
pineafan3a02ea32022-08-11 21:35:04 +0100293 await client.database.guilds.write(interaction.guild!.id, null, ["tickets.maxTickets"]);
pineafan6702cef2022-06-13 17:52:37 +0100294 data.maxTickets = 5;
295 } else lastClicked = "max";
TheCodedProf21c08592022-09-13 14:14:43 -0400296 } else if ((i.component as Component).customId === "clearSupportPing") {
pineafane23c4ec2022-07-27 21:56:27 +0100297 if (lastClicked === "sup") {
pineafan6702cef2022-06-13 17:52:37 +0100298 lastClicked = "";
pineafan3a02ea32022-08-11 21:35:04 +0100299 await client.database.guilds.write(interaction.guild!.id, null, ["tickets.supportRole"]);
pineafan6702cef2022-06-13 17:52:37 +0100300 data.supportRole = undefined;
301 } else lastClicked = "sup";
TheCodedProf21c08592022-09-13 14:14:43 -0400302 } else if ((i.component as Component).customId === "send") {
pineafan41d93562022-07-30 22:10:15 +0100303 const ticketMessages = [
Skyler Grey75ea9172022-08-06 10:22:23 +0100304 {
305 label: "Create ticket",
306 description: "Click the button below to create a ticket"
307 },
308 {
309 label: "Issues, questions or feedback?",
Skyler Grey11236ba2022-08-08 21:13:33 +0100310 description: "Click below to open a ticket and get help from our staff team"
Skyler Grey75ea9172022-08-06 10:22:23 +0100311 },
312 {
313 label: "Contact Us",
Skyler Grey11236ba2022-08-08 21:13:33 +0100314 description: "Click the button below to speak to us privately"
Skyler Grey75ea9172022-08-06 10:22:23 +0100315 }
pineafan63fc5e22022-08-04 22:04:10 +0100316 ];
Skyler Greyad002172022-08-16 18:48:26 +0100317 let innerTimedOut = false;
318 let templateSelected = false;
319 while (!innerTimedOut && !templateSelected) {
pineafan63fc5e22022-08-04 22:04:10 +0100320 const enabled = data.enabled && data.category !== null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100321 await interaction.editReply({
322 embeds: [
323 new EmojiEmbed()
324 .setTitle("Ticket Button")
Skyler Grey11236ba2022-08-08 21:13:33 +0100325 .setDescription("Select a message template to send in this channel")
Skyler Grey75ea9172022-08-06 10:22:23 +0100326 .setFooter({
327 text: enabled
328 ? ""
329 : "Tickets are not set up correctly so the button may not work for users. Check the main menu to find which options must be set."
330 })
331 .setStatus(enabled ? "Success" : "Warning")
332 .setEmoji("GUILD.ROLES.CREATE")
333 ],
334 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400335 new ActionRowBuilder().addComponents([
336 new SelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100337 .setOptions(
338 ticketMessages.map(
339 (
340 t: {
341 label: string;
342 description: string;
343 value?: string;
344 },
345 index
346 ) => {
347 t.value = index.toString();
348 return t as {
349 value: string;
350 label: string;
351 description: string;
352 };
353 }
354 )
355 )
356 .setCustomId("template")
357 .setMaxValues(1)
358 .setMinValues(1)
359 .setPlaceholder("Select a message template")
360 ]),
TheCodedProf21c08592022-09-13 14:14:43 -0400361 new ActionRowBuilder().addComponents([
362 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100363 .setCustomId("back")
364 .setLabel("Back")
365 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400366 .setStyle(ButtonStyle.Danger),
367 new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary),
368 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100369 .setCustomId("custom")
370 .setLabel("Custom")
371 .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400372 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100373 ])
374 ]
375 });
Skyler Grey1a67e182022-08-04 23:05:44 +0100376 let i: MessageComponentInteraction;
pineafan41d93562022-07-30 22:10:15 +0100377 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100378 i = await m.awaitMessageComponent({ time: 300000 });
379 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100380 innerTimedOut = true;
381 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100382 }
TheCodedProf21c08592022-09-13 14:14:43 -0400383 if ((i.component as Component).customId === "template") {
pineafan63fc5e22022-08-04 22:04:10 +0100384 i.deferUpdate();
pineafan3a02ea32022-08-11 21:35:04 +0100385 await interaction.channel!.send({
Skyler Grey75ea9172022-08-06 10:22:23 +0100386 embeds: [
387 new EmojiEmbed()
pineafan3a02ea32022-08-11 21:35:04 +0100388 .setTitle(ticketMessages[parseInt((i as SelectMenuInteraction).values[0]!)]!.label)
Skyler Grey75ea9172022-08-06 10:22:23 +0100389 .setDescription(
pineafan3a02ea32022-08-11 21:35:04 +0100390 ticketMessages[parseInt((i as SelectMenuInteraction).values[0]!)]!.description
Skyler Grey75ea9172022-08-06 10:22:23 +0100391 )
392 .setStatus("Success")
393 .setEmoji("GUILD.TICKET.OPEN")
394 ],
395 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400396 new ActionRowBuilder().addComponents([
397 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100398 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100399 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400400 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100401 .setCustomId("createticket")
402 ])
403 ]
404 });
Skyler Greyad002172022-08-16 18:48:26 +0100405 templateSelected = true;
406 continue;
TheCodedProf21c08592022-09-13 14:14:43 -0400407 } else if ((i.component as Component).customId === "blank") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100408 i.deferUpdate();
pineafan3a02ea32022-08-11 21:35:04 +0100409 await interaction.channel!.send({
Skyler Grey75ea9172022-08-06 10:22:23 +0100410 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400411 new ActionRowBuilder().addComponents([
412 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100413 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100414 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400415 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100416 .setCustomId("createticket")
417 ])
418 ]
419 });
Skyler Greyad002172022-08-16 18:48:26 +0100420 templateSelected = true;
421 continue;
TheCodedProf21c08592022-09-13 14:14:43 -0400422 } else if ((i.component as Component).customId === "custom") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100423 await i.showModal(
424 new Discord.Modal()
425 .setCustomId("modal")
426 .setTitle("Enter embed details")
427 .addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400428 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100429 new TextInputComponent()
430 .setCustomId("title")
431 .setLabel("Title")
432 .setMaxLength(256)
433 .setRequired(true)
434 .setStyle("SHORT")
435 ),
TheCodedProf21c08592022-09-13 14:14:43 -0400436 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100437 new TextInputComponent()
438 .setCustomId("description")
439 .setLabel("Description")
440 .setMaxLength(4000)
441 .setRequired(true)
442 .setStyle("PARAGRAPH")
443 )
444 )
445 );
pineafan41d93562022-07-30 22:10:15 +0100446 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100447 embeds: [
448 new EmojiEmbed()
449 .setTitle("Ticket Button")
Skyler Grey11236ba2022-08-08 21:13:33 +0100450 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100451 .setStatus("Success")
452 .setEmoji("GUILD.TICKET.OPEN")
453 ],
454 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400455 new ActionRowBuilder().addComponents([
456 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100457 .setLabel("Back")
Skyler Grey11236ba2022-08-08 21:13:33 +0100458 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400459 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100460 .setCustomId("back")
461 ])
462 ]
pineafan41d93562022-07-30 22:10:15 +0100463 });
464 let out;
465 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100466 out = await modalInteractionCollector(
467 m,
pineafan3a02ea32022-08-11 21:35:04 +0100468 (m) => m.channel!.id === interaction.channel!.id,
Skyler Grey75ea9172022-08-06 10:22:23 +0100469 (m) => m.customId === "modify"
470 );
471 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100472 innerTimedOut = true;
473 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100474 }
pineafan41d93562022-07-30 22:10:15 +0100475 if (out.fields) {
pineafan63fc5e22022-08-04 22:04:10 +0100476 const title = out.fields.getTextInputValue("title");
Skyler Grey11236ba2022-08-08 21:13:33 +0100477 const description = out.fields.getTextInputValue("description");
Skyler Grey75ea9172022-08-06 10:22:23 +0100478 await interaction.channel.send({
479 embeds: [
480 new EmojiEmbed()
481 .setTitle(title)
482 .setDescription(description)
483 .setStatus("Success")
484 .setEmoji("GUILD.TICKET.OPEN")
485 ],
486 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400487 new ActionRowBuilder().addComponents([
488 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100489 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100490 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400491 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100492 .setCustomId("createticket")
493 ])
494 ]
495 });
Skyler Greyad002172022-08-16 18:48:26 +0100496 templateSelected = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100497 }
pineafan41d93562022-07-30 22:10:15 +0100498 }
499 }
TheCodedProf21c08592022-09-13 14:14:43 -0400500 } else if ((i.component as Component).customId === "enabled") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100501 await client.database.guilds.write(interaction.guild.id, {
502 "tickets.enabled": !data.enabled
503 });
pineafan6702cef2022-06-13 17:52:37 +0100504 data.enabled = !data.enabled;
TheCodedProf21c08592022-09-13 14:14:43 -0400505 } else if ((i.component as Component).customId === "manageTypes") {
Skyler Grey1a67e182022-08-04 23:05:44 +0100506 data = await manageTypes(interaction, data, m as Message);
pineafan6702cef2022-06-13 17:52:37 +0100507 }
508 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100509 await interaction.editReply({
Skyler Greyad002172022-08-16 18:48:26 +0100510 embeds: [embed.setFooter({ text: "Message timed out" })],
Skyler Grey75ea9172022-08-06 10:22:23 +0100511 components: []
512 });
pineafan63fc5e22022-08-04 22:04:10 +0100513};
pineafan4f164f32022-02-26 22:07:12 +0000514
Skyler Grey11236ba2022-08-08 21:13:33 +0100515async function manageTypes(interaction: CommandInteraction, data: GuildConfig["tickets"], m: Message) {
Skyler Greyad002172022-08-16 18:48:26 +0100516 let timedOut = false;
517 let backPressed = false;
518 while (!timedOut && !backPressed) {
pineafan6702cef2022-06-13 17:52:37 +0100519 if (data.useCustom) {
pineafan63fc5e22022-08-04 22:04:10 +0100520 const customTypes = data.customTypes;
pineafanc6158ab2022-06-17 16:34:07 +0100521 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100522 embeds: [
523 new EmojiEmbed()
524 .setTitle("Tickets > Types")
525 .setDescription(
526 "**Custom types enabled**\n\n" +
527 "**Types in use:**\n" +
Skyler Grey11236ba2022-08-08 21:13:33 +0100528 (customTypes !== null ? customTypes.map((t) => `> ${t}`).join("\n") : "*None set*") +
Skyler Grey75ea9172022-08-06 10:22:23 +0100529 "\n\n" +
530 (customTypes === null
531 ? `${getEmojiByName(
532 "TICKETS.REPORT"
533 )} Having no types will disable tickets. Please add at least 1 type or use default types`
534 : "")
pineafan6702cef2022-06-13 17:52:37 +0100535 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100536 .setStatus("Success")
537 .setEmoji("GUILD.TICKET.OPEN")
538 ],
539 components: (customTypes
540 ? [
TheCodedProf21c08592022-09-13 14:14:43 -0400541 new ActionRowBuilder().addComponents([
542 new Discord.SelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100543 .setCustomId("removeTypes")
544 .setPlaceholder("Select types to remove")
545 .setMaxValues(customTypes.length)
546 .setMinValues(1)
547 .addOptions(
548 customTypes.map((t) => ({
549 label: t,
550 value: t
551 }))
552 )
553 ])
554 ]
555 : []
556 ).concat([
TheCodedProf21c08592022-09-13 14:14:43 -0400557 new ActionRowBuilder().addComponents([
558 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100559 .setLabel("Back")
560 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400561 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100562 .setCustomId("back"),
TheCodedProf21c08592022-09-13 14:14:43 -0400563 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100564 .setLabel("Add new type")
Skyler Grey11236ba2022-08-08 21:13:33 +0100565 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400566 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100567 .setCustomId("addType")
Skyler Grey11236ba2022-08-08 21:13:33 +0100568 .setDisabled(customTypes !== null && customTypes.length >= 25),
TheCodedProf21c08592022-09-13 14:14:43 -0400569 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100570 .setLabel("Switch to default types")
TheCodedProf21c08592022-09-13 14:14:43 -0400571 .setStyle(ButtonStyle.Secondary)
pineafan63fc5e22022-08-04 22:04:10 +0100572 .setCustomId("switchToDefault")
pineafan6702cef2022-06-13 17:52:37 +0100573 ])
574 ])
575 });
576 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100577 const inUse = toHexArray(data.types, ticketTypes);
578 const options = [];
Skyler Grey75ea9172022-08-06 10:22:23 +0100579 ticketTypes.forEach((type) => {
580 options.push(
581 new SelectMenuOption({
582 label: capitalize(type),
583 value: type,
Skyler Grey11236ba2022-08-08 21:13:33 +0100584 emoji: client.emojis.cache.get(getEmojiByName(`TICKETS.${type.toUpperCase()}`, "id")),
Skyler Grey75ea9172022-08-06 10:22:23 +0100585 default: inUse.includes(type)
586 })
587 );
pineafan63fc5e22022-08-04 22:04:10 +0100588 });
TheCodedProf21c08592022-09-13 14:14:43 -0400589 const selectPane = new ActionRowBuilder().addComponents([
590 new Discord.SelectMenuBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100591 .addOptions(options)
592 .setCustomId("types")
593 .setMaxValues(ticketTypes.length)
594 .setMinValues(1)
595 .setPlaceholder("Select types to use")
pineafan63fc5e22022-08-04 22:04:10 +0100596 ]);
pineafanc6158ab2022-06-17 16:34:07 +0100597 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100598 embeds: [
599 new EmojiEmbed()
600 .setTitle("Tickets > Types")
601 .setDescription(
602 "**Default types enabled**\n\n" +
603 "**Types in use:**\n" +
604 inUse
Skyler Grey11236ba2022-08-08 21:13:33 +0100605 .map((t) => `> ${getEmojiByName("TICKETS." + t.toUpperCase())} ${capitalize(t)}`)
Skyler Grey75ea9172022-08-06 10:22:23 +0100606 .join("\n")
607 )
608 .setStatus("Success")
609 .setEmoji("GUILD.TICKET.OPEN")
610 ],
611 components: [
pineafan6702cef2022-06-13 17:52:37 +0100612 selectPane,
TheCodedProf21c08592022-09-13 14:14:43 -0400613 new ActionRowBuilder().addComponents([
614 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100615 .setLabel("Back")
616 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400617 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100618 .setCustomId("back"),
TheCodedProf21c08592022-09-13 14:14:43 -0400619 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100620 .setLabel("Switch to custom types")
TheCodedProf21c08592022-09-13 14:14:43 -0400621 .setStyle(ButtonStyle.Secondary)
pineafan63fc5e22022-08-04 22:04:10 +0100622 .setCustomId("switchToCustom")
pineafan6702cef2022-06-13 17:52:37 +0100623 ])
624 ]
625 });
626 }
627 let i;
628 try {
pineafanc6158ab2022-06-17 16:34:07 +0100629 i = await m.awaitMessageComponent({ time: 300000 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100630 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100631 timedOut = true;
632 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100633 }
pineafane23c4ec2022-07-27 21:56:27 +0100634 if (i.component.customId === "types") {
pineafan63fc5e22022-08-04 22:04:10 +0100635 i.deferUpdate();
636 const types = toHexInteger(i.values, ticketTypes);
Skyler Grey75ea9172022-08-06 10:22:23 +0100637 await client.database.guilds.write(interaction.guild.id, {
638 "tickets.types": types
639 });
pineafan6702cef2022-06-13 17:52:37 +0100640 data.types = types;
pineafane23c4ec2022-07-27 21:56:27 +0100641 } else if (i.component.customId === "removeTypes") {
pineafan63fc5e22022-08-04 22:04:10 +0100642 i.deferUpdate();
643 const types = i.values;
pineafan6702cef2022-06-13 17:52:37 +0100644 let customTypes = data.customTypes;
645 if (customTypes) {
646 customTypes = customTypes.filter((t) => !types.includes(t));
647 customTypes = customTypes.length > 0 ? customTypes : null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100648 await client.database.guilds.write(interaction.guild.id, {
649 "tickets.customTypes": customTypes
650 });
pineafan6702cef2022-06-13 17:52:37 +0100651 data.customTypes = customTypes;
652 }
pineafane23c4ec2022-07-27 21:56:27 +0100653 } else if (i.component.customId === "addType") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100654 await i.showModal(
655 new Discord.Modal()
656 .setCustomId("modal")
657 .setTitle("Enter a name for the new type")
658 .addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400659 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100660 new TextInputComponent()
661 .setCustomId("type")
662 .setLabel("Name")
663 .setMaxLength(100)
664 .setMinLength(1)
665 .setPlaceholder('E.g. "Server Idea"')
666 .setRequired(true)
667 .setStyle("SHORT")
668 )
669 )
670 );
pineafan6702cef2022-06-13 17:52:37 +0100671 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100672 embeds: [
673 new EmojiEmbed()
674 .setTitle("Tickets > Types")
Skyler Grey11236ba2022-08-08 21:13:33 +0100675 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100676 .setStatus("Success")
677 .setEmoji("GUILD.TICKET.OPEN")
678 ],
679 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400680 new ActionRowBuilder().addComponents([
681 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100682 .setLabel("Back")
683 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400684 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100685 .setCustomId("back")
686 ])
687 ]
pineafan6702cef2022-06-13 17:52:37 +0100688 });
pineafan4edb7762022-06-26 19:21:04 +0100689 let out;
pineafan6702cef2022-06-13 17:52:37 +0100690 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100691 out = await modalInteractionCollector(
692 m,
693 (m) => m.channel.id === interaction.channel.id,
694 (m) => m.customId === "addType"
695 );
696 } catch (e) {
697 continue;
698 }
pineafan6702cef2022-06-13 17:52:37 +0100699 if (out.fields) {
700 let toAdd = out.fields.getTextInputValue("type");
Skyler Grey75ea9172022-08-06 10:22:23 +0100701 if (!toAdd) {
702 continue;
703 }
pineafan63fc5e22022-08-04 22:04:10 +0100704 toAdd = toAdd.substring(0, 80);
pineafan6702cef2022-06-13 17:52:37 +0100705 try {
Skyler Grey11236ba2022-08-08 21:13:33 +0100706 await client.database.guilds.append(interaction.guild.id, "tickets.customTypes", toAdd);
Skyler Grey75ea9172022-08-06 10:22:23 +0100707 } catch {
708 continue;
709 }
Skyler Greyad002172022-08-16 18:48:26 +0100710 data.customTypes = data.customTypes ?? [];
pineafan6702cef2022-06-13 17:52:37 +0100711 if (!data.customTypes.includes(toAdd)) {
712 data.customTypes.push(toAdd);
713 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100714 } else {
715 continue;
716 }
pineafane23c4ec2022-07-27 21:56:27 +0100717 } else if (i.component.customId === "switchToDefault") {
pineafan63fc5e22022-08-04 22:04:10 +0100718 i.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100719 await client.database.guilds.write(interaction.guild.id, { "tickets.useCustom": false }, []);
pineafan6702cef2022-06-13 17:52:37 +0100720 data.useCustom = false;
pineafane23c4ec2022-07-27 21:56:27 +0100721 } else if (i.component.customId === "switchToCustom") {
pineafan63fc5e22022-08-04 22:04:10 +0100722 i.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100723 await client.database.guilds.write(interaction.guild.id, { "tickets.useCustom": true }, []);
pineafan6702cef2022-06-13 17:52:37 +0100724 data.useCustom = true;
725 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100726 i.deferUpdate();
Skyler Greyad002172022-08-16 18:48:26 +0100727 backPressed = true;
pineafan6702cef2022-06-13 17:52:37 +0100728 }
729 }
pineafan63fc5e22022-08-04 22:04:10 +0100730 return data;
pineafan6702cef2022-06-13 17:52:37 +0100731}
732
Skyler Grey1a67e182022-08-04 23:05:44 +0100733const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100734 const member = interaction.member as Discord.GuildMember;
735 if (!member.permissions.has("MANAGE_GUILD"))
pineafan3a02ea32022-08-11 21:35:04 +0100736 throw new Error("You must have the *Manage Server* permission to use this command");
pineafan6702cef2022-06-13 17:52:37 +0100737 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100738};
pineafan4f164f32022-02-26 22:07:12 +0000739
740export { command };
741export { callback };
Skyler Grey1a67e182022-08-04 23:05:44 +0100742export { check };