blob: 8dafe52c57c3d76c9294b30e4e58751f78b30f80 [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import Discord, {
2 ActionRowBuilder,
3 ButtonBuilder,
4 ButtonStyle,
5 CommandInteraction,
6 ButtonInteraction
7} from "discord.js";
pineafan813bdf42022-07-24 10:39:10 +01008import { tickets, toHexArray } from "../../utils/calculate.js";
9import client from "../../utils/client.js";
10import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
11import getEmojiByName from "../../utils/getEmojiByName.js";
TheCodedProff86ba092023-01-27 17:10:07 -050012import { getCommandMentionByName } from "../../utils/getCommandDataByName.js";
pineafan813bdf42022-07-24 10:39:10 +010013
14function capitalize(s: string) {
pineafan63fc5e22022-08-04 22:04:10 +010015 s = s.replace(/([A-Z])/g, " $1");
PineaFan0d06edc2023-01-17 22:10:31 +000016 return s.length < 3 ? s.toUpperCase() : s[0]!.toUpperCase() + s.slice(1).toLowerCase();
pineafan813bdf42022-07-24 10:39:10 +010017}
18
PineaFan538d3752023-01-12 21:48:23 +000019export default async function (interaction: CommandInteraction | ButtonInteraction) {
PineaFana00db1b2023-01-02 15:32:54 +000020 if (!interaction.guild) return;
Skyler Grey11236ba2022-08-08 21:13:33 +010021 const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger;
pineafan63fc5e22022-08-04 22:04:10 +010022 const config = await client.database.guilds.read(interaction.guild.id);
pineafan813bdf42022-07-24 10:39:10 +010023 if (!config.tickets.enabled || !config.tickets.category) {
Skyler Grey75ea9172022-08-06 10:22:23 +010024 return await interaction.reply({
25 embeds: [
26 new EmojiEmbed()
27 .setTitle("Tickets are disabled")
Skyler Grey11236ba2022-08-08 21:13:33 +010028 .setDescription("Please enable tickets in the configuration to use this command.")
Skyler Grey75ea9172022-08-06 10:22:23 +010029 .setFooter({
PineaFan0d06edc2023-01-17 22:10:31 +000030 text: (interaction.member!.permissions as Discord.PermissionsBitField).has("ManageGuild")
Skyler Grey75ea9172022-08-06 10:22:23 +010031 ? "You can enable it by running /settings tickets"
32 : ""
33 })
34 .setStatus("Danger")
35 .setEmoji("CONTROL.BLOCKCROSS")
36 ],
37 ephemeral: true
38 });
pineafan813bdf42022-07-24 10:39:10 +010039 }
pineafan813bdf42022-07-24 10:39:10 +010040 let count = 0;
Skyler Greyda16adf2023-03-05 10:22:12 +000041 const targetChannel: Discord.CategoryChannel | Discord.TextChannel = (await interaction.guild.channels.fetch(
42 config.tickets.category
43 ))! as Discord.CategoryChannel | Discord.TextChannel;
PineaFan0d06edc2023-01-17 22:10:31 +000044 if (targetChannel.type === Discord.ChannelType.GuildCategory) {
45 // For channels, the topic is the user ID, then the word Active
46 const category = targetChannel as Discord.CategoryChannel;
47 category.children.cache.forEach((element) => {
48 if (!(element.type === Discord.ChannelType.GuildText)) return;
49 if (((element as Discord.TextChannel).topic ?? "").includes(`${interaction.member!.user.id}`)) {
Skyler Greyda16adf2023-03-05 10:22:12 +000050 if (((element as Discord.TextChannel).topic ?? "").endsWith("Active")) {
51 count++;
52 }
pineafan813bdf42022-07-24 10:39:10 +010053 }
PineaFan0d06edc2023-01-17 22:10:31 +000054 });
55 } else {
56 // For threads, the name is the users name, id, then the word Active
57 const channel = targetChannel as Discord.TextChannel;
58 channel.threads.cache.forEach((element: Discord.ThreadChannel) => {
59 if (element.name.includes(`${interaction.member!.user.id}`)) {
Skyler Greyda16adf2023-03-05 10:22:12 +000060 if (element.name.endsWith("Active")) {
61 count++;
62 }
PineaFan0d06edc2023-01-17 22:10:31 +000063 }
64 });
65 }
pineafan813bdf42022-07-24 10:39:10 +010066 if (count >= config.tickets.maxTickets) {
Skyler Grey75ea9172022-08-06 10:22:23 +010067 return await interaction.reply({
68 embeds: [
69 new EmojiEmbed()
70 .setTitle("Create Ticket")
71 .setDescription(
72 `You have reached the maximum amount of tickets (${config.tickets.maxTickets}). Please close one of your active tickets before creating a new one.`
73 )
74 .setStatus("Danger")
75 .setEmoji("CONTROL.BLOCKCROSS")
76 ],
77 ephemeral: true
78 });
pineafan813bdf42022-07-24 10:39:10 +010079 }
PineaFan0d06edc2023-01-17 22:10:31 +000080 let ticketTypes: string[];
pineafan63fc5e22022-08-04 22:04:10 +010081 let custom = false;
Skyler Grey75ea9172022-08-06 10:22:23 +010082 if (config.tickets.customTypes && config.tickets.useCustom) {
83 ticketTypes = config.tickets.customTypes;
84 custom = true;
Skyler Grey11236ba2022-08-08 21:13:33 +010085 } else if (config.tickets.types) ticketTypes = toHexArray(config.tickets.types, tickets);
pineafan813bdf42022-07-24 10:39:10 +010086 else ticketTypes = [];
PineaFan0d06edc2023-01-17 22:10:31 +000087 let chosenType: string | null;
88 let splitFormattedTicketTypes: ActionRowBuilder<ButtonBuilder>[] = [];
pineafan813bdf42022-07-24 10:39:10 +010089 if (ticketTypes.length > 0) {
90 let formattedTicketTypes = [];
Skyler Grey75ea9172022-08-06 10:22:23 +010091 formattedTicketTypes = ticketTypes.map((type) => {
pineafan813bdf42022-07-24 10:39:10 +010092 if (custom) {
TheCodedProf21c08592022-09-13 14:14:43 -040093 return new ButtonBuilder().setLabel(type).setStyle(ButtonStyle.Primary).setCustomId(type);
pineafan813bdf42022-07-24 10:39:10 +010094 } else {
TheCodedProf21c08592022-09-13 14:14:43 -040095 return new ButtonBuilder()
pineafan813bdf42022-07-24 10:39:10 +010096 .setLabel(capitalize(type))
TheCodedProf21c08592022-09-13 14:14:43 -040097 .setStyle(ButtonStyle.Primary)
pineafan813bdf42022-07-24 10:39:10 +010098 .setCustomId(type)
Skyler Grey11236ba2022-08-08 21:13:33 +010099 .setEmoji(getEmojiByName("TICKETS." + type.toString().toUpperCase(), "id"));
pineafan813bdf42022-07-24 10:39:10 +0100100 }
101 });
102 for (let i = 0; i < formattedTicketTypes.length; i += 5) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000103 splitFormattedTicketTypes.push(
104 new ActionRowBuilder<ButtonBuilder>().addComponents(formattedTicketTypes.slice(i, i + 5))
105 );
pineafan813bdf42022-07-24 10:39:10 +0100106 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100107 const m = await interaction.reply({
108 embeds: [
109 new EmojiEmbed()
110 .setTitle("Create Ticket")
111 .setDescription("Select a ticket type")
112 .setStatus("Success")
113 .setEmoji("GUILD.TICKET.OPEN")
114 ],
115 ephemeral: true,
116 fetchReply: true,
117 components: splitFormattedTicketTypes
118 });
pineafan813bdf42022-07-24 10:39:10 +0100119 let component;
120 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000121 component = await m.awaitMessageComponent({
122 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000123 filter: (i) => {
124 return (
125 i.user.id === interaction.user.id &&
126 i.channel!.id === interaction.channel!.id &&
127 i.message.id === m.id
128 );
129 }
PineaFan0d06edc2023-01-17 22:10:31 +0000130 });
pineafan813bdf42022-07-24 10:39:10 +0100131 } catch (e) {
132 return;
133 }
134 chosenType = component.customId;
135 splitFormattedTicketTypes = [];
136 formattedTicketTypes = [];
Skyler Grey75ea9172022-08-06 10:22:23 +0100137 formattedTicketTypes = ticketTypes.map((type) => {
pineafan813bdf42022-07-24 10:39:10 +0100138 if (custom) {
TheCodedProf21c08592022-09-13 14:14:43 -0400139 return new ButtonBuilder()
pineafan813bdf42022-07-24 10:39:10 +0100140 .setLabel(type)
TheCodedProf21c08592022-09-13 14:14:43 -0400141 .setStyle(chosenType === type ? ButtonStyle.Success : ButtonStyle.Secondary)
pineafan813bdf42022-07-24 10:39:10 +0100142 .setCustomId(type)
pineafan63fc5e22022-08-04 22:04:10 +0100143 .setDisabled(true);
144 } else {
TheCodedProf21c08592022-09-13 14:14:43 -0400145 return new ButtonBuilder()
pineafan813bdf42022-07-24 10:39:10 +0100146 .setLabel(capitalize(type))
TheCodedProf21c08592022-09-13 14:14:43 -0400147 .setStyle(chosenType === type ? ButtonStyle.Success : ButtonStyle.Secondary)
pineafan813bdf42022-07-24 10:39:10 +0100148 .setCustomId(type)
Skyler Grey11236ba2022-08-08 21:13:33 +0100149 .setEmoji(getEmojiByName("TICKETS." + type.toString().toUpperCase(), "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100150 .setDisabled(true);
pineafan813bdf42022-07-24 10:39:10 +0100151 }
152 });
153 for (let i = 0; i < formattedTicketTypes.length; i += 5) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000154 splitFormattedTicketTypes.push(
155 new ActionRowBuilder<ButtonBuilder>().addComponents(formattedTicketTypes.slice(i, i + 5))
156 );
pineafan813bdf42022-07-24 10:39:10 +0100157 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000158 await component.update({
Skyler Grey75ea9172022-08-06 10:22:23 +0100159 embeds: [
160 new EmojiEmbed()
161 .setTitle("Create Ticket")
162 .setDescription("Select a ticket type")
163 .setStatus("Success")
164 .setEmoji("GUILD.TICKET.OPEN")
165 ],
166 components: splitFormattedTicketTypes
167 });
pineafan813bdf42022-07-24 10:39:10 +0100168 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100169 chosenType = null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100170 await interaction.reply({
Skyler Grey11236ba2022-08-08 21:13:33 +0100171 embeds: [new EmojiEmbed().setTitle("Create Ticket").setEmoji("GUILD.TICKET.OPEN")],
Skyler Grey75ea9172022-08-06 10:22:23 +0100172 ephemeral: true,
173 components: splitFormattedTicketTypes
174 });
pineafan813bdf42022-07-24 10:39:10 +0100175 }
PineaFan0d06edc2023-01-17 22:10:31 +0000176 let c: Discord.TextChannel | Discord.PrivateThreadChannel;
177 if (targetChannel.type === Discord.ChannelType.GuildCategory) {
178 const overwrites = [
179 {
180 id: interaction.member,
181 allow: ["ViewChannel", "SendMessages", "AttachFiles", "AddReactions", "ReadMessageHistory"],
182 type: Discord.OverwriteType.Member
183 }
184 ] as Discord.OverwriteResolvable[];
pineafan813bdf42022-07-24 10:39:10 +0100185 overwrites.push({
PineaFan0d06edc2023-01-17 22:10:31 +0000186 id: interaction.guild.roles.everyone,
187 deny: ["ViewChannel"],
188 type: Discord.OverwriteType.Role
pineafan63fc5e22022-08-04 22:04:10 +0100189 });
PineaFan0d06edc2023-01-17 22:10:31 +0000190 if (config.tickets.supportRole !== null) {
191 overwrites.push({
192 id: interaction.guild.roles.cache.get(config.tickets.supportRole)!,
193 allow: ["ViewChannel", "SendMessages", "AttachFiles", "AddReactions", "ReadMessageHistory"],
194 type: Discord.OverwriteType.Role
195 });
196 }
pineafan813bdf42022-07-24 10:39:10 +0100197
PineaFan0d06edc2023-01-17 22:10:31 +0000198 try {
199 c = await interaction.guild.channels.create({
200 name: `${interaction.member!.user.username.toLowerCase()}`,
201 type: Discord.ChannelType.GuildText,
202 topic: `${interaction.member!.user.id} Active`,
203 parent: config.tickets.category,
204 nsfw: false,
205 permissionOverwrites: overwrites as Discord.OverwriteResolvable[],
206 reason: "Creating ticket"
207 });
208 } catch (e) {
209 return await interaction.editReply({
210 embeds: [
211 new EmojiEmbed()
212 .setTitle("Create Ticket")
213 .setDescription("Failed to create ticket")
214 .setStatus("Danger")
215 .setEmoji("CONTROL.BLOCKCROSS")
216 ]
217 });
218 }
219 try {
220 await c.send({
221 content:
222 `<@${interaction.member!.user.id}>` +
223 (config.tickets.supportRole !== null ? ` • <@&${config.tickets.supportRole}>` : ""),
224 allowedMentions: {
225 users: [(interaction.member as Discord.GuildMember).id],
226 roles: config.tickets.supportRole !== null ? [config.tickets.supportRole] : []
227 }
228 });
229 let content: string | null = null;
230 if (interaction.isCommand()) {
231 content = interaction.options.get("message")?.value as string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100232 }
PineaFan0d06edc2023-01-17 22:10:31 +0000233 if (content) content = `**Message:**\n> ${content}\n`;
234 let emoji;
235 if (chosenType) {
236 emoji = custom ? "" : getEmojiByName("TICKETS." + chosenType.toUpperCase());
237 } else {
238 emoji = "";
pineafan813bdf42022-07-24 10:39:10 +0100239 }
PineaFan0d06edc2023-01-17 22:10:31 +0000240 await c.send({
241 embeds: [
242 new EmojiEmbed()
243 .setTitle("New Ticket")
244 .setDescription(
245 `Ticket created by <@${interaction.member!.user.id}>\n` +
246 `**Support type:** ${
247 chosenType !== null ? emoji + " " + capitalize(chosenType) : "General"
248 }\n` +
249 `**Ticket ID:** \`${c.id}\`\n${content ?? ""}\n` +
TheCodedProff86ba092023-01-27 17:10:07 -0500250 `Type ${getCommandMentionByName("ticket/close")} to close this ticket.`
PineaFan0d06edc2023-01-17 22:10:31 +0000251 )
252 .setStatus("Success")
253 .setEmoji("GUILD.TICKET.OPEN")
254 ],
255 components: [
256 new ActionRowBuilder<ButtonBuilder>().addComponents([
257 new ButtonBuilder()
258 .setLabel("Close")
259 .setStyle(ButtonStyle.Danger)
260 .setCustomId("closeticket")
261 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
262 ])
263 ]
264 });
265 } catch (e) {
266 return await interaction.editReply({
267 embeds: [
268 new EmojiEmbed()
269 .setTitle("Create Ticket")
270 .setDescription("Failed to create ticket")
271 .setStatus("Danger")
272 .setEmoji("GUILD.TICKET.CLOSE")
273 ]
274 });
275 }
276 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000277 c = (await targetChannel.threads.create({
278 name: `${interaction.member!.user.username} - ${interaction.member!.user.id} - Active`,
279 autoArchiveDuration: 60 * 24 * 7,
280 type: Discord.ChannelType.PrivateThread,
281 reason: "Creating ticket"
282 })) as Discord.PrivateThreadChannel;
Skyler Greyf4f21c42023-03-08 14:36:29 +0000283 await c.members.add(interaction.member!.user.id);
PineaFan0d06edc2023-01-17 22:10:31 +0000284 try {
285 await c.send({
286 content:
287 `<@${interaction.member!.user.id}>` +
288 (config.tickets.supportRole !== null ? ` • <@&${config.tickets.supportRole}>` : ""),
289 allowedMentions: {
290 users: [(interaction.member as Discord.GuildMember).id],
291 roles: config.tickets.supportRole !== null ? [config.tickets.supportRole] : []
292 }
293 });
294 let content: string | null = null;
295 if (interaction.isCommand()) {
296 content = interaction.options.get("message")?.value as string;
297 }
298 if (content) content = `**Message:**\n> ${content}\n`;
299 let emoji;
300 if (chosenType) {
301 emoji = custom ? "" : getEmojiByName("TICKETS." + chosenType.toUpperCase());
302 } else {
303 emoji = "";
304 }
305 await c.send({
306 embeds: [
307 new EmojiEmbed()
308 .setTitle("New Ticket")
309 .setDescription(
310 `Ticket created by <@${interaction.member!.user.id}>\n` +
311 `**Support type:** ${
312 chosenType !== null ? emoji + " " + capitalize(chosenType) : "General"
313 }\n` +
314 `**Ticket ID:** \`${c.id}\`\n${content ?? ""}\n` +
TheCodedProff86ba092023-01-27 17:10:07 -0500315 `Type ${getCommandMentionByName("ticket/close")} to close this ticket.`
PineaFan0d06edc2023-01-17 22:10:31 +0000316 )
317 .setStatus("Success")
318 .setEmoji("GUILD.TICKET.OPEN")
319 ],
320 components: [
321 new ActionRowBuilder<ButtonBuilder>().addComponents([
322 new ButtonBuilder()
323 .setLabel("Close")
324 .setStyle(ButtonStyle.Danger)
325 .setCustomId("closeticket")
326 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
327 ])
328 ]
329 });
330 } catch (e) {
331 return await interaction.editReply({
332 embeds: [
333 new EmojiEmbed()
334 .setTitle("Create Ticket")
335 .setDescription("Failed to create ticket")
336 .setStatus("Danger")
337 .setEmoji("GUILD.TICKET.CLOSE")
338 ]
339 });
340 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100341 }
PineaFan0d06edc2023-01-17 22:10:31 +0000342 const data = {
343 meta: {
344 type: "ticketCreate",
345 displayName: "Ticket Created",
346 calculateType: "ticketUpdate",
347 color: NucleusColors.green,
348 emoji: "GUILD.TICKET.OPEN",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500349 timestamp: Date.now()
PineaFan0d06edc2023-01-17 22:10:31 +0000350 },
351 list: {
352 ticketFor: entry(interaction.member!.user.id, renderUser(interaction.member!.user! as Discord.User)),
TheCodedProf6ec331b2023-02-20 12:13:06 -0500353 created: entry(Date.now(), renderDelta(Date.now())),
PineaFan0d06edc2023-01-17 22:10:31 +0000354 ticketChannel: entry(c.id, renderChannel(c))
355 },
356 hidden: {
357 guild: interaction.guild.id
358 }
359 };
Skyler Greyf4f21c42023-03-08 14:36:29 +0000360 await log(data);
Skyler Grey75ea9172022-08-06 10:22:23 +0100361 await interaction.editReply({
362 embeds: [
363 new EmojiEmbed()
364 .setTitle("Create Ticket")
Skyler Grey11236ba2022-08-08 21:13:33 +0100365 .setDescription(`Ticket created. You can view it here: <#${c.id}>`)
Skyler Grey75ea9172022-08-06 10:22:23 +0100366 .setStatus("Success")
367 .setEmoji("GUILD.TICKET.OPEN")
368 ],
369 components: splitFormattedTicketTypes
370 });
371}