blob: ee0f2d147dc1e8cb17fccd135be8a6ff2b2c013f [file] [log] [blame]
PineaFan0d06edc2023-01-17 22:10:31 +00001import { LoadingEmbed } from "../../utils/defaults.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 {
PineaFan0d06edc2023-01-17 22:10:31 +0000279 i = await m.awaitMessageComponent({
280 time: 300000,
281 filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
282 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100283 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100284 timedOut = true;
285 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100286 }
pineafan63fc5e22022-08-04 22:04:10 +0100287 i.deferUpdate();
TheCodedProf21c08592022-09-13 14:14:43 -0400288 if ((i.component as Component).customId === "clearCategory") {
pineafane23c4ec2022-07-27 21:56:27 +0100289 if (lastClicked === "cat") {
pineafan6702cef2022-06-13 17:52:37 +0100290 lastClicked = "";
PineaFana00db1b2023-01-02 15:32:54 +0000291 await client.database.guilds.write(interaction.guild.id, null, ["tickets.category"]);
pineafan6702cef2022-06-13 17:52:37 +0100292 data.category = undefined;
293 } else lastClicked = "cat";
TheCodedProf21c08592022-09-13 14:14:43 -0400294 } else if ((i.component as Component).customId === "clearMaxTickets") {
pineafane23c4ec2022-07-27 21:56:27 +0100295 if (lastClicked === "max") {
pineafan6702cef2022-06-13 17:52:37 +0100296 lastClicked = "";
PineaFana00db1b2023-01-02 15:32:54 +0000297 await client.database.guilds.write(interaction.guild.id, null, ["tickets.maxTickets"]);
pineafan6702cef2022-06-13 17:52:37 +0100298 data.maxTickets = 5;
299 } else lastClicked = "max";
TheCodedProf21c08592022-09-13 14:14:43 -0400300 } else if ((i.component as Component).customId === "clearSupportPing") {
pineafane23c4ec2022-07-27 21:56:27 +0100301 if (lastClicked === "sup") {
pineafan6702cef2022-06-13 17:52:37 +0100302 lastClicked = "";
PineaFana00db1b2023-01-02 15:32:54 +0000303 await client.database.guilds.write(interaction.guild.id, null, ["tickets.supportRole"]);
pineafan6702cef2022-06-13 17:52:37 +0100304 data.supportRole = undefined;
305 } else lastClicked = "sup";
TheCodedProf21c08592022-09-13 14:14:43 -0400306 } else if ((i.component as Component).customId === "send") {
pineafan41d93562022-07-30 22:10:15 +0100307 const ticketMessages = [
Skyler Grey75ea9172022-08-06 10:22:23 +0100308 {
309 label: "Create ticket",
310 description: "Click the button below to create a ticket"
311 },
312 {
313 label: "Issues, questions or feedback?",
Skyler Grey11236ba2022-08-08 21:13:33 +0100314 description: "Click below to open a ticket and get help from our staff team"
Skyler Grey75ea9172022-08-06 10:22:23 +0100315 },
316 {
317 label: "Contact Us",
Skyler Grey11236ba2022-08-08 21:13:33 +0100318 description: "Click the button below to speak to us privately"
Skyler Grey75ea9172022-08-06 10:22:23 +0100319 }
pineafan63fc5e22022-08-04 22:04:10 +0100320 ];
Skyler Greyad002172022-08-16 18:48:26 +0100321 let innerTimedOut = false;
322 let templateSelected = false;
323 while (!innerTimedOut && !templateSelected) {
pineafan63fc5e22022-08-04 22:04:10 +0100324 const enabled = data.enabled && data.category !== null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100325 await interaction.editReply({
326 embeds: [
327 new EmojiEmbed()
328 .setTitle("Ticket Button")
Skyler Grey11236ba2022-08-08 21:13:33 +0100329 .setDescription("Select a message template to send in this channel")
Skyler Grey75ea9172022-08-06 10:22:23 +0100330 .setFooter({
331 text: enabled
332 ? ""
333 : "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."
334 })
335 .setStatus(enabled ? "Success" : "Warning")
336 .setEmoji("GUILD.ROLES.CREATE")
337 ],
338 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400339 new ActionRowBuilder().addComponents([
340 new SelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100341 .setOptions(
342 ticketMessages.map(
343 (
344 t: {
345 label: string;
346 description: string;
347 value?: string;
348 },
349 index
350 ) => {
351 t.value = index.toString();
352 return t as {
353 value: string;
354 label: string;
355 description: string;
356 };
357 }
358 )
359 )
360 .setCustomId("template")
361 .setMaxValues(1)
362 .setMinValues(1)
363 .setPlaceholder("Select a message template")
364 ]),
TheCodedProf21c08592022-09-13 14:14:43 -0400365 new ActionRowBuilder().addComponents([
366 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100367 .setCustomId("back")
368 .setLabel("Back")
369 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400370 .setStyle(ButtonStyle.Danger),
371 new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary),
372 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100373 .setCustomId("custom")
374 .setLabel("Custom")
375 .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400376 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100377 ])
378 ]
379 });
Skyler Grey1a67e182022-08-04 23:05:44 +0100380 let i: MessageComponentInteraction;
pineafan41d93562022-07-30 22:10:15 +0100381 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000382 i = await m.awaitMessageComponent({
383 time: 300000,
384 filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
385 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100386 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100387 innerTimedOut = true;
388 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100389 }
TheCodedProf21c08592022-09-13 14:14:43 -0400390 if ((i.component as Component).customId === "template") {
pineafan63fc5e22022-08-04 22:04:10 +0100391 i.deferUpdate();
pineafan3a02ea32022-08-11 21:35:04 +0100392 await interaction.channel!.send({
Skyler Grey75ea9172022-08-06 10:22:23 +0100393 embeds: [
394 new EmojiEmbed()
pineafan3a02ea32022-08-11 21:35:04 +0100395 .setTitle(ticketMessages[parseInt((i as SelectMenuInteraction).values[0]!)]!.label)
Skyler Grey75ea9172022-08-06 10:22:23 +0100396 .setDescription(
pineafan3a02ea32022-08-11 21:35:04 +0100397 ticketMessages[parseInt((i as SelectMenuInteraction).values[0]!)]!.description
Skyler Grey75ea9172022-08-06 10:22:23 +0100398 )
399 .setStatus("Success")
400 .setEmoji("GUILD.TICKET.OPEN")
401 ],
402 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400403 new ActionRowBuilder().addComponents([
404 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100405 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100406 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400407 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100408 .setCustomId("createticket")
409 ])
410 ]
411 });
Skyler Greyad002172022-08-16 18:48:26 +0100412 templateSelected = true;
413 continue;
TheCodedProf21c08592022-09-13 14:14:43 -0400414 } else if ((i.component as Component).customId === "blank") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100415 i.deferUpdate();
pineafan3a02ea32022-08-11 21:35:04 +0100416 await interaction.channel!.send({
Skyler Grey75ea9172022-08-06 10:22:23 +0100417 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400418 new ActionRowBuilder().addComponents([
419 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100420 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100421 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400422 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100423 .setCustomId("createticket")
424 ])
425 ]
426 });
Skyler Greyad002172022-08-16 18:48:26 +0100427 templateSelected = true;
428 continue;
TheCodedProf21c08592022-09-13 14:14:43 -0400429 } else if ((i.component as Component).customId === "custom") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100430 await i.showModal(
431 new Discord.Modal()
432 .setCustomId("modal")
433 .setTitle("Enter embed details")
434 .addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400435 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100436 new TextInputComponent()
437 .setCustomId("title")
438 .setLabel("Title")
439 .setMaxLength(256)
440 .setRequired(true)
441 .setStyle("SHORT")
442 ),
TheCodedProf21c08592022-09-13 14:14:43 -0400443 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100444 new TextInputComponent()
445 .setCustomId("description")
446 .setLabel("Description")
447 .setMaxLength(4000)
448 .setRequired(true)
449 .setStyle("PARAGRAPH")
450 )
451 )
452 );
pineafan41d93562022-07-30 22:10:15 +0100453 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100454 embeds: [
455 new EmojiEmbed()
456 .setTitle("Ticket Button")
Skyler Grey11236ba2022-08-08 21:13:33 +0100457 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100458 .setStatus("Success")
459 .setEmoji("GUILD.TICKET.OPEN")
460 ],
461 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400462 new ActionRowBuilder().addComponents([
463 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100464 .setLabel("Back")
Skyler Grey11236ba2022-08-08 21:13:33 +0100465 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400466 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100467 .setCustomId("back")
468 ])
469 ]
pineafan41d93562022-07-30 22:10:15 +0100470 });
471 let out;
472 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100473 out = await modalInteractionCollector(
474 m,
pineafan3a02ea32022-08-11 21:35:04 +0100475 (m) => m.channel!.id === interaction.channel!.id,
Skyler Grey75ea9172022-08-06 10:22:23 +0100476 (m) => m.customId === "modify"
477 );
478 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100479 innerTimedOut = true;
480 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100481 }
pineafan41d93562022-07-30 22:10:15 +0100482 if (out.fields) {
pineafan63fc5e22022-08-04 22:04:10 +0100483 const title = out.fields.getTextInputValue("title");
Skyler Grey11236ba2022-08-08 21:13:33 +0100484 const description = out.fields.getTextInputValue("description");
Skyler Grey75ea9172022-08-06 10:22:23 +0100485 await interaction.channel.send({
486 embeds: [
487 new EmojiEmbed()
488 .setTitle(title)
489 .setDescription(description)
490 .setStatus("Success")
491 .setEmoji("GUILD.TICKET.OPEN")
492 ],
493 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400494 new ActionRowBuilder().addComponents([
495 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100496 .setLabel("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100497 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400498 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100499 .setCustomId("createticket")
500 ])
501 ]
502 });
Skyler Greyad002172022-08-16 18:48:26 +0100503 templateSelected = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100504 }
pineafan41d93562022-07-30 22:10:15 +0100505 }
506 }
TheCodedProf21c08592022-09-13 14:14:43 -0400507 } else if ((i.component as Component).customId === "enabled") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100508 await client.database.guilds.write(interaction.guild.id, {
509 "tickets.enabled": !data.enabled
510 });
pineafan6702cef2022-06-13 17:52:37 +0100511 data.enabled = !data.enabled;
TheCodedProf21c08592022-09-13 14:14:43 -0400512 } else if ((i.component as Component).customId === "manageTypes") {
Skyler Grey1a67e182022-08-04 23:05:44 +0100513 data = await manageTypes(interaction, data, m as Message);
pineafan6702cef2022-06-13 17:52:37 +0100514 }
515 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100516 await interaction.editReply({
Skyler Greyad002172022-08-16 18:48:26 +0100517 embeds: [embed.setFooter({ text: "Message timed out" })],
Skyler Grey75ea9172022-08-06 10:22:23 +0100518 components: []
519 });
pineafan63fc5e22022-08-04 22:04:10 +0100520};
pineafan4f164f32022-02-26 22:07:12 +0000521
Skyler Grey11236ba2022-08-08 21:13:33 +0100522async function manageTypes(interaction: CommandInteraction, data: GuildConfig["tickets"], m: Message) {
Skyler Greyad002172022-08-16 18:48:26 +0100523 let timedOut = false;
524 let backPressed = false;
525 while (!timedOut && !backPressed) {
pineafan6702cef2022-06-13 17:52:37 +0100526 if (data.useCustom) {
pineafan63fc5e22022-08-04 22:04:10 +0100527 const customTypes = data.customTypes;
pineafanc6158ab2022-06-17 16:34:07 +0100528 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100529 embeds: [
530 new EmojiEmbed()
531 .setTitle("Tickets > Types")
532 .setDescription(
533 "**Custom types enabled**\n\n" +
534 "**Types in use:**\n" +
Skyler Grey11236ba2022-08-08 21:13:33 +0100535 (customTypes !== null ? customTypes.map((t) => `> ${t}`).join("\n") : "*None set*") +
Skyler Grey75ea9172022-08-06 10:22:23 +0100536 "\n\n" +
537 (customTypes === null
538 ? `${getEmojiByName(
539 "TICKETS.REPORT"
540 )} Having no types will disable tickets. Please add at least 1 type or use default types`
541 : "")
pineafan6702cef2022-06-13 17:52:37 +0100542 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100543 .setStatus("Success")
544 .setEmoji("GUILD.TICKET.OPEN")
545 ],
546 components: (customTypes
547 ? [
TheCodedProf21c08592022-09-13 14:14:43 -0400548 new ActionRowBuilder().addComponents([
549 new Discord.SelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100550 .setCustomId("removeTypes")
551 .setPlaceholder("Select types to remove")
552 .setMaxValues(customTypes.length)
553 .setMinValues(1)
554 .addOptions(
555 customTypes.map((t) => ({
556 label: t,
557 value: t
558 }))
559 )
560 ])
561 ]
562 : []
563 ).concat([
TheCodedProf21c08592022-09-13 14:14:43 -0400564 new ActionRowBuilder().addComponents([
565 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100566 .setLabel("Back")
567 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400568 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100569 .setCustomId("back"),
TheCodedProf21c08592022-09-13 14:14:43 -0400570 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100571 .setLabel("Add new type")
Skyler Grey11236ba2022-08-08 21:13:33 +0100572 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400573 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100574 .setCustomId("addType")
Skyler Grey11236ba2022-08-08 21:13:33 +0100575 .setDisabled(customTypes !== null && customTypes.length >= 25),
TheCodedProf21c08592022-09-13 14:14:43 -0400576 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100577 .setLabel("Switch to default types")
TheCodedProf21c08592022-09-13 14:14:43 -0400578 .setStyle(ButtonStyle.Secondary)
pineafan63fc5e22022-08-04 22:04:10 +0100579 .setCustomId("switchToDefault")
pineafan6702cef2022-06-13 17:52:37 +0100580 ])
581 ])
582 });
583 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100584 const inUse = toHexArray(data.types, ticketTypes);
585 const options = [];
Skyler Grey75ea9172022-08-06 10:22:23 +0100586 ticketTypes.forEach((type) => {
587 options.push(
588 new SelectMenuOption({
589 label: capitalize(type),
590 value: type,
Skyler Grey11236ba2022-08-08 21:13:33 +0100591 emoji: client.emojis.cache.get(getEmojiByName(`TICKETS.${type.toUpperCase()}`, "id")),
Skyler Grey75ea9172022-08-06 10:22:23 +0100592 default: inUse.includes(type)
593 })
594 );
pineafan63fc5e22022-08-04 22:04:10 +0100595 });
TheCodedProf21c08592022-09-13 14:14:43 -0400596 const selectPane = new ActionRowBuilder().addComponents([
597 new Discord.SelectMenuBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100598 .addOptions(options)
599 .setCustomId("types")
600 .setMaxValues(ticketTypes.length)
601 .setMinValues(1)
602 .setPlaceholder("Select types to use")
pineafan63fc5e22022-08-04 22:04:10 +0100603 ]);
pineafanc6158ab2022-06-17 16:34:07 +0100604 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100605 embeds: [
606 new EmojiEmbed()
607 .setTitle("Tickets > Types")
608 .setDescription(
609 "**Default types enabled**\n\n" +
610 "**Types in use:**\n" +
611 inUse
Skyler Grey11236ba2022-08-08 21:13:33 +0100612 .map((t) => `> ${getEmojiByName("TICKETS." + t.toUpperCase())} ${capitalize(t)}`)
Skyler Grey75ea9172022-08-06 10:22:23 +0100613 .join("\n")
614 )
615 .setStatus("Success")
616 .setEmoji("GUILD.TICKET.OPEN")
617 ],
618 components: [
pineafan6702cef2022-06-13 17:52:37 +0100619 selectPane,
TheCodedProf21c08592022-09-13 14:14:43 -0400620 new ActionRowBuilder().addComponents([
621 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100622 .setLabel("Back")
623 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400624 .setStyle(ButtonStyle.Primary)
pineafan6702cef2022-06-13 17:52:37 +0100625 .setCustomId("back"),
TheCodedProf21c08592022-09-13 14:14:43 -0400626 new ButtonBuilder()
pineafan6702cef2022-06-13 17:52:37 +0100627 .setLabel("Switch to custom types")
TheCodedProf21c08592022-09-13 14:14:43 -0400628 .setStyle(ButtonStyle.Secondary)
pineafan63fc5e22022-08-04 22:04:10 +0100629 .setCustomId("switchToCustom")
pineafan6702cef2022-06-13 17:52:37 +0100630 ])
631 ]
632 });
633 }
634 let i;
635 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000636 i = await m.awaitMessageComponent({
637 time: 300000,
638 filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
639 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100640 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100641 timedOut = true;
642 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100643 }
pineafane23c4ec2022-07-27 21:56:27 +0100644 if (i.component.customId === "types") {
pineafan63fc5e22022-08-04 22:04:10 +0100645 i.deferUpdate();
646 const types = toHexInteger(i.values, ticketTypes);
Skyler Grey75ea9172022-08-06 10:22:23 +0100647 await client.database.guilds.write(interaction.guild.id, {
648 "tickets.types": types
649 });
pineafan6702cef2022-06-13 17:52:37 +0100650 data.types = types;
pineafane23c4ec2022-07-27 21:56:27 +0100651 } else if (i.component.customId === "removeTypes") {
pineafan63fc5e22022-08-04 22:04:10 +0100652 i.deferUpdate();
653 const types = i.values;
pineafan6702cef2022-06-13 17:52:37 +0100654 let customTypes = data.customTypes;
655 if (customTypes) {
656 customTypes = customTypes.filter((t) => !types.includes(t));
657 customTypes = customTypes.length > 0 ? customTypes : null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100658 await client.database.guilds.write(interaction.guild.id, {
659 "tickets.customTypes": customTypes
660 });
pineafan6702cef2022-06-13 17:52:37 +0100661 data.customTypes = customTypes;
662 }
pineafane23c4ec2022-07-27 21:56:27 +0100663 } else if (i.component.customId === "addType") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100664 await i.showModal(
665 new Discord.Modal()
666 .setCustomId("modal")
667 .setTitle("Enter a name for the new type")
668 .addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400669 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100670 new TextInputComponent()
671 .setCustomId("type")
672 .setLabel("Name")
673 .setMaxLength(100)
674 .setMinLength(1)
675 .setPlaceholder('E.g. "Server Idea"')
676 .setRequired(true)
677 .setStyle("SHORT")
678 )
679 )
680 );
pineafan6702cef2022-06-13 17:52:37 +0100681 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100682 embeds: [
683 new EmojiEmbed()
684 .setTitle("Tickets > Types")
Skyler Grey11236ba2022-08-08 21:13:33 +0100685 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100686 .setStatus("Success")
687 .setEmoji("GUILD.TICKET.OPEN")
688 ],
689 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400690 new ActionRowBuilder().addComponents([
691 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100692 .setLabel("Back")
693 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400694 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100695 .setCustomId("back")
696 ])
697 ]
pineafan6702cef2022-06-13 17:52:37 +0100698 });
pineafan4edb7762022-06-26 19:21:04 +0100699 let out;
pineafan6702cef2022-06-13 17:52:37 +0100700 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100701 out = await modalInteractionCollector(
702 m,
703 (m) => m.channel.id === interaction.channel.id,
704 (m) => m.customId === "addType"
705 );
706 } catch (e) {
707 continue;
708 }
pineafan6702cef2022-06-13 17:52:37 +0100709 if (out.fields) {
710 let toAdd = out.fields.getTextInputValue("type");
Skyler Grey75ea9172022-08-06 10:22:23 +0100711 if (!toAdd) {
712 continue;
713 }
pineafan63fc5e22022-08-04 22:04:10 +0100714 toAdd = toAdd.substring(0, 80);
pineafan6702cef2022-06-13 17:52:37 +0100715 try {
Skyler Grey11236ba2022-08-08 21:13:33 +0100716 await client.database.guilds.append(interaction.guild.id, "tickets.customTypes", toAdd);
Skyler Grey75ea9172022-08-06 10:22:23 +0100717 } catch {
718 continue;
719 }
Skyler Greyad002172022-08-16 18:48:26 +0100720 data.customTypes = data.customTypes ?? [];
pineafan6702cef2022-06-13 17:52:37 +0100721 if (!data.customTypes.includes(toAdd)) {
722 data.customTypes.push(toAdd);
723 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100724 } else {
725 continue;
726 }
pineafane23c4ec2022-07-27 21:56:27 +0100727 } else if (i.component.customId === "switchToDefault") {
pineafan63fc5e22022-08-04 22:04:10 +0100728 i.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100729 await client.database.guilds.write(interaction.guild.id, { "tickets.useCustom": false }, []);
pineafan6702cef2022-06-13 17:52:37 +0100730 data.useCustom = false;
pineafane23c4ec2022-07-27 21:56:27 +0100731 } else if (i.component.customId === "switchToCustom") {
pineafan63fc5e22022-08-04 22:04:10 +0100732 i.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100733 await client.database.guilds.write(interaction.guild.id, { "tickets.useCustom": true }, []);
pineafan6702cef2022-06-13 17:52:37 +0100734 data.useCustom = true;
735 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100736 i.deferUpdate();
Skyler Greyad002172022-08-16 18:48:26 +0100737 backPressed = true;
pineafan6702cef2022-06-13 17:52:37 +0100738 }
739 }
pineafan63fc5e22022-08-04 22:04:10 +0100740 return data;
pineafan6702cef2022-06-13 17:52:37 +0100741}
742
Skyler Grey1a67e182022-08-04 23:05:44 +0100743const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100744 const member = interaction.member as Discord.GuildMember;
PineaFana00db1b2023-01-02 15:32:54 +0000745 if (!member.permissions.has("ManageGuild"))
PineaFan0d06edc2023-01-17 22:10:31 +0000746 return "You must have the *Manage Server* permission to use this command";
pineafan6702cef2022-06-13 17:52:37 +0100747 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100748};
pineafan4f164f32022-02-26 22:07:12 +0000749
750export { command };
751export { callback };
Skyler Grey1a67e182022-08-04 23:05:44 +0100752export { check };