blob: 54b79ea3042878fcb3fee4c34b61a997f0c56b43 [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,
TheCodedProf362c51d2023-02-14 16:31:33 -050011 ComponentType,
12 APIBaseSelectMenuComponent,
13 SelectMenuComponent
Skyler Greyf21323a2022-08-13 23:58:22 +010014} from "discord.js";
pineafan813bdf42022-07-24 10:39:10 +010015import EmojiEmbed from "../utils/generateEmojiEmbed.js";
16import getEmojiByName from "../utils/getEmojiByName.js";
17import { PasteClient, Publicity, ExpireDate } from "pastebin-api";
pineafan813bdf42022-07-24 10:39:10 +010018import client from "../utils/client.js";
19
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
57interface TranscriptMessage {
58 id: string;
59 author: TranscriptAuthor;
60 content?: string;
61 embeds?: TranscriptEmbed[];
62 components?: TranscriptComponent[][];
63 editedTimestamp?: number;
64 createdTimestamp: number;
65 flags?: string[];
66 attachments?: unknown; //FIXME
67 stickerURLs?: string[];
68 referencedMessage?: string | [string, string, string];
69}
70
71interface Transcript {
72 type: "ticket" | "purge"
73 guild: string;
74 channel: string;
75 messages: TranscriptMessage[];
76 createdTimestamp: number;
77 createdBy: TranscriptAuthor;
78}
79
pineafan0f5cc782022-08-12 21:55:42 +010080export default async function (interaction: CommandInteraction | MessageComponentInteraction) {
Skyler Grey11236ba2022-08-08 21:13:33 +010081 if (interaction.channel === null) return;
pineafan4e425942022-08-08 22:01:47 +010082 if (!(interaction.channel instanceof TextChannel)) return;
Skyler Grey11236ba2022-08-08 21:13:33 +010083 const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
pineafan813bdf42022-07-24 10:39:10 +010084
Skyler Grey11236ba2022-08-08 21:13:33 +010085 let messages: Message[] = [];
86 let deletedCount: number;
pineafan813bdf42022-07-24 10:39:10 +010087
Skyler Grey11236ba2022-08-08 21:13:33 +010088 do {
89 const fetched = await (interaction.channel as TextChannel).messages.fetch({ limit: 100 });
90 const deleted = await (interaction.channel as TextChannel).bulkDelete(fetched, true);
91 deletedCount = deleted.size;
TheCodedProfbc94a5c2023-01-18 18:59:31 -050092 messages = messages.concat(Array.from(deleted.values() as Iterable<Message>));
Skyler Grey11236ba2022-08-08 21:13:33 +010093 } while (deletedCount === 100);
94
pineafan63fc5e22022-08-04 22:04:10 +010095 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +010096 messages.reverse().forEach((message) => {
pineafan813bdf42022-07-24 10:39:10 +010097 if (!message.author.bot) {
pineafan63fc5e22022-08-04 22:04:10 +010098 const sentDate = new Date(message.createdTimestamp);
Skyler Grey11236ba2022-08-08 21:13:33 +010099 out += `${message.author.username}#${message.author.discriminator} (${
100 message.author.id
101 }) [${sentDate.toUTCString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100102 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100103 lines.forEach((line) => {
104 out += `> ${line}\n`;
105 });
pineafan63fc5e22022-08-04 22:04:10 +0100106 out += "\n\n";
pineafan813bdf42022-07-24 10:39:10 +0100107 }
pineafan63fc5e22022-08-04 22:04:10 +0100108 });
TheCodedProf2e54a772023-02-14 16:26:47 -0500109
110 let interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
111
112 let newOut: Transcript = {
113 type: "ticket",
114 guild: interaction.guild!.id,
115 channel: interaction.channel!.id,
116 messages: [],
117 createdTimestamp: Date.now(),
118 createdBy: {
119 username: interaction.user.username,
120 discriminator: parseInt(interaction.user.discriminator),
121 id: interaction.user.id,
122 topRole: {
123 color: interactionMember?.roles.highest.color ?? 0x000000
124 }
125 }
126 }
127 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
128 messages.reverse().forEach((message) => {
129 let msg: TranscriptMessage = {
130 id: message.id,
131 author: {
132 username: message.author.username,
133 discriminator: parseInt(message.author.discriminator),
134 id: message.author.id,
135 topRole: {
136 color: message.member!.roles.highest.color
137 }
138 },
139 createdTimestamp: message.createdTimestamp
140 };
141 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
142 if (message.content) msg.content = message.content;
143 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
144 let obj: TranscriptEmbed = {};
145 if (embed.title) obj.title = embed.title;
146 if (embed.description) obj.description = embed.description;
147 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
148 return {
149 name: field.name,
150 value: field.value,
151 inline: field.inline ?? false
152 }
153 });
154 if (embed.footer) obj.footer = {
155 text: embed.footer.text,
156 };
157 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
158 return obj;
159 });
160 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
161 let obj: TranscriptComponent = {
162 type: child.type
163 }
164 if (child.type === ComponentType.Button) {
165 obj.style = child.style;
166 obj.label = child.label ?? "";
TheCodedProf362c51d2023-02-14 16:31:33 -0500167 } else if (child.type > 2) {
168 obj.placeholder = child.placeholder ?? "";
TheCodedProf2e54a772023-02-14 16:26:47 -0500169 }
170 return obj
171 }));
172 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
173 if (message.flags) msg.flags = message.flags.toArray();
174
175 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
176 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
177
178 });
179
pineafan3a02ea32022-08-11 21:35:04 +0100180 const topic = interaction.channel.topic;
181 let member: GuildMember | null = null;
182 if (topic !== null) {
183 const part = topic.split(" ")[0] ?? null;
184 if (part !== null) member = interaction.guild!.members.cache.get(part) ?? null;
185 }
186 let m: Message;
pineafan813bdf42022-07-24 10:39:10 +0100187 if (out !== "") {
188 const url = await pbClient.createPaste({
189 code: out,
190 expireDate: ExpireDate.Never,
pineafan3a02ea32022-08-11 21:35:04 +0100191 name:
192 `Ticket Transcript ${
193 member ? "for " + member.user.username + "#" + member.user.discriminator + " " : ""
194 }` + `(Created at ${new Date(interaction.channel.createdTimestamp).toDateString()})`,
pineafan63fc5e22022-08-04 22:04:10 +0100195 publicity: Publicity.Unlisted
196 });
pineafan4e425942022-08-08 22:01:47 +0100197 const guildConfig = await client.database.guilds.read(interaction.guild!.id);
pineafan3a02ea32022-08-11 21:35:04 +0100198 m = (await interaction.reply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100199 embeds: [
200 new EmojiEmbed()
201 .setTitle("Transcript")
202 .setDescription(
203 "You can view the transcript using the link below. You can save the link for later" +
204 (guildConfig.logging.logs.channel
205 ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.`
206 : ".")
207 )
208 .setStatus("Success")
209 .setEmoji("CONTROL.DOWNLOAD")
210 ],
211 components: [
PineaFan538d3752023-01-12 21:48:23 +0000212 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400213 new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(url),
214 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100215 .setLabel("Delete")
TheCodedProf21c08592022-09-13 14:14:43 -0400216 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100217 .setCustomId("close")
218 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
219 ])
220 ],
221 fetchReply: true
pineafan3a02ea32022-08-11 21:35:04 +0100222 })) as Message;
pineafan813bdf42022-07-24 10:39:10 +0100223 } else {
pineafan3a02ea32022-08-11 21:35:04 +0100224 m = (await interaction.reply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100225 embeds: [
226 new EmojiEmbed()
227 .setTitle("Transcript")
228 .setDescription(
229 "The transcript was empty, so no changes were made. To delete this ticket, press the delete button below."
230 )
231 .setStatus("Success")
232 .setEmoji("CONTROL.DOWNLOAD")
233 ],
234 components: [
PineaFan538d3752023-01-12 21:48:23 +0000235 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400236 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100237 .setLabel("Delete")
TheCodedProf21c08592022-09-13 14:14:43 -0400238 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 .setCustomId("close")
240 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
241 ])
242 ],
243 fetchReply: true
pineafan3a02ea32022-08-11 21:35:04 +0100244 })) as Message;
pineafan813bdf42022-07-24 10:39:10 +0100245 }
246 let i;
247 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000248 i = await m.awaitMessageComponent({
249 time: 300000,
TheCodedProf267563a2023-01-21 17:00:57 -0500250 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 +0000251 });
TheCodedProf267563a2023-01-21 17:00:57 -0500252 await i.deferUpdate();
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 } catch {
254 return;
255 }
pineafan63fc5e22022-08-04 22:04:10 +0100256 const data = {
Skyler Grey75ea9172022-08-06 10:22:23 +0100257 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100258 type: "ticketDeleted",
259 displayName: "Ticket Deleted",
pineafan813bdf42022-07-24 10:39:10 +0100260 calculateType: "ticketUpdate",
261 color: NucleusColors.red,
pineafan63fc5e22022-08-04 22:04:10 +0100262 emoji: "GUILD.TICKET.CLOSE",
pineafan813bdf42022-07-24 10:39:10 +0100263 timestamp: new Date().getTime()
264 },
265 list: {
pineafan3a02ea32022-08-11 21:35:04 +0100266 ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"),
PineaFan538d3752023-01-12 21:48:23 +0000267 deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)),
268 deleted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime()))
pineafan813bdf42022-07-24 10:39:10 +0100269 },
270 hidden: {
pineafan4e425942022-08-08 22:01:47 +0100271 guild: interaction.guild!.id
pineafan813bdf42022-07-24 10:39:10 +0100272 }
pineafan63fc5e22022-08-04 22:04:10 +0100273 };
pineafan813bdf42022-07-24 10:39:10 +0100274 log(data);
pineafan63fc5e22022-08-04 22:04:10 +0100275 await interaction.channel.delete();
276 return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100277}