blob: 2978cc1cc0fbef6463cd8dcaf21d77f737f4423b [file] [log] [blame]
Skyler Grey11236ba2022-08-08 21:13:33 +01001import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel } from "discord.js";
pineafan3a02ea32022-08-11 21:35:04 +01002import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
pineafan8b4b17f2022-02-27 20:42:52 +00003import confirmationMessage from "../../utils/confirmationMessage.js";
pineafan4edb7762022-06-26 19:21:04 +01004import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +00005import keyValueList from "../../utils/generateKeyValueList.js";
6import getEmojiByName from "../../utils/getEmojiByName.js";
pineafan4edb7762022-06-26 19:21:04 +01007import client from "../../utils/client.js";
pineafan4f164f32022-02-26 22:07:12 +00008
9const command = (builder: SlashCommandSubcommandBuilder) =>
10 builder
pineafan63fc5e22022-08-04 22:04:10 +010011 .setName("purge")
12 .setDescription("Bulk deletes messages in a channel")
Skyler Grey75ea9172022-08-06 10:22:23 +010013 .addIntegerOption((option) =>
14 option
15 .setName("amount")
16 .setDescription("The amount of messages to delete")
17 .setRequired(false)
18 .setMinValue(1)
19 .setMaxValue(100)
20 )
21 .addUserOption((option) =>
Skyler Grey11236ba2022-08-08 21:13:33 +010022 option.setName("user").setDescription("The user to purge messages from").setRequired(false)
Skyler Grey75ea9172022-08-06 10:22:23 +010023 )
24 .addStringOption((option) =>
Skyler Grey11236ba2022-08-08 21:13:33 +010025 option.setName("reason").setDescription("The reason for the purge").setRequired(false)
Skyler Grey75ea9172022-08-06 10:22:23 +010026 );
pineafan4f164f32022-02-26 22:07:12 +000027
pineafan3a02ea32022-08-11 21:35:04 +010028const callback = async (interaction: CommandInteraction): Promise<unknown> => {
Skyler Grey75ea9172022-08-06 10:22:23 +010029 const user = (interaction.options.getMember("user") as GuildMember) ?? null;
30 const channel = interaction.channel as GuildChannel;
31 if (
Skyler Grey11236ba2022-08-08 21:13:33 +010032 !["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(
33 channel.type.toString()
34 )
Skyler Grey75ea9172022-08-06 10:22:23 +010035 ) {
pineafan8b4b17f2022-02-27 20:42:52 +000036 return await interaction.reply({
37 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010038 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000039 .setEmoji("CHANNEL.PURGE.RED")
40 .setTitle("Purge")
41 .setDescription("You cannot purge this channel")
42 .setStatus("Danger")
43 ],
44 components: [],
pineafan63fc5e22022-08-04 22:04:10 +010045 ephemeral: true
46 });
pineafan8b4b17f2022-02-27 20:42:52 +000047 }
48 // TODO:[Modals] Replace this with a modal
Skyler Grey75ea9172022-08-06 10:22:23 +010049 if (!interaction.options.getInteger("amount")) {
pineafan8b4b17f2022-02-27 20:42:52 +000050 await interaction.reply({
51 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010052 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000053 .setEmoji("CHANNEL.PURGE.RED")
54 .setTitle("Purge")
55 .setDescription("Select how many messages to delete")
56 .setStatus("Danger")
57 ],
58 components: [],
59 ephemeral: true,
60 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +010061 });
62 let deleted = [] as Discord.Message[];
Skyler Greyad002172022-08-16 18:48:26 +010063 let timedOut = false;
64 let amountSelected = false;
65 while (!timedOut && !amountSelected) {
Skyler Grey75ea9172022-08-06 10:22:23 +010066 const m = (await interaction.editReply({
pineafan8b4b17f2022-02-27 20:42:52 +000067 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010068 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000069 .setEmoji("CHANNEL.PURGE.RED")
70 .setTitle("Purge")
Skyler Grey75ea9172022-08-06 10:22:23 +010071 .setDescription(
72 "Select how many messages to delete. You can continue clicking until all messages are cleared."
73 )
pineafan8b4b17f2022-02-27 20:42:52 +000074 .setStatus("Danger")
75 ],
76 components: [
77 new Discord.MessageActionRow().addComponents([
Skyler Grey11236ba2022-08-08 21:13:33 +010078 new Discord.MessageButton().setCustomId("1").setLabel("1").setStyle("SECONDARY"),
79 new Discord.MessageButton().setCustomId("3").setLabel("3").setStyle("SECONDARY"),
80 new Discord.MessageButton().setCustomId("5").setLabel("5").setStyle("SECONDARY")
pineafan63fc5e22022-08-04 22:04:10 +010081 ]),
82 new Discord.MessageActionRow().addComponents([
Skyler Grey11236ba2022-08-08 21:13:33 +010083 new Discord.MessageButton().setCustomId("10").setLabel("10").setStyle("SECONDARY"),
84 new Discord.MessageButton().setCustomId("25").setLabel("25").setStyle("SECONDARY"),
85 new Discord.MessageButton().setCustomId("50").setLabel("50").setStyle("SECONDARY")
pineafan8b4b17f2022-02-27 20:42:52 +000086 ]),
87 new Discord.MessageActionRow().addComponents([
88 new Discord.MessageButton()
89 .setCustomId("done")
90 .setLabel("Done")
91 .setStyle("SUCCESS")
92 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
93 ])
94 ]
Skyler Grey75ea9172022-08-06 10:22:23 +010095 })) as Discord.Message;
pineafan8b4b17f2022-02-27 20:42:52 +000096 let component;
97 try {
Skyler Grey75ea9172022-08-06 10:22:23 +010098 component = m.awaitMessageComponent({
99 filter: (m) => m.user.id === interaction.user.id,
100 time: 300000
101 });
102 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100103 timedOut = true;
104 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100105 }
pineafan8b4b17f2022-02-27 20:42:52 +0000106 component.deferUpdate();
Skyler Greyad002172022-08-16 18:48:26 +0100107 if (component.customId === "done") {
108 amountSelected = true;
109 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100110 }
Skyler Greyad002172022-08-16 18:48:26 +0100111 const amount = parseInt(component.customId);
112
pineafan5d1908e2022-02-28 21:34:47 +0000113 let messages;
Skyler Grey11236ba2022-08-08 21:13:33 +0100114 await (interaction.channel as TextChannel).messages.fetch({ limit: amount }).then(async (ms) => {
115 if (user) {
116 ms = ms.filter((m) => m.author.id === user.id);
117 }
118 messages = await (channel as TextChannel).bulkDelete(ms, true);
119 });
pineafane625d782022-05-09 18:04:32 +0100120 if (messages) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100121 deleted = deleted.concat(messages.map((m) => m));
pineafane625d782022-05-09 18:04:32 +0100122 }
pineafan8b4b17f2022-02-27 20:42:52 +0000123 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100124 if (deleted.length === 0)
125 return await interaction.editReply({
126 embeds: [
127 new EmojiEmbed()
128 .setEmoji("CHANNEL.PURGE.RED")
129 .setTitle("Purge")
130 .setDescription("No messages were deleted")
131 .setStatus("Danger")
132 ],
133 components: []
134 });
pineafan4edb7762022-06-26 19:21:04 +0100135 if (user) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100136 await client.database.history.create(
137 "purge",
138 interaction.guild.id,
139 user,
140 interaction.options.getString("reason"),
141 null,
142 null,
143 deleted.length
144 );
pineafan4edb7762022-06-26 19:21:04 +0100145 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100146 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
pineafan63fc5e22022-08-04 22:04:10 +0100147 const data = {
148 meta: {
149 type: "channelPurge",
150 displayName: "Channel Purged",
151 calculateType: "messageDelete",
152 color: NucleusColors.red,
153 emoji: "PUNISH.BAN.RED",
154 timestamp: new Date().getTime()
155 },
156 list: {
Skyler Grey11236ba2022-08-08 21:13:33 +0100157 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
158 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
159 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
pineafan63fc5e22022-08-04 22:04:10 +0100160 messagesCleared: entry(deleted.length, deleted.length)
161 },
162 hidden: {
163 guild: interaction.guild.id
pineafane625d782022-05-09 18:04:32 +0100164 }
pineafan63fc5e22022-08-04 22:04:10 +0100165 };
166 log(data);
167 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +0100168 deleted.reverse().forEach((message) => {
Skyler Grey11236ba2022-08-08 21:13:33 +0100169 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(
Skyler Grey75ea9172022-08-06 10:22:23 +0100170 message.createdTimestamp
171 ).toISOString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100172 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100173 lines.forEach((line) => {
174 out += `> ${line}\n`;
175 });
pineafan63fc5e22022-08-04 22:04:10 +0100176 out += "\n\n";
177 });
178 const attachmentObject = {
179 attachment: Buffer.from(out),
180 name: `purge-${channel.id}-${Date.now()}.txt`,
181 description: "Purge log"
182 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100183 const m = (await interaction.editReply({
184 embeds: [
185 new EmojiEmbed()
186 .setEmoji("CHANNEL.PURGE.GREEN")
187 .setTitle("Purge")
188 .setDescription("Messages cleared")
189 .setStatus("Success")
190 ],
191 components: [
192 new Discord.MessageActionRow().addComponents([
193 new Discord.MessageButton()
194 .setCustomId("download")
195 .setLabel("Download transcript")
196 .setStyle("SUCCESS")
197 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
198 ])
199 ]
200 })) as Discord.Message;
pineafan5d1908e2022-02-28 21:34:47 +0000201 let component;
202 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100203 component = await m.awaitMessageComponent({
204 filter: (m) => m.user.id === interaction.user.id,
205 time: 300000
206 });
207 } catch {
208 return;
209 }
pineafan5d1908e2022-02-28 21:34:47 +0000210 if (component && component.customId === "download") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100211 interaction.editReply({
212 embeds: [
213 new EmojiEmbed()
214 .setEmoji("CHANNEL.PURGE.GREEN")
215 .setTitle("Purge")
216 .setDescription("Uploaded")
217 .setStatus("Success")
218 ],
219 components: [],
220 files: [attachmentObject]
221 });
pineafan5d1908e2022-02-28 21:34:47 +0000222 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100223 interaction.editReply({
224 embeds: [
225 new EmojiEmbed()
226 .setEmoji("CHANNEL.PURGE.GREEN")
227 .setTitle("Purge")
228 .setDescription("Messages cleared")
229 .setStatus("Success")
230 ],
231 components: []
232 });
pineafan5d1908e2022-02-28 21:34:47 +0000233 }
pineafan63fc5e22022-08-04 22:04:10 +0100234 return;
pineafan8b4b17f2022-02-27 20:42:52 +0000235 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100236 const confirmation = await new confirmationMessage(interaction)
pineafan8b4b17f2022-02-27 20:42:52 +0000237 .setEmoji("CHANNEL.PURGE.RED")
238 .setTitle("Purge")
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 .setDescription(
240 keyValueList({
241 channel: `<#${channel.id}>`,
242 amount: interaction.options.getInteger("amount").toString(),
243 reason: `\n> ${
244 interaction.options.getString("reason")
245 ? interaction.options.getString("reason")
246 : "*No reason provided*"
247 }`
248 })
249 )
pineafan8b4b17f2022-02-27 20:42:52 +0000250 .setColor("Danger")
pineafan63fc5e22022-08-04 22:04:10 +0100251 .send();
252 if (confirmation.cancelled) return;
pineafan377794f2022-04-18 19:01:01 +0100253 if (confirmation.success) {
pineafan5d1908e2022-02-28 21:34:47 +0000254 let messages;
pineafan8b4b17f2022-02-27 20:42:52 +0000255 try {
pineafan377794f2022-04-18 19:01:01 +0100256 if (!user) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100257 const toDelete = await (interaction.channel as TextChannel).messages.fetch({
Skyler Grey75ea9172022-08-06 10:22:23 +0100258 limit: interaction.options.getInteger("amount")
259 });
Skyler Grey11236ba2022-08-08 21:13:33 +0100260 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
pineafan377794f2022-04-18 19:01:01 +0100261 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100262 const toDelete = (
263 await (
Skyler Grey11236ba2022-08-08 21:13:33 +0100264 await (interaction.channel as TextChannel).messages.fetch({
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 limit: 100
266 })
267 ).filter((m) => m.author.id === user.id)
268 ).first(interaction.options.getInteger("amount"));
Skyler Grey11236ba2022-08-08 21:13:33 +0100269 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
pineafan377794f2022-04-18 19:01:01 +0100270 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100271 } catch (e) {
272 await interaction.editReply({
273 embeds: [
274 new EmojiEmbed()
275 .setEmoji("CHANNEL.PURGE.RED")
276 .setTitle("Purge")
Skyler Grey11236ba2022-08-08 21:13:33 +0100277 .setDescription("Something went wrong and no messages were deleted")
Skyler Grey75ea9172022-08-06 10:22:23 +0100278 .setStatus("Danger")
279 ],
280 components: []
281 });
pineafan8b4b17f2022-02-27 20:42:52 +0000282 }
pineafan4edb7762022-06-26 19:21:04 +0100283 if (user) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100284 await client.database.history.create(
285 "purge",
286 interaction.guild.id,
287 user,
288 interaction.options.getString("reason"),
289 null,
290 null,
291 messages.size
292 );
pineafan4edb7762022-06-26 19:21:04 +0100293 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100294 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
pineafan63fc5e22022-08-04 22:04:10 +0100295 const data = {
296 meta: {
297 type: "channelPurge",
298 displayName: "Channel Purged",
299 calculateType: "messageDelete",
300 color: NucleusColors.red,
301 emoji: "PUNISH.BAN.RED",
302 timestamp: new Date().getTime()
303 },
304 list: {
Skyler Grey11236ba2022-08-08 21:13:33 +0100305 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
306 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
307 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
pineafan63fc5e22022-08-04 22:04:10 +0100308 messagesCleared: entry(messages.size, messages.size)
309 },
310 hidden: {
311 guild: interaction.guild.id
pineafane625d782022-05-09 18:04:32 +0100312 }
pineafan63fc5e22022-08-04 22:04:10 +0100313 };
314 log(data);
315 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +0100316 messages.reverse().forEach((message) => {
Skyler Grey11236ba2022-08-08 21:13:33 +0100317 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(
Skyler Grey75ea9172022-08-06 10:22:23 +0100318 message.createdTimestamp
319 ).toISOString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100320 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100321 lines.forEach((line) => {
322 out += `> ${line}\n`;
323 });
pineafan63fc5e22022-08-04 22:04:10 +0100324 out += "\n\n";
325 });
326 const attachmentObject = {
327 attachment: Buffer.from(out),
328 name: `purge-${channel.id}-${Date.now()}.txt`,
329 description: "Purge log"
330 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100331 const m = (await interaction.editReply({
332 embeds: [
333 new EmojiEmbed()
334 .setEmoji("CHANNEL.PURGE.GREEN")
335 .setTitle("Purge")
336 .setDescription("Messages cleared")
337 .setStatus("Success")
338 ],
339 components: [
340 new Discord.MessageActionRow().addComponents([
341 new Discord.MessageButton()
342 .setCustomId("download")
343 .setLabel("Download transcript")
344 .setStyle("SUCCESS")
345 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
346 ])
347 ]
348 })) as Discord.Message;
pineafan5d1908e2022-02-28 21:34:47 +0000349 let component;
350 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100351 component = await m.awaitMessageComponent({
352 filter: (m) => m.user.id === interaction.user.id,
353 time: 300000
354 });
355 } catch {
356 return;
357 }
pineafan5d1908e2022-02-28 21:34:47 +0000358 if (component && component.customId === "download") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100359 interaction.editReply({
360 embeds: [
361 new EmojiEmbed()
362 .setEmoji("CHANNEL.PURGE.GREEN")
363 .setTitle("Purge")
364 .setDescription("Transcript uploaded above")
365 .setStatus("Success")
366 ],
367 components: [],
368 files: [attachmentObject]
369 });
pineafan5d1908e2022-02-28 21:34:47 +0000370 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100371 interaction.editReply({
372 embeds: [
373 new EmojiEmbed()
374 .setEmoji("CHANNEL.PURGE.GREEN")
375 .setTitle("Purge")
376 .setDescription("Messages cleared")
377 .setStatus("Success")
378 ],
379 components: []
380 });
pineafan5d1908e2022-02-28 21:34:47 +0000381 }
pineafan8b4b17f2022-02-27 20:42:52 +0000382 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100383 await interaction.editReply({
384 embeds: [
385 new EmojiEmbed()
386 .setEmoji("CHANNEL.PURGE.GREEN")
387 .setTitle("Purge")
388 .setDescription("No changes were made")
389 .setStatus("Success")
390 ],
391 components: []
392 });
pineafan8b4b17f2022-02-27 20:42:52 +0000393 }
394 }
pineafan63fc5e22022-08-04 22:04:10 +0100395};
pineafan4f164f32022-02-26 22:07:12 +0000396
pineafanbd02b4a2022-08-05 22:01:38 +0100397const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100398 const member = interaction.member as GuildMember;
399 const me = interaction.guild.me!;
pineafanc1c18792022-08-03 21:41:36 +0100400 // Check if nucleus has the manage_messages permission
pineafan3a02ea32022-08-11 21:35:04 +0100401 if (!me.permissions.has("MANAGE_MESSAGES")) throw new Error("I do not have the *Manage Messages* permission");
pineafan8b4b17f2022-02-27 20:42:52 +0000402 // Allow the owner to purge
pineafan63fc5e22022-08-04 22:04:10 +0100403 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000404 // Check if the user has manage_messages permission
pineafan3a02ea32022-08-11 21:35:04 +0100405 if (!member.permissions.has("MANAGE_MESSAGES")) throw new Error("You do not have the *Manage Messages* permission");
pineafanc1c18792022-08-03 21:41:36 +0100406 // Allow purge
pineafan63fc5e22022-08-04 22:04:10 +0100407 return true;
408};
pineafan4f164f32022-02-26 22:07:12 +0000409
Skyler Grey75ea9172022-08-06 10:22:23 +0100410export { command, callback, check };