blob: 865089b72e9cb20af1f4a82bbf1dea57ae0d9d14 [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,
TheCodedProf1f675042023-02-16 17:01:29 -050011 ComponentType
Skyler Greyf21323a2022-08-13 23:58:22 +010012} from "discord.js";
pineafan813bdf42022-07-24 10:39:10 +010013import EmojiEmbed from "../utils/generateEmojiEmbed.js";
14import getEmojiByName from "../utils/getEmojiByName.js";
15import { PasteClient, Publicity, ExpireDate } from "pastebin-api";
pineafan813bdf42022-07-24 10:39:10 +010016import client from "../utils/client.js";
17
PineaFan752af462022-12-31 21:59:38 +000018const pbClient = new PasteClient(client.config.pastebinApiKey);
pineafan813bdf42022-07-24 10:39:10 +010019
TheCodedProf2e54a772023-02-14 16:26:47 -050020interface TranscriptEmbed {
21 title?: string;
22 description?: string;
23 fields?: {
24 name: string;
25 value: string;
26 inline: boolean;
27 }[];
28 footer?: {
29 text: string;
30 iconURL?: string;
31 };
32}
33
34interface TranscriptComponent {
35 type: number;
36 style?: ButtonStyle;
37 label?: string;
38 description?: string;
39 placeholder?: string;
40 emojiURL?: string;
41}
42
43interface TranscriptAuthor {
44 username: string;
45 discriminator: number;
46 nickname?: string;
47 id: string;
48 iconURL?: string;
49 topRole: {
50 color: number;
51 badgeURL?: string;
52 }
53}
54
TheCodedProf1f675042023-02-16 17:01:29 -050055interface TranscriptAttachment {
56 url: string;
57 filename: string;
58 size: number;
59 log?: string;
60}
61
TheCodedProf2e54a772023-02-14 16:26:47 -050062interface TranscriptMessage {
63 id: string;
64 author: TranscriptAuthor;
65 content?: string;
66 embeds?: TranscriptEmbed[];
67 components?: TranscriptComponent[][];
68 editedTimestamp?: number;
69 createdTimestamp: number;
70 flags?: string[];
TheCodedProf1f675042023-02-16 17:01:29 -050071 attachments?: TranscriptAttachment[];
TheCodedProf2e54a772023-02-14 16:26:47 -050072 stickerURLs?: string[];
73 referencedMessage?: string | [string, string, string];
74}
75
76interface Transcript {
77 type: "ticket" | "purge"
78 guild: string;
79 channel: string;
80 messages: TranscriptMessage[];
81 createdTimestamp: number;
82 createdBy: TranscriptAuthor;
83}
84
pineafan0f5cc782022-08-12 21:55:42 +010085export default async function (interaction: CommandInteraction | MessageComponentInteraction) {
Skyler Grey11236ba2022-08-08 21:13:33 +010086 if (interaction.channel === null) return;
pineafan4e425942022-08-08 22:01:47 +010087 if (!(interaction.channel instanceof TextChannel)) return;
Skyler Grey11236ba2022-08-08 21:13:33 +010088 const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
pineafan813bdf42022-07-24 10:39:10 +010089
Skyler Grey11236ba2022-08-08 21:13:33 +010090 let messages: Message[] = [];
91 let deletedCount: number;
pineafan813bdf42022-07-24 10:39:10 +010092
Skyler Grey11236ba2022-08-08 21:13:33 +010093 do {
94 const fetched = await (interaction.channel as TextChannel).messages.fetch({ limit: 100 });
95 const deleted = await (interaction.channel as TextChannel).bulkDelete(fetched, true);
96 deletedCount = deleted.size;
TheCodedProfbc94a5c2023-01-18 18:59:31 -050097 messages = messages.concat(Array.from(deleted.values() as Iterable<Message>));
Skyler Grey11236ba2022-08-08 21:13:33 +010098 } while (deletedCount === 100);
99
pineafan63fc5e22022-08-04 22:04:10 +0100100 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +0100101 messages.reverse().forEach((message) => {
pineafan813bdf42022-07-24 10:39:10 +0100102 if (!message.author.bot) {
pineafan63fc5e22022-08-04 22:04:10 +0100103 const sentDate = new Date(message.createdTimestamp);
Skyler Grey11236ba2022-08-08 21:13:33 +0100104 out += `${message.author.username}#${message.author.discriminator} (${
105 message.author.id
106 }) [${sentDate.toUTCString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100107 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100108 lines.forEach((line) => {
109 out += `> ${line}\n`;
110 });
pineafan63fc5e22022-08-04 22:04:10 +0100111 out += "\n\n";
pineafan813bdf42022-07-24 10:39:10 +0100112 }
pineafan63fc5e22022-08-04 22:04:10 +0100113 });
TheCodedProf2e54a772023-02-14 16:26:47 -0500114
115 let interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
116
117 let newOut: Transcript = {
118 type: "ticket",
119 guild: interaction.guild!.id,
120 channel: interaction.channel!.id,
121 messages: [],
122 createdTimestamp: Date.now(),
123 createdBy: {
124 username: interaction.user.username,
125 discriminator: parseInt(interaction.user.discriminator),
126 id: interaction.user.id,
127 topRole: {
128 color: interactionMember?.roles.highest.color ?? 0x000000
129 }
130 }
131 }
132 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
133 messages.reverse().forEach((message) => {
134 let msg: TranscriptMessage = {
135 id: message.id,
136 author: {
137 username: message.author.username,
138 discriminator: parseInt(message.author.discriminator),
139 id: message.author.id,
140 topRole: {
141 color: message.member!.roles.highest.color
142 }
143 },
144 createdTimestamp: message.createdTimestamp
145 };
146 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
147 if (message.content) msg.content = message.content;
148 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
149 let obj: TranscriptEmbed = {};
150 if (embed.title) obj.title = embed.title;
151 if (embed.description) obj.description = embed.description;
152 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
153 return {
154 name: field.name,
155 value: field.value,
156 inline: field.inline ?? false
157 }
158 });
159 if (embed.footer) obj.footer = {
160 text: embed.footer.text,
161 };
162 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
163 return obj;
164 });
165 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
166 let obj: TranscriptComponent = {
167 type: child.type
168 }
169 if (child.type === ComponentType.Button) {
170 obj.style = child.style;
171 obj.label = child.label ?? "";
TheCodedProf362c51d2023-02-14 16:31:33 -0500172 } else if (child.type > 2) {
173 obj.placeholder = child.placeholder ?? "";
TheCodedProf2e54a772023-02-14 16:26:47 -0500174 }
175 return obj
176 }));
177 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
178 if (message.flags) msg.flags = message.flags.toArray();
179
180 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
181 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
182
183 });
184
pineafan3a02ea32022-08-11 21:35:04 +0100185 const topic = interaction.channel.topic;
186 let member: GuildMember | null = null;
187 if (topic !== null) {
188 const part = topic.split(" ")[0] ?? null;
189 if (part !== null) member = interaction.guild!.members.cache.get(part) ?? null;
190 }
191 let m: Message;
pineafan813bdf42022-07-24 10:39:10 +0100192 if (out !== "") {
193 const url = await pbClient.createPaste({
194 code: out,
195 expireDate: ExpireDate.Never,
pineafan3a02ea32022-08-11 21:35:04 +0100196 name:
197 `Ticket Transcript ${
198 member ? "for " + member.user.username + "#" + member.user.discriminator + " " : ""
199 }` + `(Created at ${new Date(interaction.channel.createdTimestamp).toDateString()})`,
pineafan63fc5e22022-08-04 22:04:10 +0100200 publicity: Publicity.Unlisted
201 });
pineafan4e425942022-08-08 22:01:47 +0100202 const guildConfig = await client.database.guilds.read(interaction.guild!.id);
pineafan3a02ea32022-08-11 21:35:04 +0100203 m = (await interaction.reply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100204 embeds: [
205 new EmojiEmbed()
206 .setTitle("Transcript")
207 .setDescription(
208 "You can view the transcript using the link below. You can save the link for later" +
209 (guildConfig.logging.logs.channel
210 ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.`
211 : ".")
212 )
213 .setStatus("Success")
214 .setEmoji("CONTROL.DOWNLOAD")
215 ],
216 components: [
PineaFan538d3752023-01-12 21:48:23 +0000217 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400218 new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(url),
219 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100220 .setLabel("Delete")
TheCodedProf21c08592022-09-13 14:14:43 -0400221 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100222 .setCustomId("close")
223 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
224 ])
225 ],
226 fetchReply: true
pineafan3a02ea32022-08-11 21:35:04 +0100227 })) as Message;
pineafan813bdf42022-07-24 10:39:10 +0100228 } else {
pineafan3a02ea32022-08-11 21:35:04 +0100229 m = (await interaction.reply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100230 embeds: [
231 new EmojiEmbed()
232 .setTitle("Transcript")
233 .setDescription(
234 "The transcript was empty, so no changes were made. To delete this ticket, press the delete button below."
235 )
236 .setStatus("Success")
237 .setEmoji("CONTROL.DOWNLOAD")
238 ],
239 components: [
PineaFan538d3752023-01-12 21:48:23 +0000240 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400241 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100242 .setLabel("Delete")
TheCodedProf21c08592022-09-13 14:14:43 -0400243 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100244 .setCustomId("close")
245 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
246 ])
247 ],
248 fetchReply: true
pineafan3a02ea32022-08-11 21:35:04 +0100249 })) as Message;
pineafan813bdf42022-07-24 10:39:10 +0100250 }
251 let i;
252 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000253 i = await m.awaitMessageComponent({
254 time: 300000,
TheCodedProf267563a2023-01-21 17:00:57 -0500255 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 +0000256 });
TheCodedProf267563a2023-01-21 17:00:57 -0500257 await i.deferUpdate();
Skyler Grey75ea9172022-08-06 10:22:23 +0100258 } catch {
259 return;
260 }
pineafan63fc5e22022-08-04 22:04:10 +0100261 const data = {
Skyler Grey75ea9172022-08-06 10:22:23 +0100262 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100263 type: "ticketDeleted",
264 displayName: "Ticket Deleted",
pineafan813bdf42022-07-24 10:39:10 +0100265 calculateType: "ticketUpdate",
266 color: NucleusColors.red,
pineafan63fc5e22022-08-04 22:04:10 +0100267 emoji: "GUILD.TICKET.CLOSE",
pineafan813bdf42022-07-24 10:39:10 +0100268 timestamp: new Date().getTime()
269 },
270 list: {
pineafan3a02ea32022-08-11 21:35:04 +0100271 ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"),
PineaFan538d3752023-01-12 21:48:23 +0000272 deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)),
273 deleted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime()))
pineafan813bdf42022-07-24 10:39:10 +0100274 },
275 hidden: {
pineafan4e425942022-08-08 22:01:47 +0100276 guild: interaction.guild!.id
pineafan813bdf42022-07-24 10:39:10 +0100277 }
pineafan63fc5e22022-08-04 22:04:10 +0100278 };
pineafan813bdf42022-07-24 10:39:10 +0100279 log(data);
pineafan63fc5e22022-08-04 22:04:10 +0100280 await interaction.channel.delete();
281 return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100282}