blob: 70ba52d2b11b099aed35e96bde9bd2f5b6fe2f82 [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> => {
PineaFana00db1b2023-01-02 15:32:54 +000065 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +010066 let m = (await interaction.reply({
67 embeds: LoadingEmbed,
68 ephemeral: true,
69 fetchReply: true
70 })) as Message;
pineafan63fc5e22022-08-04 22:04:10 +010071 const options = {
Skyler Greyad002172022-08-16 18:48:26 +010072 enabled: interaction.options.getString("enabled")?.startsWith("yes") as boolean | null,
pineafan6702cef2022-06-13 17:52:37 +010073 category: interaction.options.getChannel("category"),
74 maxtickets: interaction.options.getNumber("maxticketsperuser"),
75 supportping: interaction.options.getRole("supportrole")
pineafan63fc5e22022-08-04 22:04:10 +010076 };
Skyler Grey11236ba2022-08-08 21:13:33 +010077 if (options.enabled !== null || options.category || options.maxtickets || options.supportping) {
pineafan6702cef2022-06-13 17:52:37 +010078 if (options.category) {
pineafan3a02ea32022-08-11 21:35:04 +010079 let channel: GuildChannel | null;
pineafan6702cef2022-06-13 17:52:37 +010080 try {
PineaFana00db1b2023-01-02 15:32:54 +000081 channel = await interaction.guild.channels.fetch(options.category.id);
pineafan6702cef2022-06-13 17:52:37 +010082 } catch {
83 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +010084 embeds: [
85 new EmojiEmbed()
86 .setEmoji("CHANNEL.TEXT.DELETE")
87 .setTitle("Tickets > Category")
Skyler Grey11236ba2022-08-08 21:13:33 +010088 .setDescription("The channel you provided is not a valid category")
Skyler Grey75ea9172022-08-06 10:22:23 +010089 .setStatus("Danger")
pineafan6702cef2022-06-13 17:52:37 +010090 ]
pineafan63fc5e22022-08-04 22:04:10 +010091 });
pineafan6702cef2022-06-13 17:52:37 +010092 }
pineafan3a02ea32022-08-11 21:35:04 +010093 if (!channel) return;
pineafan63fc5e22022-08-04 22:04:10 +010094 channel = channel as Discord.CategoryChannel;
PineaFana00db1b2023-01-02 15:32:54 +000095 if (channel.guild.id !== interaction.guild.id)
Skyler Grey75ea9172022-08-06 10:22:23 +010096 return interaction.editReply({
97 embeds: [
98 new EmojiEmbed()
99 .setTitle("Tickets > Category")
Skyler Grey11236ba2022-08-08 21:13:33 +0100100 .setDescription("You must choose a category in this server")
Skyler Grey75ea9172022-08-06 10:22:23 +0100101 .setStatus("Danger")
102 .setEmoji("CHANNEL.TEXT.DELETE")
103 ]
104 });
pineafan6702cef2022-06-13 17:52:37 +0100105 }
106 if (options.maxtickets) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100107 if (options.maxtickets < 1)
108 return interaction.editReply({
109 embeds: [
110 new EmojiEmbed()
111 .setTitle("Tickets > Max Tickets")
Skyler Grey11236ba2022-08-08 21:13:33 +0100112 .setDescription("You must choose a number greater than 0")
Skyler Grey75ea9172022-08-06 10:22:23 +0100113 .setStatus("Danger")
114 .setEmoji("CHANNEL.TEXT.DELETE")
115 ]
116 });
pineafan6702cef2022-06-13 17:52:37 +0100117 }
pineafan3a02ea32022-08-11 21:35:04 +0100118 let role: Role | null;
pineafan6702cef2022-06-13 17:52:37 +0100119 if (options.supportping) {
120 try {
PineaFana00db1b2023-01-02 15:32:54 +0000121 role = await interaction.guild.roles.fetch(options.supportping.id);
pineafan6702cef2022-06-13 17:52:37 +0100122 } catch {
123 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100124 embeds: [
125 new EmojiEmbed()
126 .setEmoji("GUILD.ROLE.DELETE")
127 .setTitle("Tickets > Support Ping")
Skyler Grey11236ba2022-08-08 21:13:33 +0100128 .setDescription("The role you provided is not a valid role")
Skyler Grey75ea9172022-08-06 10:22:23 +0100129 .setStatus("Danger")
pineafan6702cef2022-06-13 17:52:37 +0100130 ]
pineafan63fc5e22022-08-04 22:04:10 +0100131 });
pineafan6702cef2022-06-13 17:52:37 +0100132 }
pineafan3a02ea32022-08-11 21:35:04 +0100133 if (!role) return;
pineafan63fc5e22022-08-04 22:04:10 +0100134 role = role as Discord.Role;
PineaFana00db1b2023-01-02 15:32:54 +0000135 if (role.guild.id !== interaction.guild.id)
Skyler Grey75ea9172022-08-06 10:22:23 +0100136 return interaction.editReply({
137 embeds: [
138 new EmojiEmbed()
139 .setTitle("Tickets > Support Ping")
Skyler Grey11236ba2022-08-08 21:13:33 +0100140 .setDescription("You must choose a role in this server")
Skyler Grey75ea9172022-08-06 10:22:23 +0100141 .setStatus("Danger")
142 .setEmoji("GUILD.ROLE.DELETE")
143 ]
144 });
pineafan6702cef2022-06-13 17:52:37 +0100145 }
146
pineafan63fc5e22022-08-04 22:04:10 +0100147 const confirmation = await new confirmationMessage(interaction)
pineafan62ce1922022-08-25 20:34:45 +0100148 .setEmoji("GUILD.TICKET.ARCHIVED", "GUILD.TICKET.CLOSE")
pineafan6702cef2022-06-13 17:52:37 +0100149 .setTitle("Tickets")
150 .setDescription(
Skyler Grey11236ba2022-08-08 21:13:33 +0100151 (options.category ? `**Category:** ${options.category.name}\n` : "") +
152 (options.maxtickets ? `**Max Tickets:** ${options.maxtickets}\n` : "") +
153 (options.supportping ? `**Support Ping:** ${options.supportping.name}\n` : "") +
Skyler Grey75ea9172022-08-06 10:22:23 +0100154 (options.enabled !== null
155 ? `**Enabled:** ${
pineafan62ce1922022-08-25 20:34:45 +0100156 options.enabled
157 ? `${getEmojiByName("CONTROL.TICK")} Yes`
158 : `${getEmojiByName("CONTROL.CROSS")} No`
159 }\n`
Skyler Grey75ea9172022-08-06 10:22:23 +0100160 : "") +
161 "\nAre you sure you want to apply these settings?"
pineafan6702cef2022-06-13 17:52:37 +0100162 )
163 .setColor("Warning")
164 .setInverted(true)
pineafan63fc5e22022-08-04 22:04:10 +0100165 .send(true);
166 if (confirmation.cancelled) return;
pineafan6702cef2022-06-13 17:52:37 +0100167 if (confirmation.success) {
pineafan3a02ea32022-08-11 21:35:04 +0100168 const toUpdate: Record<string, string | boolean | number> = {};
Skyler Grey11236ba2022-08-08 21:13:33 +0100169 if (options.enabled !== null) toUpdate["tickets.enabled"] = options.enabled;
170 if (options.category) toUpdate["tickets.category"] = options.category.id;
171 if (options.maxtickets) toUpdate["tickets.maxTickets"] = options.maxtickets;
172 if (options.supportping) toUpdate["tickets.supportRole"] = options.supportping.id;
pineafan6702cef2022-06-13 17:52:37 +0100173 try {
PineaFana00db1b2023-01-02 15:32:54 +0000174 await client.database.guilds.write(interaction.guild.id, toUpdate);
pineafan6702cef2022-06-13 17:52:37 +0100175 } catch (e) {
176 return interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100177 embeds: [
178 new EmojiEmbed()
179 .setTitle("Tickets")
Skyler Grey11236ba2022-08-08 21:13:33 +0100180 .setDescription("Something went wrong and the staff notifications channel could not be set")
Skyler Grey75ea9172022-08-06 10:22:23 +0100181 .setStatus("Danger")
182 .setEmoji("GUILD.TICKET.DELETE")
183 ],
184 components: []
pineafan6702cef2022-06-13 17:52:37 +0100185 });
186 }
187 } else {
188 return interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100189 embeds: [
190 new EmojiEmbed()
191 .setTitle("Tickets")
192 .setDescription("No changes were made")
193 .setStatus("Success")
194 .setEmoji("GUILD.TICKET.OPEN")
195 ],
196 components: []
pineafan6702cef2022-06-13 17:52:37 +0100197 });
198 }
199 }
PineaFana00db1b2023-01-02 15:32:54 +0000200 let data = await client.database.guilds.read(interaction.guild.id);
Skyler Grey75ea9172022-08-06 10:22:23 +0100201 data.tickets.customTypes = (data.tickets.customTypes || []).filter(
Skyler Grey11236ba2022-08-08 21:13:33 +0100202 (value: string, index: number, array: string[]) => array.indexOf(value) === index
Skyler Grey75ea9172022-08-06 10:22:23 +0100203 );
pineafan6702cef2022-06-13 17:52:37 +0100204 let lastClicked = "";
Skyler Grey1a67e182022-08-04 23:05:44 +0100205 let embed: EmojiEmbed;
pineafan6702cef2022-06-13 17:52:37 +0100206 data = {
207 enabled: data.tickets.enabled,
208 category: data.tickets.category,
209 maxTickets: data.tickets.maxTickets,
210 supportRole: data.tickets.supportRole,
211 useCustom: data.tickets.useCustom,
212 types: data.tickets.types,
213 customTypes: data.tickets.customTypes
pineafan63fc5e22022-08-04 22:04:10 +0100214 };
Skyler Greyad002172022-08-16 18:48:26 +0100215 let timedOut = false;
216 while (!timedOut) {
pineafan4edb7762022-06-26 19:21:04 +0100217 embed = new EmojiEmbed()
pineafan6702cef2022-06-13 17:52:37 +0100218 .setTitle("Tickets")
219 .setDescription(
Skyler Grey11236ba2022-08-08 21:13:33 +0100220 `${data.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${
221 data.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
Skyler Grey75ea9172022-08-06 10:22:23 +0100222 }\n` +
Skyler Grey11236ba2022-08-08 21:13:33 +0100223 `${data.category ? "" : getEmojiByName("TICKETS.REPORT")} **Category:** ${
Skyler Grey75ea9172022-08-06 10:22:23 +0100224 data.category ? `<#${data.category}>` : "*None set*"
225 }\n` +
Skyler Grey11236ba2022-08-08 21:13:33 +0100226 `**Max Tickets:** ${data.maxTickets ? data.maxTickets : "*No limit*"}\n` +
227 `**Support Ping:** ${data.supportRole ? `<@&${data.supportRole}>` : "*None set*"}\n\n` +
228 (data.useCustom && data.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") +
Skyler Grey75ea9172022-08-06 10:22:23 +0100229 `${data.useCustom ? "Custom" : "Default"} types in use` +
230 "\n\n" +
Skyler Grey11236ba2022-08-08 21:13:33 +0100231 `${getEmojiByName("TICKETS.REPORT")} *Indicates a setting stopping tickets from being used*`
pineafan6702cef2022-06-13 17:52:37 +0100232 )
233 .setStatus("Success")
pineafan63fc5e22022-08-04 22:04:10 +0100234 .setEmoji("GUILD.TICKET.OPEN");
Skyler Grey75ea9172022-08-06 10:22:23 +0100235 m = (await interaction.editReply({
236 embeds: [embed],
237 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400238 new ActionRowBuilder().addComponents([
239 new ButtonBuilder()
Skyler Grey11236ba2022-08-08 21:13:33 +0100240 .setLabel("Tickets " + (data.enabled ? "enabled" : "disabled"))
241 .setEmoji(getEmojiByName("CONTROL." + (data.enabled ? "TICK" : "CROSS"), "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400242 .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100243 .setCustomId("enabled"),
TheCodedProf21c08592022-09-13 14:14:43 -0400244 new ButtonBuilder()
Skyler Grey11236ba2022-08-08 21:13:33 +0100245 .setLabel(lastClicked === "cat" ? "Click again to confirm" : "Clear category")
Skyler Grey75ea9172022-08-06 10:22:23 +0100246 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400247 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100248 .setCustomId("clearCategory")
249 .setDisabled(data.category === null),
TheCodedProf21c08592022-09-13 14:14:43 -0400250 new ButtonBuilder()
Skyler Grey11236ba2022-08-08 21:13:33 +0100251 .setLabel(lastClicked === "max" ? "Click again to confirm" : "Reset max tickets")
Skyler Grey75ea9172022-08-06 10:22:23 +0100252 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400253 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100254 .setCustomId("clearMaxTickets")
255 .setDisabled(data.maxTickets === 5),
TheCodedProf21c08592022-09-13 14:14:43 -0400256 new ButtonBuilder()
Skyler Grey11236ba2022-08-08 21:13:33 +0100257 .setLabel(lastClicked === "sup" ? "Click again to confirm" : "Clear support ping")
Skyler Grey75ea9172022-08-06 10:22:23 +0100258 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400259 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100260 .setCustomId("clearSupportPing")
261 .setDisabled(data.supportRole === null)
262 ]),
TheCodedProf21c08592022-09-13 14:14:43 -0400263 new ActionRowBuilder().addComponents([
264 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 .setLabel("Manage types")
266 .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400267 .setStyle(ButtonStyle.Secondary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100268 .setCustomId("manageTypes"),
TheCodedProf21c08592022-09-13 14:14:43 -0400269 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100270 .setLabel("Add create ticket button")
271 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400272 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100273 .setCustomId("send")
274 ])
275 ]
276 })) as Message;
Skyler Grey1a67e182022-08-04 23:05:44 +0100277 let i: MessageComponentInteraction;
pineafan6702cef2022-06-13 17:52:37 +0100278 try {
pineafanbd02b4a2022-08-05 22:01:38 +0100279 i = await m.awaitMessageComponent({ time: 300000 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100280 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100281 timedOut = true;
282 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100283 }
pineafan63fc5e22022-08-04 22:04:10 +0100284 i.deferUpdate();
TheCodedProf21c08592022-09-13 14:14:43 -0400285 if ((i.component as Component).customId === "clearCategory") {
pineafane23c4ec2022-07-27 21:56:27 +0100286 if (lastClicked === "cat") {
pineafan6702cef2022-06-13 17:52:37 +0100287 lastClicked = "";
PineaFana00db1b2023-01-02 15:32:54 +0000288 await client.database.guilds.write(interaction.guild.id, null, ["tickets.category"]);
pineafan6702cef2022-06-13 17:52:37 +0100289 data.category = undefined;
290 } else lastClicked = "cat";
TheCodedProf21c08592022-09-13 14:14:43 -0400291 } else if ((i.component as Component).customId === "clearMaxTickets") {
pineafane23c4ec2022-07-27 21:56:27 +0100292 if (lastClicked === "max") {
pineafan6702cef2022-06-13 17:52:37 +0100293 lastClicked = "";
PineaFana00db1b2023-01-02 15:32:54 +0000294 await client.database.guilds.write(interaction.guild.id, null, ["tickets.maxTickets"]);
pineafan6702cef2022-06-13 17:52:37 +0100295 data.maxTickets = 5;
296 } else lastClicked = "max";
TheCodedProf21c08592022-09-13 14:14:43 -0400297 } else if ((i.component as Component).customId === "clearSupportPing") {
pineafane23c4ec2022-07-27 21:56:27 +0100298 if (lastClicked === "sup") {
pineafan6702cef2022-06-13 17:52:37 +0100299 lastClicked = "";
PineaFana00db1b2023-01-02 15:32:54 +0000300 await client.database.guilds.write(interaction.guild.id, null, ["tickets.supportRole"]);
pineafan6702cef2022-06-13 17:52:37 +0100301 data.supportRole = undefined;
302 } else lastClicked = "sup";
TheCodedProf21c08592022-09-13 14:14:43 -0400303 } else if ((i.component as Component).customId === "send") {
pineafan41d93562022-07-30 22:10:15 +0100304 const ticketMessages = [
Skyler Grey75ea9172022-08-06 10:22:23 +0100305 {
306 label: "Create ticket",
307 description: "Click the button below to create a ticket"
308 },
309 {
310 label: "Issues, questions or feedback?",
Skyler Grey11236ba2022-08-08 21:13:33 +0100311 description: "Click below to open a ticket and get help from our staff team"
Skyler Grey75ea9172022-08-06 10:22:23 +0100312 },
313 {
314 label: "Contact Us",
Skyler Grey11236ba2022-08-08 21:13:33 +0100315 description: "Click the button below to speak to us privately"
Skyler Grey75ea9172022-08-06 10:22:23 +0100316 }
pineafan63fc5e22022-08-04 22:04:10 +0100317 ];
Skyler Greyad002172022-08-16 18:48:26 +0100318 let innerTimedOut = false;
319 let templateSelected = false;
320 while (!innerTimedOut && !templateSelected) {
pineafan63fc5e22022-08-04 22:04:10 +0100321 const enabled = data.enabled && data.category !== null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100322 await interaction.editReply({
323 embeds: [
324 new EmojiEmbed()
325 .setTitle("Ticket Button")
Skyler Grey11236ba2022-08-08 21:13:33 +0100326 .setDescription("Select a message template to send in this channel")
Skyler Grey75ea9172022-08-06 10:22:23 +0100327 .setFooter({
328 text: enabled
329 ? ""
330 : "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."
331 })
332 .setStatus(enabled ? "Success" : "Warning")
333 .setEmoji("GUILD.ROLES.CREATE")
334 ],
335 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400336 new ActionRowBuilder().addComponents([
337 new SelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100338 .setOptions(
339 ticketMessages.map(
340 (
341 t: {
342 label: string;
343 description: string;
344 value?: string;
345 },
346 index
347 ) => {
348 t.value = index.toString();
349 return t as {
350 value: string;
351 label: string;
352 description: string;
353 };
354 }
355 )
356 )
357 .setCustomId("template")
358 .setMaxValues(1)
359 .setMinValues(1)
360 .setPlaceholder("Select a message template")
361 ]),
TheCodedProf21c08592022-09-13 14:14:43 -0400362 new ActionRowBuilder().addComponents([
363 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100364 .setCustomId("back")
365 .setLabel("Back")
366 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400367 .setStyle(ButtonStyle.Danger),
368 new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary),
369 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100370 .setCustomId("custom")
371 .setLabel("Custom")
372 .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400373 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100374 ])
375 ]
376 });
Skyler Grey1a67e182022-08-04 23:05:44 +0100377 let i: MessageComponentInteraction;
pineafan41d93562022-07-30 22:10:15 +0100378 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100379 i = await m.awaitMessageComponent({ time: 300000 });
380 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100381 innerTimedOut = true;
382 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100383 }
TheCodedProf21c08592022-09-13 14:14:43 -0400384 if ((i.component as Component).customId === "template") {
pineafan63fc5e22022-08-04 22:04:10 +0100385 i.deferUpdate();
pineafan3a02ea32022-08-11 21:35:04 +0100386 await interaction.channel!.send({
Skyler Grey75ea9172022-08-06 10:22:23 +0100387 embeds: [
388 new EmojiEmbed()
pineafan3a02ea32022-08-11 21:35:04 +0100389 .setTitle(ticketMessages[parseInt((i as SelectMenuInteraction).values[0]!)]!.label)
Skyler Grey75ea9172022-08-06 10:22:23 +0100390 .setDescription(
pineafan3a02ea32022-08-11 21:35:04 +0100391 ticketMessages[parseInt((i as SelectMenuInteraction).values[0]!)]!.description
Skyler Grey75ea9172022-08-06 10:22:23 +0100392 )
393 .setStatus("Success")
394 .setEmoji("GUILD.TICKET.OPEN")
395 ],
396 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400397 new ActionRowBuilder().addComponents([
398 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100399 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100400 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400401 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100402 .setCustomId("createticket")
403 ])
404 ]
405 });
Skyler Greyad002172022-08-16 18:48:26 +0100406 templateSelected = true;
407 continue;
TheCodedProf21c08592022-09-13 14:14:43 -0400408 } else if ((i.component as Component).customId === "blank") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100409 i.deferUpdate();
pineafan3a02ea32022-08-11 21:35:04 +0100410 await interaction.channel!.send({
Skyler Grey75ea9172022-08-06 10:22:23 +0100411 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400412 new ActionRowBuilder().addComponents([
413 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100414 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100415 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400416 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100417 .setCustomId("createticket")
418 ])
419 ]
420 });
Skyler Greyad002172022-08-16 18:48:26 +0100421 templateSelected = true;
422 continue;
TheCodedProf21c08592022-09-13 14:14:43 -0400423 } else if ((i.component as Component).customId === "custom") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100424 await i.showModal(
425 new Discord.Modal()
426 .setCustomId("modal")
427 .setTitle("Enter embed details")
428 .addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400429 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100430 new TextInputComponent()
431 .setCustomId("title")
432 .setLabel("Title")
433 .setMaxLength(256)
434 .setRequired(true)
435 .setStyle("SHORT")
436 ),
TheCodedProf21c08592022-09-13 14:14:43 -0400437 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100438 new TextInputComponent()
439 .setCustomId("description")
440 .setLabel("Description")
441 .setMaxLength(4000)
442 .setRequired(true)
443 .setStyle("PARAGRAPH")
444 )
445 )
446 );
pineafan41d93562022-07-30 22:10:15 +0100447 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100448 embeds: [
449 new EmojiEmbed()
450 .setTitle("Ticket Button")
Skyler Grey11236ba2022-08-08 21:13:33 +0100451 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100452 .setStatus("Success")
453 .setEmoji("GUILD.TICKET.OPEN")
454 ],
455 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400456 new ActionRowBuilder().addComponents([
457 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100458 .setLabel("Back")
Skyler Grey11236ba2022-08-08 21:13:33 +0100459 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400460 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100461 .setCustomId("back")
462 ])
463 ]
pineafan41d93562022-07-30 22:10:15 +0100464 });
465 let out;
466 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100467 out = await modalInteractionCollector(
468 m,
pineafan3a02ea32022-08-11 21:35:04 +0100469 (m) => m.channel!.id === interaction.channel!.id,
Skyler Grey75ea9172022-08-06 10:22:23 +0100470 (m) => m.customId === "modify"
471 );
472 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100473 innerTimedOut = true;
474 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100475 }
pineafan41d93562022-07-30 22:10:15 +0100476 if (out.fields) {
pineafan63fc5e22022-08-04 22:04:10 +0100477 const title = out.fields.getTextInputValue("title");
Skyler Grey11236ba2022-08-08 21:13:33 +0100478 const description = out.fields.getTextInputValue("description");
Skyler Grey75ea9172022-08-06 10:22:23 +0100479 await interaction.channel.send({
480 embeds: [
481 new EmojiEmbed()
482 .setTitle(title)
483 .setDescription(description)
484 .setStatus("Success")
485 .setEmoji("GUILD.TICKET.OPEN")
486 ],
487 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400488 new ActionRowBuilder().addComponents([
489 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100490 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100491 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400492 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100493 .setCustomId("createticket")
494 ])
495 ]
496 });
Skyler Greyad002172022-08-16 18:48:26 +0100497 templateSelected = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100498 }
pineafan41d93562022-07-30 22:10:15 +0100499 }
500 }
TheCodedProf21c08592022-09-13 14:14:43 -0400501 } else if ((i.component as Component).customId === "enabled") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100502 await client.database.guilds.write(interaction.guild.id, {
503 "tickets.enabled": !data.enabled
504 });
pineafan6702cef2022-06-13 17:52:37 +0100505 data.enabled = !data.enabled;
TheCodedProf21c08592022-09-13 14:14:43 -0400506 } else if ((i.component as Component).customId === "manageTypes") {
Skyler Grey1a67e182022-08-04 23:05:44 +0100507 data = await manageTypes(interaction, data, m as Message);
pineafan6702cef2022-06-13 17:52:37 +0100508 }
509 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100510 await interaction.editReply({
Skyler Greyad002172022-08-16 18:48:26 +0100511 embeds: [embed.setFooter({ text: "Message timed out" })],
Skyler Grey75ea9172022-08-06 10:22:23 +0100512 components: []
513 });
pineafan63fc5e22022-08-04 22:04:10 +0100514};
pineafan4f164f32022-02-26 22:07:12 +0000515
Skyler Grey11236ba2022-08-08 21:13:33 +0100516async function manageTypes(interaction: CommandInteraction, data: GuildConfig["tickets"], m: Message) {
Skyler Greyad002172022-08-16 18:48:26 +0100517 let timedOut = false;
518 let backPressed = false;
519 while (!timedOut && !backPressed) {
pineafan6702cef2022-06-13 17:52:37 +0100520 if (data.useCustom) {
pineafan63fc5e22022-08-04 22:04:10 +0100521 const customTypes = data.customTypes;
pineafanc6158ab2022-06-17 16:34:07 +0100522 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100523 embeds: [
524 new EmojiEmbed()
525 .setTitle("Tickets > Types")
526 .setDescription(
527 "**Custom types enabled**\n\n" +
528 "**Types in use:**\n" +
Skyler Grey11236ba2022-08-08 21:13:33 +0100529 (customTypes !== null ? customTypes.map((t) => `> ${t}`).join("\n") : "*None set*") +
Skyler Grey75ea9172022-08-06 10:22:23 +0100530 "\n\n" +
531 (customTypes === null
532 ? `${getEmojiByName(
533 "TICKETS.REPORT"
534 )} Having no types will disable tickets. Please add at least 1 type or use default types`
535 : "")
pineafan6702cef2022-06-13 17:52:37 +0100536 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100537 .setStatus("Success")
538 .setEmoji("GUILD.TICKET.OPEN")
539 ],
540 components: (customTypes
541 ? [
TheCodedProf21c08592022-09-13 14:14:43 -0400542 new ActionRowBuilder().addComponents([
543 new Discord.SelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100544 .setCustomId("removeTypes")
545 .setPlaceholder("Select types to remove")
546 .setMaxValues(customTypes.length)
547 .setMinValues(1)
548 .addOptions(
549 customTypes.map((t) => ({
550 label: t,
551 value: t
552 }))
553 )
554 ])
555 ]
556 : []
557 ).concat([
TheCodedProf21c08592022-09-13 14:14:43 -0400558 new ActionRowBuilder().addComponents([
559 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100560 .setLabel("Back")
561 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400562 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100563 .setCustomId("back"),
TheCodedProf21c08592022-09-13 14:14:43 -0400564 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100565 .setLabel("Add new type")
Skyler Grey11236ba2022-08-08 21:13:33 +0100566 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400567 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100568 .setCustomId("addType")
Skyler Grey11236ba2022-08-08 21:13:33 +0100569 .setDisabled(customTypes !== null && customTypes.length >= 25),
TheCodedProf21c08592022-09-13 14:14:43 -0400570 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100571 .setLabel("Switch to default types")
TheCodedProf21c08592022-09-13 14:14:43 -0400572 .setStyle(ButtonStyle.Secondary)
pineafan63fc5e22022-08-04 22:04:10 +0100573 .setCustomId("switchToDefault")
pineafan6702cef2022-06-13 17:52:37 +0100574 ])
575 ])
576 });
577 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100578 const inUse = toHexArray(data.types, ticketTypes);
579 const options = [];
Skyler Grey75ea9172022-08-06 10:22:23 +0100580 ticketTypes.forEach((type) => {
581 options.push(
582 new SelectMenuOption({
583 label: capitalize(type),
584 value: type,
Skyler Grey11236ba2022-08-08 21:13:33 +0100585 emoji: client.emojis.cache.get(getEmojiByName(`TICKETS.${type.toUpperCase()}`, "id")),
Skyler Grey75ea9172022-08-06 10:22:23 +0100586 default: inUse.includes(type)
587 })
588 );
pineafan63fc5e22022-08-04 22:04:10 +0100589 });
TheCodedProf21c08592022-09-13 14:14:43 -0400590 const selectPane = new ActionRowBuilder().addComponents([
591 new Discord.SelectMenuBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100592 .addOptions(options)
593 .setCustomId("types")
594 .setMaxValues(ticketTypes.length)
595 .setMinValues(1)
596 .setPlaceholder("Select types to use")
pineafan63fc5e22022-08-04 22:04:10 +0100597 ]);
pineafanc6158ab2022-06-17 16:34:07 +0100598 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100599 embeds: [
600 new EmojiEmbed()
601 .setTitle("Tickets > Types")
602 .setDescription(
603 "**Default types enabled**\n\n" +
604 "**Types in use:**\n" +
605 inUse
Skyler Grey11236ba2022-08-08 21:13:33 +0100606 .map((t) => `> ${getEmojiByName("TICKETS." + t.toUpperCase())} ${capitalize(t)}`)
Skyler Grey75ea9172022-08-06 10:22:23 +0100607 .join("\n")
608 )
609 .setStatus("Success")
610 .setEmoji("GUILD.TICKET.OPEN")
611 ],
612 components: [
pineafan6702cef2022-06-13 17:52:37 +0100613 selectPane,
TheCodedProf21c08592022-09-13 14:14:43 -0400614 new ActionRowBuilder().addComponents([
615 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100616 .setLabel("Back")
617 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400618 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100619 .setCustomId("back"),
TheCodedProf21c08592022-09-13 14:14:43 -0400620 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100621 .setLabel("Switch to custom types")
TheCodedProf21c08592022-09-13 14:14:43 -0400622 .setStyle(ButtonStyle.Secondary)
pineafan63fc5e22022-08-04 22:04:10 +0100623 .setCustomId("switchToCustom")
pineafan6702cef2022-06-13 17:52:37 +0100624 ])
625 ]
626 });
627 }
628 let i;
629 try {
pineafanc6158ab2022-06-17 16:34:07 +0100630 i = await m.awaitMessageComponent({ time: 300000 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100631 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100632 timedOut = true;
633 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100634 }
pineafane23c4ec2022-07-27 21:56:27 +0100635 if (i.component.customId === "types") {
pineafan63fc5e22022-08-04 22:04:10 +0100636 i.deferUpdate();
637 const types = toHexInteger(i.values, ticketTypes);
Skyler Grey75ea9172022-08-06 10:22:23 +0100638 await client.database.guilds.write(interaction.guild.id, {
639 "tickets.types": types
640 });
pineafan6702cef2022-06-13 17:52:37 +0100641 data.types = types;
pineafane23c4ec2022-07-27 21:56:27 +0100642 } else if (i.component.customId === "removeTypes") {
pineafan63fc5e22022-08-04 22:04:10 +0100643 i.deferUpdate();
644 const types = i.values;
pineafan6702cef2022-06-13 17:52:37 +0100645 let customTypes = data.customTypes;
646 if (customTypes) {
647 customTypes = customTypes.filter((t) => !types.includes(t));
648 customTypes = customTypes.length > 0 ? customTypes : null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100649 await client.database.guilds.write(interaction.guild.id, {
650 "tickets.customTypes": customTypes
651 });
pineafan6702cef2022-06-13 17:52:37 +0100652 data.customTypes = customTypes;
653 }
pineafane23c4ec2022-07-27 21:56:27 +0100654 } else if (i.component.customId === "addType") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100655 await i.showModal(
656 new Discord.Modal()
657 .setCustomId("modal")
658 .setTitle("Enter a name for the new type")
659 .addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400660 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100661 new TextInputComponent()
662 .setCustomId("type")
663 .setLabel("Name")
664 .setMaxLength(100)
665 .setMinLength(1)
666 .setPlaceholder('E.g. "Server Idea"')
667 .setRequired(true)
668 .setStyle("SHORT")
669 )
670 )
671 );
pineafan6702cef2022-06-13 17:52:37 +0100672 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100673 embeds: [
674 new EmojiEmbed()
675 .setTitle("Tickets > Types")
Skyler Grey11236ba2022-08-08 21:13:33 +0100676 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100677 .setStatus("Success")
678 .setEmoji("GUILD.TICKET.OPEN")
679 ],
680 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400681 new ActionRowBuilder().addComponents([
682 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100683 .setLabel("Back")
684 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400685 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100686 .setCustomId("back")
687 ])
688 ]
pineafan6702cef2022-06-13 17:52:37 +0100689 });
pineafan4edb7762022-06-26 19:21:04 +0100690 let out;
pineafan6702cef2022-06-13 17:52:37 +0100691 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100692 out = await modalInteractionCollector(
693 m,
694 (m) => m.channel.id === interaction.channel.id,
695 (m) => m.customId === "addType"
696 );
697 } catch (e) {
698 continue;
699 }
pineafan6702cef2022-06-13 17:52:37 +0100700 if (out.fields) {
701 let toAdd = out.fields.getTextInputValue("type");
Skyler Grey75ea9172022-08-06 10:22:23 +0100702 if (!toAdd) {
703 continue;
704 }
pineafan63fc5e22022-08-04 22:04:10 +0100705 toAdd = toAdd.substring(0, 80);
pineafan6702cef2022-06-13 17:52:37 +0100706 try {
Skyler Grey11236ba2022-08-08 21:13:33 +0100707 await client.database.guilds.append(interaction.guild.id, "tickets.customTypes", toAdd);
Skyler Grey75ea9172022-08-06 10:22:23 +0100708 } catch {
709 continue;
710 }
Skyler Greyad002172022-08-16 18:48:26 +0100711 data.customTypes = data.customTypes ?? [];
pineafan6702cef2022-06-13 17:52:37 +0100712 if (!data.customTypes.includes(toAdd)) {
713 data.customTypes.push(toAdd);
714 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100715 } else {
716 continue;
717 }
pineafane23c4ec2022-07-27 21:56:27 +0100718 } else if (i.component.customId === "switchToDefault") {
pineafan63fc5e22022-08-04 22:04:10 +0100719 i.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100720 await client.database.guilds.write(interaction.guild.id, { "tickets.useCustom": false }, []);
pineafan6702cef2022-06-13 17:52:37 +0100721 data.useCustom = false;
pineafane23c4ec2022-07-27 21:56:27 +0100722 } else if (i.component.customId === "switchToCustom") {
pineafan63fc5e22022-08-04 22:04:10 +0100723 i.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100724 await client.database.guilds.write(interaction.guild.id, { "tickets.useCustom": true }, []);
pineafan6702cef2022-06-13 17:52:37 +0100725 data.useCustom = true;
726 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100727 i.deferUpdate();
Skyler Greyad002172022-08-16 18:48:26 +0100728 backPressed = true;
pineafan6702cef2022-06-13 17:52:37 +0100729 }
730 }
pineafan63fc5e22022-08-04 22:04:10 +0100731 return data;
pineafan6702cef2022-06-13 17:52:37 +0100732}
733
Skyler Grey1a67e182022-08-04 23:05:44 +0100734const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100735 const member = interaction.member as Discord.GuildMember;
PineaFana00db1b2023-01-02 15:32:54 +0000736 if (!member.permissions.has("ManageGuild"))
pineafan3a02ea32022-08-11 21:35:04 +0100737 throw new Error("You must have the *Manage Server* permission to use this command");
pineafan6702cef2022-06-13 17:52:37 +0100738 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100739};
pineafan4f164f32022-02-26 22:07:12 +0000740
741export { command };
742export { callback };
Skyler Grey1a67e182022-08-04 23:05:44 +0100743export { check };