blob: 63a919c6c1085f62a8ed935855a9017ee504a0c9 [file] [log] [blame]
Skyler Grey11236ba2022-08-08 21:13:33 +01001import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel } from "discord.js";
pineafan4f164f32022-02-26 22:07:12 +00002import { 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
Skyler Grey11236ba2022-08-08 21:13:33 +010028const callback = async (interaction: CommandInteraction): Promise<void | 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[];
pineafan8b4b17f2022-02-27 20:42:52 +000063 while (true) {
Skyler Grey75ea9172022-08-06 10:22:23 +010064 const m = (await interaction.editReply({
pineafan8b4b17f2022-02-27 20:42:52 +000065 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010066 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000067 .setEmoji("CHANNEL.PURGE.RED")
68 .setTitle("Purge")
Skyler Grey75ea9172022-08-06 10:22:23 +010069 .setDescription(
70 "Select how many messages to delete. You can continue clicking until all messages are cleared."
71 )
pineafan8b4b17f2022-02-27 20:42:52 +000072 .setStatus("Danger")
73 ],
74 components: [
75 new Discord.MessageActionRow().addComponents([
Skyler Grey11236ba2022-08-08 21:13:33 +010076 new Discord.MessageButton().setCustomId("1").setLabel("1").setStyle("SECONDARY"),
77 new Discord.MessageButton().setCustomId("3").setLabel("3").setStyle("SECONDARY"),
78 new Discord.MessageButton().setCustomId("5").setLabel("5").setStyle("SECONDARY")
pineafan63fc5e22022-08-04 22:04:10 +010079 ]),
80 new Discord.MessageActionRow().addComponents([
Skyler Grey11236ba2022-08-08 21:13:33 +010081 new Discord.MessageButton().setCustomId("10").setLabel("10").setStyle("SECONDARY"),
82 new Discord.MessageButton().setCustomId("25").setLabel("25").setStyle("SECONDARY"),
83 new Discord.MessageButton().setCustomId("50").setLabel("50").setStyle("SECONDARY")
pineafan8b4b17f2022-02-27 20:42:52 +000084 ]),
85 new Discord.MessageActionRow().addComponents([
86 new Discord.MessageButton()
87 .setCustomId("done")
88 .setLabel("Done")
89 .setStyle("SUCCESS")
90 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
91 ])
92 ]
Skyler Grey75ea9172022-08-06 10:22:23 +010093 })) as Discord.Message;
pineafan8b4b17f2022-02-27 20:42:52 +000094 let component;
95 try {
Skyler Grey75ea9172022-08-06 10:22:23 +010096 component = m.awaitMessageComponent({
97 filter: (m) => m.user.id === interaction.user.id,
98 time: 300000
99 });
100 } catch (e) {
101 break;
102 }
pineafan8b4b17f2022-02-27 20:42:52 +0000103 component.deferUpdate();
104 if (component.customId === "done") break;
105 let amount;
Skyler Grey75ea9172022-08-06 10:22:23 +0100106 try {
107 amount = parseInt(component.customId);
108 } catch {
109 break;
110 }
pineafan5d1908e2022-02-28 21:34:47 +0000111 let messages;
Skyler Grey11236ba2022-08-08 21:13:33 +0100112 await (interaction.channel as TextChannel).messages.fetch({ limit: amount }).then(async (ms) => {
113 if (user) {
114 ms = ms.filter((m) => m.author.id === user.id);
115 }
116 messages = await (channel as TextChannel).bulkDelete(ms, true);
117 });
pineafane625d782022-05-09 18:04:32 +0100118 if (messages) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100119 deleted = deleted.concat(messages.map((m) => m));
pineafane625d782022-05-09 18:04:32 +0100120 }
pineafan8b4b17f2022-02-27 20:42:52 +0000121 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100122 if (deleted.length === 0)
123 return await interaction.editReply({
124 embeds: [
125 new EmojiEmbed()
126 .setEmoji("CHANNEL.PURGE.RED")
127 .setTitle("Purge")
128 .setDescription("No messages were deleted")
129 .setStatus("Danger")
130 ],
131 components: []
132 });
pineafan4edb7762022-06-26 19:21:04 +0100133 if (user) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100134 await client.database.history.create(
135 "purge",
136 interaction.guild.id,
137 user,
138 interaction.options.getString("reason"),
139 null,
140 null,
141 deleted.length
142 );
pineafan4edb7762022-06-26 19:21:04 +0100143 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100144 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
pineafan63fc5e22022-08-04 22:04:10 +0100145 const data = {
146 meta: {
147 type: "channelPurge",
148 displayName: "Channel Purged",
149 calculateType: "messageDelete",
150 color: NucleusColors.red,
151 emoji: "PUNISH.BAN.RED",
152 timestamp: new Date().getTime()
153 },
154 list: {
Skyler Grey11236ba2022-08-08 21:13:33 +0100155 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
156 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
157 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
pineafan63fc5e22022-08-04 22:04:10 +0100158 messagesCleared: entry(deleted.length, deleted.length)
159 },
160 hidden: {
161 guild: interaction.guild.id
pineafane625d782022-05-09 18:04:32 +0100162 }
pineafan63fc5e22022-08-04 22:04:10 +0100163 };
164 log(data);
165 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +0100166 deleted.reverse().forEach((message) => {
Skyler Grey11236ba2022-08-08 21:13:33 +0100167 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(
Skyler Grey75ea9172022-08-06 10:22:23 +0100168 message.createdTimestamp
169 ).toISOString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100170 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100171 lines.forEach((line) => {
172 out += `> ${line}\n`;
173 });
pineafan63fc5e22022-08-04 22:04:10 +0100174 out += "\n\n";
175 });
176 const attachmentObject = {
177 attachment: Buffer.from(out),
178 name: `purge-${channel.id}-${Date.now()}.txt`,
179 description: "Purge log"
180 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100181 const m = (await interaction.editReply({
182 embeds: [
183 new EmojiEmbed()
184 .setEmoji("CHANNEL.PURGE.GREEN")
185 .setTitle("Purge")
186 .setDescription("Messages cleared")
187 .setStatus("Success")
188 ],
189 components: [
190 new Discord.MessageActionRow().addComponents([
191 new Discord.MessageButton()
192 .setCustomId("download")
193 .setLabel("Download transcript")
194 .setStyle("SUCCESS")
195 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
196 ])
197 ]
198 })) as Discord.Message;
pineafan5d1908e2022-02-28 21:34:47 +0000199 let component;
200 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100201 component = await m.awaitMessageComponent({
202 filter: (m) => m.user.id === interaction.user.id,
203 time: 300000
204 });
205 } catch {
206 return;
207 }
pineafan5d1908e2022-02-28 21:34:47 +0000208 if (component && component.customId === "download") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100209 interaction.editReply({
210 embeds: [
211 new EmojiEmbed()
212 .setEmoji("CHANNEL.PURGE.GREEN")
213 .setTitle("Purge")
214 .setDescription("Uploaded")
215 .setStatus("Success")
216 ],
217 components: [],
218 files: [attachmentObject]
219 });
pineafan5d1908e2022-02-28 21:34:47 +0000220 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100221 interaction.editReply({
222 embeds: [
223 new EmojiEmbed()
224 .setEmoji("CHANNEL.PURGE.GREEN")
225 .setTitle("Purge")
226 .setDescription("Messages cleared")
227 .setStatus("Success")
228 ],
229 components: []
230 });
pineafan5d1908e2022-02-28 21:34:47 +0000231 }
pineafan63fc5e22022-08-04 22:04:10 +0100232 return;
pineafan8b4b17f2022-02-27 20:42:52 +0000233 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100234 const confirmation = await new confirmationMessage(interaction)
pineafan8b4b17f2022-02-27 20:42:52 +0000235 .setEmoji("CHANNEL.PURGE.RED")
236 .setTitle("Purge")
Skyler Grey75ea9172022-08-06 10:22:23 +0100237 .setDescription(
238 keyValueList({
239 channel: `<#${channel.id}>`,
240 amount: interaction.options.getInteger("amount").toString(),
241 reason: `\n> ${
242 interaction.options.getString("reason")
243 ? interaction.options.getString("reason")
244 : "*No reason provided*"
245 }`
246 })
247 )
pineafan8b4b17f2022-02-27 20:42:52 +0000248 .setColor("Danger")
pineafan63fc5e22022-08-04 22:04:10 +0100249 .send();
250 if (confirmation.cancelled) return;
pineafan377794f2022-04-18 19:01:01 +0100251 if (confirmation.success) {
pineafan5d1908e2022-02-28 21:34:47 +0000252 let messages;
pineafan8b4b17f2022-02-27 20:42:52 +0000253 try {
pineafan377794f2022-04-18 19:01:01 +0100254 if (!user) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100255 const toDelete = await (interaction.channel as TextChannel).messages.fetch({
Skyler Grey75ea9172022-08-06 10:22:23 +0100256 limit: interaction.options.getInteger("amount")
257 });
Skyler Grey11236ba2022-08-08 21:13:33 +0100258 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
pineafan377794f2022-04-18 19:01:01 +0100259 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100260 const toDelete = (
261 await (
Skyler Grey11236ba2022-08-08 21:13:33 +0100262 await (interaction.channel as TextChannel).messages.fetch({
Skyler Grey75ea9172022-08-06 10:22:23 +0100263 limit: 100
264 })
265 ).filter((m) => m.author.id === user.id)
266 ).first(interaction.options.getInteger("amount"));
Skyler Grey11236ba2022-08-08 21:13:33 +0100267 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
pineafan377794f2022-04-18 19:01:01 +0100268 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100269 } catch (e) {
270 await interaction.editReply({
271 embeds: [
272 new EmojiEmbed()
273 .setEmoji("CHANNEL.PURGE.RED")
274 .setTitle("Purge")
Skyler Grey11236ba2022-08-08 21:13:33 +0100275 .setDescription("Something went wrong and no messages were deleted")
Skyler Grey75ea9172022-08-06 10:22:23 +0100276 .setStatus("Danger")
277 ],
278 components: []
279 });
pineafan8b4b17f2022-02-27 20:42:52 +0000280 }
pineafan4edb7762022-06-26 19:21:04 +0100281 if (user) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100282 await client.database.history.create(
283 "purge",
284 interaction.guild.id,
285 user,
286 interaction.options.getString("reason"),
287 null,
288 null,
289 messages.size
290 );
pineafan4edb7762022-06-26 19:21:04 +0100291 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100292 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
pineafan63fc5e22022-08-04 22:04:10 +0100293 const data = {
294 meta: {
295 type: "channelPurge",
296 displayName: "Channel Purged",
297 calculateType: "messageDelete",
298 color: NucleusColors.red,
299 emoji: "PUNISH.BAN.RED",
300 timestamp: new Date().getTime()
301 },
302 list: {
Skyler Grey11236ba2022-08-08 21:13:33 +0100303 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
304 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
305 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
pineafan63fc5e22022-08-04 22:04:10 +0100306 messagesCleared: entry(messages.size, messages.size)
307 },
308 hidden: {
309 guild: interaction.guild.id
pineafane625d782022-05-09 18:04:32 +0100310 }
pineafan63fc5e22022-08-04 22:04:10 +0100311 };
312 log(data);
313 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +0100314 messages.reverse().forEach((message) => {
Skyler Grey11236ba2022-08-08 21:13:33 +0100315 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(
Skyler Grey75ea9172022-08-06 10:22:23 +0100316 message.createdTimestamp
317 ).toISOString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100318 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100319 lines.forEach((line) => {
320 out += `> ${line}\n`;
321 });
pineafan63fc5e22022-08-04 22:04:10 +0100322 out += "\n\n";
323 });
324 const attachmentObject = {
325 attachment: Buffer.from(out),
326 name: `purge-${channel.id}-${Date.now()}.txt`,
327 description: "Purge log"
328 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100329 const m = (await interaction.editReply({
330 embeds: [
331 new EmojiEmbed()
332 .setEmoji("CHANNEL.PURGE.GREEN")
333 .setTitle("Purge")
334 .setDescription("Messages cleared")
335 .setStatus("Success")
336 ],
337 components: [
338 new Discord.MessageActionRow().addComponents([
339 new Discord.MessageButton()
340 .setCustomId("download")
341 .setLabel("Download transcript")
342 .setStyle("SUCCESS")
343 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
344 ])
345 ]
346 })) as Discord.Message;
pineafan5d1908e2022-02-28 21:34:47 +0000347 let component;
348 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100349 component = await m.awaitMessageComponent({
350 filter: (m) => m.user.id === interaction.user.id,
351 time: 300000
352 });
353 } catch {
354 return;
355 }
pineafan5d1908e2022-02-28 21:34:47 +0000356 if (component && component.customId === "download") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100357 interaction.editReply({
358 embeds: [
359 new EmojiEmbed()
360 .setEmoji("CHANNEL.PURGE.GREEN")
361 .setTitle("Purge")
362 .setDescription("Transcript uploaded above")
363 .setStatus("Success")
364 ],
365 components: [],
366 files: [attachmentObject]
367 });
pineafan5d1908e2022-02-28 21:34:47 +0000368 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100369 interaction.editReply({
370 embeds: [
371 new EmojiEmbed()
372 .setEmoji("CHANNEL.PURGE.GREEN")
373 .setTitle("Purge")
374 .setDescription("Messages cleared")
375 .setStatus("Success")
376 ],
377 components: []
378 });
pineafan5d1908e2022-02-28 21:34:47 +0000379 }
pineafan8b4b17f2022-02-27 20:42:52 +0000380 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100381 await interaction.editReply({
382 embeds: [
383 new EmojiEmbed()
384 .setEmoji("CHANNEL.PURGE.GREEN")
385 .setTitle("Purge")
386 .setDescription("No changes were made")
387 .setStatus("Success")
388 ],
389 components: []
390 });
pineafan8b4b17f2022-02-27 20:42:52 +0000391 }
392 }
pineafan63fc5e22022-08-04 22:04:10 +0100393};
pineafan4f164f32022-02-26 22:07:12 +0000394
pineafanbd02b4a2022-08-05 22:01:38 +0100395const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100396 const member = interaction.member as GuildMember;
397 const me = interaction.guild.me!;
pineafanc1c18792022-08-03 21:41:36 +0100398 // Check if nucleus has the manage_messages permission
Skyler Grey11236ba2022-08-08 21:13:33 +0100399 if (!me.permissions.has("MANAGE_MESSAGES")) throw "I do not have the *Manage Messages* permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000400 // Allow the owner to purge
pineafan63fc5e22022-08-04 22:04:10 +0100401 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000402 // Check if the user has manage_messages permission
Skyler Grey11236ba2022-08-08 21:13:33 +0100403 if (!member.permissions.has("MANAGE_MESSAGES")) throw "You do not have the *Manage Messages* permission";
pineafanc1c18792022-08-03 21:41:36 +0100404 // Allow purge
pineafan63fc5e22022-08-04 22:04:10 +0100405 return true;
406};
pineafan4f164f32022-02-26 22:07:12 +0000407
Skyler Grey75ea9172022-08-06 10:22:23 +0100408export { command, callback, check };