blob: 20b790af1ca7678f2d562ea405070ce864544961 [file] [log] [blame]
Skyler Greyf21323a2022-08-13 23:58:22 +01001import {
2 CommandInteraction,
3 GuildMember,
4 Message,
TheCodedProf21c08592022-09-13 14:14:43 -04005 ActionRowBuilder,
6 ButtonBuilder,
Skyler Greyf21323a2022-08-13 23:58:22 +01007 MessageComponentInteraction,
TheCodedProf21c08592022-09-13 14:14:43 -04008 TextChannel,
PineaFan538d3752023-01-12 21:48:23 +00009 ButtonStyle,
TheCodedProf2e54a772023-02-14 16:26:47 -050010 User,
TheCodedProfcfe8e9a2023-02-26 17:28:09 -050011 ComponentType,
12 ThreadChannel
Skyler Greyf21323a2022-08-13 23:58:22 +010013} from "discord.js";
pineafan813bdf42022-07-24 10:39:10 +010014import EmojiEmbed from "../utils/generateEmojiEmbed.js";
15import getEmojiByName from "../utils/getEmojiByName.js";
16import { PasteClient, Publicity, ExpireDate } from "pastebin-api";
pineafan813bdf42022-07-24 10:39:10 +010017import client from "../utils/client.js";
TheCodedProf94ff6de2023-02-22 17:47:26 -050018import { messageException } from '../utils/createTemporaryStorage.js';
pineafan813bdf42022-07-24 10:39:10 +010019
PineaFan752af462022-12-31 21:59:38 +000020const pbClient = new PasteClient(client.config.pastebinApiKey);
pineafan813bdf42022-07-24 10:39:10 +010021
TheCodedProf2e54a772023-02-14 16:26:47 -050022interface TranscriptEmbed {
23 title?: string;
24 description?: string;
25 fields?: {
26 name: string;
27 value: string;
28 inline: boolean;
29 }[];
30 footer?: {
31 text: string;
32 iconURL?: string;
33 };
34}
35
36interface TranscriptComponent {
37 type: number;
38 style?: ButtonStyle;
39 label?: string;
40 description?: string;
41 placeholder?: string;
42 emojiURL?: string;
43}
44
45interface TranscriptAuthor {
46 username: string;
47 discriminator: number;
48 nickname?: string;
49 id: string;
50 iconURL?: string;
51 topRole: {
52 color: number;
53 badgeURL?: string;
54 }
55}
56
TheCodedProf1f675042023-02-16 17:01:29 -050057interface TranscriptAttachment {
58 url: string;
59 filename: string;
60 size: number;
61 log?: string;
62}
63
TheCodedProf2e54a772023-02-14 16:26:47 -050064interface TranscriptMessage {
65 id: string;
66 author: TranscriptAuthor;
67 content?: string;
68 embeds?: TranscriptEmbed[];
69 components?: TranscriptComponent[][];
70 editedTimestamp?: number;
71 createdTimestamp: number;
72 flags?: string[];
TheCodedProf1f675042023-02-16 17:01:29 -050073 attachments?: TranscriptAttachment[];
TheCodedProf2e54a772023-02-14 16:26:47 -050074 stickerURLs?: string[];
75 referencedMessage?: string | [string, string, string];
76}
77
78interface Transcript {
79 type: "ticket" | "purge"
80 guild: string;
81 channel: string;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -050082 for: TranscriptAuthor
TheCodedProf2e54a772023-02-14 16:26:47 -050083 messages: TranscriptMessage[];
84 createdTimestamp: number;
85 createdBy: TranscriptAuthor;
86}
87
TheCodedProfcfe8e9a2023-02-26 17:28:09 -050088const noTopic = new EmojiEmbed()
89 .setTitle("User not found")
90 .setDescription("There is no user associated with this ticket.")
91 .setStatus("Danger")
92 .setEmoji("CONTROL.BLOCKCROSS")
93
pineafan0f5cc782022-08-12 21:55:42 +010094export default async function (interaction: CommandInteraction | MessageComponentInteraction) {
Skyler Grey11236ba2022-08-08 21:13:33 +010095 if (interaction.channel === null) return;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -050096 if (!(interaction.channel instanceof TextChannel || interaction.channel instanceof ThreadChannel)) return;
Skyler Grey11236ba2022-08-08 21:13:33 +010097 const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
pineafan813bdf42022-07-24 10:39:10 +010098
Skyler Grey11236ba2022-08-08 21:13:33 +010099 let messages: Message[] = [];
100 let deletedCount: number;
pineafan813bdf42022-07-24 10:39:10 +0100101
Skyler Grey11236ba2022-08-08 21:13:33 +0100102 do {
103 const fetched = await (interaction.channel as TextChannel).messages.fetch({ limit: 100 });
104 const deleted = await (interaction.channel as TextChannel).bulkDelete(fetched, true);
105 deletedCount = deleted.size;
TheCodedProfbc94a5c2023-01-18 18:59:31 -0500106 messages = messages.concat(Array.from(deleted.values() as Iterable<Message>));
TheCodedProf94ff6de2023-02-22 17:47:26 -0500107 if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id)
Skyler Grey11236ba2022-08-08 21:13:33 +0100108 } while (deletedCount === 100);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500109 messages = messages.filter(message => !(
110 message.components.some(
111 component => component.components.some(
112 child => child.customId?.includes("transcript") ?? false
113 )
114 )
115 ));
Skyler Grey11236ba2022-08-08 21:13:33 +0100116
pineafan63fc5e22022-08-04 22:04:10 +0100117 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +0100118 messages.reverse().forEach((message) => {
pineafan813bdf42022-07-24 10:39:10 +0100119 if (!message.author.bot) {
pineafan63fc5e22022-08-04 22:04:10 +0100120 const sentDate = new Date(message.createdTimestamp);
Skyler Grey11236ba2022-08-08 21:13:33 +0100121 out += `${message.author.username}#${message.author.discriminator} (${
122 message.author.id
123 }) [${sentDate.toUTCString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100124 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100125 lines.forEach((line) => {
126 out += `> ${line}\n`;
127 });
pineafan63fc5e22022-08-04 22:04:10 +0100128 out += "\n\n";
pineafan813bdf42022-07-24 10:39:10 +0100129 }
pineafan63fc5e22022-08-04 22:04:10 +0100130 });
TheCodedProf2e54a772023-02-14 16:26:47 -0500131
TheCodedProf1807fb32023-02-20 14:33:48 -0500132 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
TheCodedProf2e54a772023-02-14 16:26:47 -0500133
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500134 let topic
135 let member: GuildMember | null = null;
136 if (interaction.channel instanceof TextChannel) {
137 topic = interaction.channel.topic;
138 if (topic === null) return await interaction.reply({ embeds: [noTopic] });
139 member = interaction.guild!.members.cache.get(topic.split(" ")[1]!) ?? null;
140 } else {
141 topic = interaction.channel.name;
142 const split = topic.split("-").map(p => p.trim()) as [string, string, string];
143 member = interaction.guild!.members.cache.get(split[1]) ?? null;
144 if (member === null) return await interaction.reply({ embeds: [noTopic] });
145 }
146
147
TheCodedProf1807fb32023-02-20 14:33:48 -0500148 const newOut: Transcript = {
TheCodedProf2e54a772023-02-14 16:26:47 -0500149 type: "ticket",
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500150 for: {
151 username: member!.user.username,
152 discriminator: parseInt(member!.user.discriminator),
153 id: member!.user.id,
154 topRole: {
155 color: member!.roles.highest.color
156 }
157 },
TheCodedProf2e54a772023-02-14 16:26:47 -0500158 guild: interaction.guild!.id,
159 channel: interaction.channel!.id,
160 messages: [],
161 createdTimestamp: Date.now(),
162 createdBy: {
163 username: interaction.user.username,
164 discriminator: parseInt(interaction.user.discriminator),
165 id: interaction.user.id,
166 topRole: {
167 color: interactionMember?.roles.highest.color ?? 0x000000
168 }
169 }
170 }
171 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
172 messages.reverse().forEach((message) => {
TheCodedProf1807fb32023-02-20 14:33:48 -0500173 const msg: TranscriptMessage = {
TheCodedProf2e54a772023-02-14 16:26:47 -0500174 id: message.id,
175 author: {
176 username: message.author.username,
177 discriminator: parseInt(message.author.discriminator),
178 id: message.author.id,
179 topRole: {
180 color: message.member!.roles.highest.color
181 }
182 },
183 createdTimestamp: message.createdTimestamp
184 };
185 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
186 if (message.content) msg.content = message.content;
187 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
TheCodedProf1807fb32023-02-20 14:33:48 -0500188 const obj: TranscriptEmbed = {};
TheCodedProf2e54a772023-02-14 16:26:47 -0500189 if (embed.title) obj.title = embed.title;
190 if (embed.description) obj.description = embed.description;
191 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
192 return {
193 name: field.name,
194 value: field.value,
195 inline: field.inline ?? false
196 }
197 });
198 if (embed.footer) obj.footer = {
199 text: embed.footer.text,
200 };
201 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
202 return obj;
203 });
204 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
TheCodedProf1807fb32023-02-20 14:33:48 -0500205 const obj: TranscriptComponent = {
TheCodedProf2e54a772023-02-14 16:26:47 -0500206 type: child.type
207 }
208 if (child.type === ComponentType.Button) {
209 obj.style = child.style;
210 obj.label = child.label ?? "";
TheCodedProf362c51d2023-02-14 16:31:33 -0500211 } else if (child.type > 2) {
212 obj.placeholder = child.placeholder ?? "";
TheCodedProf2e54a772023-02-14 16:26:47 -0500213 }
214 return obj
215 }));
216 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
TheCodedProf1807fb32023-02-20 14:33:48 -0500217 msg.flags = message.flags.toArray();
TheCodedProf2e54a772023-02-14 16:26:47 -0500218
219 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
220 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
TheCodedProfaa3fe992023-02-25 21:53:09 -0500221 newOut.messages.push(msg);
TheCodedProf2e54a772023-02-14 16:26:47 -0500222 });
223
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500224 const code = await client.database.transcripts.create(newOut);
225 if(!code) return await interaction.reply({embeds: [new EmojiEmbed().setTitle("Error").setDescription("An error occurred while creating the transcript.").setStatus("Danger").setEmoji("CONTROL.BLOCKCROSS")]})
pineafan3a02ea32022-08-11 21:35:04 +0100226 let m: Message;
pineafan813bdf42022-07-24 10:39:10 +0100227 if (out !== "") {
228 const url = await pbClient.createPaste({
229 code: out,
230 expireDate: ExpireDate.Never,
pineafan3a02ea32022-08-11 21:35:04 +0100231 name:
232 `Ticket Transcript ${
233 member ? "for " + member.user.username + "#" + member.user.discriminator + " " : ""
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500234 }` + `(Created at ${new Date(interaction.channel.createdTimestamp!).toDateString()})`,
pineafan63fc5e22022-08-04 22:04:10 +0100235 publicity: Publicity.Unlisted
236 });
pineafan4e425942022-08-08 22:01:47 +0100237 const guildConfig = await client.database.guilds.read(interaction.guild!.id);
pineafan3a02ea32022-08-11 21:35:04 +0100238 m = (await interaction.reply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 embeds: [
240 new EmojiEmbed()
241 .setTitle("Transcript")
242 .setDescription(
243 "You can view the transcript using the link below. You can save the link for later" +
244 (guildConfig.logging.logs.channel
245 ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.`
246 : ".")
247 )
248 .setStatus("Success")
249 .setEmoji("CONTROL.DOWNLOAD")
250 ],
251 components: [
PineaFan538d3752023-01-12 21:48:23 +0000252 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400253 new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(url),
254 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100255 .setLabel("Delete")
TheCodedProf21c08592022-09-13 14:14:43 -0400256 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100257 .setCustomId("close")
258 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
259 ])
260 ],
261 fetchReply: true
pineafan3a02ea32022-08-11 21:35:04 +0100262 })) as Message;
pineafan813bdf42022-07-24 10:39:10 +0100263 } else {
pineafan3a02ea32022-08-11 21:35:04 +0100264 m = (await interaction.reply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 embeds: [
266 new EmojiEmbed()
267 .setTitle("Transcript")
268 .setDescription(
269 "The transcript was empty, so no changes were made. To delete this ticket, press the delete button below."
270 )
271 .setStatus("Success")
272 .setEmoji("CONTROL.DOWNLOAD")
273 ],
274 components: [
PineaFan538d3752023-01-12 21:48:23 +0000275 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400276 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100277 .setLabel("Delete")
TheCodedProf21c08592022-09-13 14:14:43 -0400278 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100279 .setCustomId("close")
280 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
281 ])
282 ],
283 fetchReply: true
pineafan3a02ea32022-08-11 21:35:04 +0100284 })) as Message;
pineafan813bdf42022-07-24 10:39:10 +0100285 }
286 let i;
287 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000288 i = await m.awaitMessageComponent({
289 time: 300000,
TheCodedProf267563a2023-01-21 17:00:57 -0500290 filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id }
PineaFan0d06edc2023-01-17 22:10:31 +0000291 });
TheCodedProf267563a2023-01-21 17:00:57 -0500292 await i.deferUpdate();
Skyler Grey75ea9172022-08-06 10:22:23 +0100293 } catch {
294 return;
295 }
pineafan63fc5e22022-08-04 22:04:10 +0100296 const data = {
Skyler Grey75ea9172022-08-06 10:22:23 +0100297 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100298 type: "ticketDeleted",
299 displayName: "Ticket Deleted",
pineafan813bdf42022-07-24 10:39:10 +0100300 calculateType: "ticketUpdate",
301 color: NucleusColors.red,
pineafan63fc5e22022-08-04 22:04:10 +0100302 emoji: "GUILD.TICKET.CLOSE",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500303 timestamp: Date.now()
pineafan813bdf42022-07-24 10:39:10 +0100304 },
305 list: {
pineafan3a02ea32022-08-11 21:35:04 +0100306 ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"),
PineaFan538d3752023-01-12 21:48:23 +0000307 deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)),
TheCodedProf6ec331b2023-02-20 12:13:06 -0500308 deleted: entry(Date.now().toString(), renderDelta(Date.now()))
pineafan813bdf42022-07-24 10:39:10 +0100309 },
310 hidden: {
pineafan4e425942022-08-08 22:01:47 +0100311 guild: interaction.guild!.id
pineafan813bdf42022-07-24 10:39:10 +0100312 }
pineafan63fc5e22022-08-04 22:04:10 +0100313 };
pineafan813bdf42022-07-24 10:39:10 +0100314 log(data);
pineafan63fc5e22022-08-04 22:04:10 +0100315 await interaction.channel.delete();
316 return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100317}