blob: b78c423266b0edbcc8c9d78304e1b8a7f781f8e0 [file] [log] [blame]
Skyler Grey75ea9172022-08-06 10:22:23 +01001import Discord, {
2 CommandInteraction,
3 GuildChannel,
4 GuildMember,
5 TextChannel
6} from "discord.js";
pineafan4f164f32022-02-26 22:07:12 +00007import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
pineafan8b4b17f2022-02-27 20:42:52 +00008import confirmationMessage from "../../utils/confirmationMessage.js";
pineafan4edb7762022-06-26 19:21:04 +01009import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +000010import keyValueList from "../../utils/generateKeyValueList.js";
11import getEmojiByName from "../../utils/getEmojiByName.js";
pineafan4edb7762022-06-26 19:21:04 +010012import client from "../../utils/client.js";
pineafan4f164f32022-02-26 22:07:12 +000013
14const command = (builder: SlashCommandSubcommandBuilder) =>
15 builder
pineafan63fc5e22022-08-04 22:04:10 +010016 .setName("purge")
17 .setDescription("Bulk deletes messages in a channel")
Skyler Grey75ea9172022-08-06 10:22:23 +010018 .addIntegerOption((option) =>
19 option
20 .setName("amount")
21 .setDescription("The amount of messages to delete")
22 .setRequired(false)
23 .setMinValue(1)
24 .setMaxValue(100)
25 )
26 .addUserOption((option) =>
27 option
28 .setName("user")
29 .setDescription("The user to purge messages from")
30 .setRequired(false)
31 )
32 .addStringOption((option) =>
33 option
34 .setName("reason")
35 .setDescription("The reason for the purge")
36 .setRequired(false)
37 );
pineafan4f164f32022-02-26 22:07:12 +000038
Skyler Grey75ea9172022-08-06 10:22:23 +010039const callback = async (
40 interaction: CommandInteraction
41): Promise<void | unknown> => {
42 const user = (interaction.options.getMember("user") as GuildMember) ?? null;
43 const channel = interaction.channel as GuildChannel;
44 if (
45 ![
46 "GUILD_TEXT",
47 "GUILD_NEWS",
48 "GUILD_NEWS_THREAD",
49 "GUILD_PUBLIC_THREAD",
50 "GUILD_PRIVATE_THREAD"
51 ].includes(channel.type.toString())
52 ) {
pineafan8b4b17f2022-02-27 20:42:52 +000053 return await interaction.reply({
54 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010055 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000056 .setEmoji("CHANNEL.PURGE.RED")
57 .setTitle("Purge")
58 .setDescription("You cannot purge this channel")
59 .setStatus("Danger")
60 ],
61 components: [],
pineafan63fc5e22022-08-04 22:04:10 +010062 ephemeral: true
63 });
pineafan8b4b17f2022-02-27 20:42:52 +000064 }
65 // TODO:[Modals] Replace this with a modal
Skyler Grey75ea9172022-08-06 10:22:23 +010066 if (!interaction.options.getInteger("amount")) {
pineafan8b4b17f2022-02-27 20:42:52 +000067 await interaction.reply({
68 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010069 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000070 .setEmoji("CHANNEL.PURGE.RED")
71 .setTitle("Purge")
72 .setDescription("Select how many messages to delete")
73 .setStatus("Danger")
74 ],
75 components: [],
76 ephemeral: true,
77 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +010078 });
79 let deleted = [] as Discord.Message[];
pineafan8b4b17f2022-02-27 20:42:52 +000080 while (true) {
Skyler Grey75ea9172022-08-06 10:22:23 +010081 const m = (await interaction.editReply({
pineafan8b4b17f2022-02-27 20:42:52 +000082 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010083 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000084 .setEmoji("CHANNEL.PURGE.RED")
85 .setTitle("Purge")
Skyler Grey75ea9172022-08-06 10:22:23 +010086 .setDescription(
87 "Select how many messages to delete. You can continue clicking until all messages are cleared."
88 )
pineafan8b4b17f2022-02-27 20:42:52 +000089 .setStatus("Danger")
90 ],
91 components: [
92 new Discord.MessageActionRow().addComponents([
93 new Discord.MessageButton()
94 .setCustomId("1")
95 .setLabel("1")
96 .setStyle("SECONDARY"),
97 new Discord.MessageButton()
98 .setCustomId("3")
99 .setLabel("3")
100 .setStyle("SECONDARY"),
101 new Discord.MessageButton()
102 .setCustomId("5")
103 .setLabel("5")
104 .setStyle("SECONDARY")
pineafan63fc5e22022-08-04 22:04:10 +0100105 ]),
106 new Discord.MessageActionRow().addComponents([
pineafan8b4b17f2022-02-27 20:42:52 +0000107 new Discord.MessageButton()
108 .setCustomId("10")
109 .setLabel("10")
110 .setStyle("SECONDARY"),
111 new Discord.MessageButton()
112 .setCustomId("25")
113 .setLabel("25")
114 .setStyle("SECONDARY"),
115 new Discord.MessageButton()
116 .setCustomId("50")
117 .setLabel("50")
118 .setStyle("SECONDARY")
119 ]),
120 new Discord.MessageActionRow().addComponents([
121 new Discord.MessageButton()
122 .setCustomId("done")
123 .setLabel("Done")
124 .setStyle("SUCCESS")
125 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
126 ])
127 ]
Skyler Grey75ea9172022-08-06 10:22:23 +0100128 })) as Discord.Message;
pineafan8b4b17f2022-02-27 20:42:52 +0000129 let component;
130 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100131 component = m.awaitMessageComponent({
132 filter: (m) => m.user.id === interaction.user.id,
133 time: 300000
134 });
135 } catch (e) {
136 break;
137 }
pineafan8b4b17f2022-02-27 20:42:52 +0000138 component.deferUpdate();
139 if (component.customId === "done") break;
140 let amount;
Skyler Grey75ea9172022-08-06 10:22:23 +0100141 try {
142 amount = parseInt(component.customId);
143 } catch {
144 break;
145 }
pineafan5d1908e2022-02-28 21:34:47 +0000146 let messages;
Skyler Grey75ea9172022-08-06 10:22:23 +0100147 await (interaction.channel as TextChannel).messages
148 .fetch({ limit: amount })
149 .then(async (ms) => {
150 if (user) {
151 ms = ms.filter((m) => m.author.id === user.id);
152 }
153 messages = await (channel as TextChannel).bulkDelete(
154 ms,
155 true
156 );
157 });
pineafane625d782022-05-09 18:04:32 +0100158 if (messages) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100159 deleted = deleted.concat(messages.map((m) => m));
pineafane625d782022-05-09 18:04:32 +0100160 }
pineafan8b4b17f2022-02-27 20:42:52 +0000161 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100162 if (deleted.length === 0)
163 return await interaction.editReply({
164 embeds: [
165 new EmojiEmbed()
166 .setEmoji("CHANNEL.PURGE.RED")
167 .setTitle("Purge")
168 .setDescription("No messages were deleted")
169 .setStatus("Danger")
170 ],
171 components: []
172 });
pineafan4edb7762022-06-26 19:21:04 +0100173 if (user) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100174 await client.database.history.create(
175 "purge",
176 interaction.guild.id,
177 user,
178 interaction.options.getString("reason"),
179 null,
180 null,
181 deleted.length
182 );
pineafan4edb7762022-06-26 19:21:04 +0100183 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100184 const { log, NucleusColors, entry, renderUser, renderChannel } =
185 client.logger;
pineafan63fc5e22022-08-04 22:04:10 +0100186 const data = {
187 meta: {
188 type: "channelPurge",
189 displayName: "Channel Purged",
190 calculateType: "messageDelete",
191 color: NucleusColors.red,
192 emoji: "PUNISH.BAN.RED",
193 timestamp: new Date().getTime()
194 },
195 list: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100196 memberId: entry(
197 interaction.user.id,
198 `\`${interaction.user.id}\``
199 ),
200 purgedBy: entry(
201 interaction.user.id,
202 renderUser(interaction.user)
203 ),
204 channel: entry(
205 interaction.channel.id,
206 renderChannel(interaction.channel)
207 ),
pineafan63fc5e22022-08-04 22:04:10 +0100208 messagesCleared: entry(deleted.length, deleted.length)
209 },
210 hidden: {
211 guild: interaction.guild.id
pineafane625d782022-05-09 18:04:32 +0100212 }
pineafan63fc5e22022-08-04 22:04:10 +0100213 };
214 log(data);
215 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +0100216 deleted.reverse().forEach((message) => {
217 out += `${message.author.username}#${
218 message.author.discriminator
219 } (${message.author.id}) [${new Date(
220 message.createdTimestamp
221 ).toISOString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100222 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100223 lines.forEach((line) => {
224 out += `> ${line}\n`;
225 });
pineafan63fc5e22022-08-04 22:04:10 +0100226 out += "\n\n";
227 });
228 const attachmentObject = {
229 attachment: Buffer.from(out),
230 name: `purge-${channel.id}-${Date.now()}.txt`,
231 description: "Purge log"
232 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100233 const m = (await interaction.editReply({
234 embeds: [
235 new EmojiEmbed()
236 .setEmoji("CHANNEL.PURGE.GREEN")
237 .setTitle("Purge")
238 .setDescription("Messages cleared")
239 .setStatus("Success")
240 ],
241 components: [
242 new Discord.MessageActionRow().addComponents([
243 new Discord.MessageButton()
244 .setCustomId("download")
245 .setLabel("Download transcript")
246 .setStyle("SUCCESS")
247 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
248 ])
249 ]
250 })) as Discord.Message;
pineafan5d1908e2022-02-28 21:34:47 +0000251 let component;
252 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 component = await m.awaitMessageComponent({
254 filter: (m) => m.user.id === interaction.user.id,
255 time: 300000
256 });
257 } catch {
258 return;
259 }
pineafan5d1908e2022-02-28 21:34:47 +0000260 if (component && component.customId === "download") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100261 interaction.editReply({
262 embeds: [
263 new EmojiEmbed()
264 .setEmoji("CHANNEL.PURGE.GREEN")
265 .setTitle("Purge")
266 .setDescription("Uploaded")
267 .setStatus("Success")
268 ],
269 components: [],
270 files: [attachmentObject]
271 });
pineafan5d1908e2022-02-28 21:34:47 +0000272 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100273 interaction.editReply({
274 embeds: [
275 new EmojiEmbed()
276 .setEmoji("CHANNEL.PURGE.GREEN")
277 .setTitle("Purge")
278 .setDescription("Messages cleared")
279 .setStatus("Success")
280 ],
281 components: []
282 });
pineafan5d1908e2022-02-28 21:34:47 +0000283 }
pineafan63fc5e22022-08-04 22:04:10 +0100284 return;
pineafan8b4b17f2022-02-27 20:42:52 +0000285 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100286 const confirmation = await new confirmationMessage(interaction)
pineafan8b4b17f2022-02-27 20:42:52 +0000287 .setEmoji("CHANNEL.PURGE.RED")
288 .setTitle("Purge")
Skyler Grey75ea9172022-08-06 10:22:23 +0100289 .setDescription(
290 keyValueList({
291 channel: `<#${channel.id}>`,
292 amount: interaction.options.getInteger("amount").toString(),
293 reason: `\n> ${
294 interaction.options.getString("reason")
295 ? interaction.options.getString("reason")
296 : "*No reason provided*"
297 }`
298 })
299 )
pineafan8b4b17f2022-02-27 20:42:52 +0000300 .setColor("Danger")
pineafan63fc5e22022-08-04 22:04:10 +0100301 .send();
302 if (confirmation.cancelled) return;
pineafan377794f2022-04-18 19:01:01 +0100303 if (confirmation.success) {
pineafan5d1908e2022-02-28 21:34:47 +0000304 let messages;
pineafan8b4b17f2022-02-27 20:42:52 +0000305 try {
pineafan377794f2022-04-18 19:01:01 +0100306 if (!user) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100307 const toDelete = await (
308 interaction.channel as TextChannel
309 ).messages.fetch({
310 limit: interaction.options.getInteger("amount")
311 });
312 messages = await (channel as TextChannel).bulkDelete(
313 toDelete,
314 true
315 );
pineafan377794f2022-04-18 19:01:01 +0100316 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100317 const toDelete = (
318 await (
319 await (
320 interaction.channel as TextChannel
321 ).messages.fetch({
322 limit: 100
323 })
324 ).filter((m) => m.author.id === user.id)
325 ).first(interaction.options.getInteger("amount"));
326 messages = await (channel as TextChannel).bulkDelete(
327 toDelete,
328 true
329 );
pineafan377794f2022-04-18 19:01:01 +0100330 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100331 } catch (e) {
332 await interaction.editReply({
333 embeds: [
334 new EmojiEmbed()
335 .setEmoji("CHANNEL.PURGE.RED")
336 .setTitle("Purge")
337 .setDescription(
338 "Something went wrong and no messages were deleted"
339 )
340 .setStatus("Danger")
341 ],
342 components: []
343 });
pineafan8b4b17f2022-02-27 20:42:52 +0000344 }
pineafan4edb7762022-06-26 19:21:04 +0100345 if (user) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100346 await client.database.history.create(
347 "purge",
348 interaction.guild.id,
349 user,
350 interaction.options.getString("reason"),
351 null,
352 null,
353 messages.size
354 );
pineafan4edb7762022-06-26 19:21:04 +0100355 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100356 const { log, NucleusColors, entry, renderUser, renderChannel } =
357 client.logger;
pineafan63fc5e22022-08-04 22:04:10 +0100358 const data = {
359 meta: {
360 type: "channelPurge",
361 displayName: "Channel Purged",
362 calculateType: "messageDelete",
363 color: NucleusColors.red,
364 emoji: "PUNISH.BAN.RED",
365 timestamp: new Date().getTime()
366 },
367 list: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100368 memberId: entry(
369 interaction.user.id,
370 `\`${interaction.user.id}\``
371 ),
372 purgedBy: entry(
373 interaction.user.id,
374 renderUser(interaction.user)
375 ),
376 channel: entry(
377 interaction.channel.id,
378 renderChannel(interaction.channel)
379 ),
pineafan63fc5e22022-08-04 22:04:10 +0100380 messagesCleared: entry(messages.size, messages.size)
381 },
382 hidden: {
383 guild: interaction.guild.id
pineafane625d782022-05-09 18:04:32 +0100384 }
pineafan63fc5e22022-08-04 22:04:10 +0100385 };
386 log(data);
387 let out = "";
Skyler Grey75ea9172022-08-06 10:22:23 +0100388 messages.reverse().forEach((message) => {
389 out += `${message.author.username}#${
390 message.author.discriminator
391 } (${message.author.id}) [${new Date(
392 message.createdTimestamp
393 ).toISOString()}]\n`;
pineafan63fc5e22022-08-04 22:04:10 +0100394 const lines = message.content.split("\n");
Skyler Grey75ea9172022-08-06 10:22:23 +0100395 lines.forEach((line) => {
396 out += `> ${line}\n`;
397 });
pineafan63fc5e22022-08-04 22:04:10 +0100398 out += "\n\n";
399 });
400 const attachmentObject = {
401 attachment: Buffer.from(out),
402 name: `purge-${channel.id}-${Date.now()}.txt`,
403 description: "Purge log"
404 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100405 const m = (await interaction.editReply({
406 embeds: [
407 new EmojiEmbed()
408 .setEmoji("CHANNEL.PURGE.GREEN")
409 .setTitle("Purge")
410 .setDescription("Messages cleared")
411 .setStatus("Success")
412 ],
413 components: [
414 new Discord.MessageActionRow().addComponents([
415 new Discord.MessageButton()
416 .setCustomId("download")
417 .setLabel("Download transcript")
418 .setStyle("SUCCESS")
419 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
420 ])
421 ]
422 })) as Discord.Message;
pineafan5d1908e2022-02-28 21:34:47 +0000423 let component;
424 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100425 component = await m.awaitMessageComponent({
426 filter: (m) => m.user.id === interaction.user.id,
427 time: 300000
428 });
429 } catch {
430 return;
431 }
pineafan5d1908e2022-02-28 21:34:47 +0000432 if (component && component.customId === "download") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100433 interaction.editReply({
434 embeds: [
435 new EmojiEmbed()
436 .setEmoji("CHANNEL.PURGE.GREEN")
437 .setTitle("Purge")
438 .setDescription("Transcript uploaded above")
439 .setStatus("Success")
440 ],
441 components: [],
442 files: [attachmentObject]
443 });
pineafan5d1908e2022-02-28 21:34:47 +0000444 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100445 interaction.editReply({
446 embeds: [
447 new EmojiEmbed()
448 .setEmoji("CHANNEL.PURGE.GREEN")
449 .setTitle("Purge")
450 .setDescription("Messages cleared")
451 .setStatus("Success")
452 ],
453 components: []
454 });
pineafan5d1908e2022-02-28 21:34:47 +0000455 }
pineafan8b4b17f2022-02-27 20:42:52 +0000456 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100457 await interaction.editReply({
458 embeds: [
459 new EmojiEmbed()
460 .setEmoji("CHANNEL.PURGE.GREEN")
461 .setTitle("Purge")
462 .setDescription("No changes were made")
463 .setStatus("Success")
464 ],
465 components: []
466 });
pineafan8b4b17f2022-02-27 20:42:52 +0000467 }
468 }
pineafan63fc5e22022-08-04 22:04:10 +0100469};
pineafan4f164f32022-02-26 22:07:12 +0000470
pineafanbd02b4a2022-08-05 22:01:38 +0100471const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100472 const member = interaction.member as GuildMember;
473 const me = interaction.guild.me!;
pineafanc1c18792022-08-03 21:41:36 +0100474 // Check if nucleus has the manage_messages permission
Skyler Grey75ea9172022-08-06 10:22:23 +0100475 if (!me.permissions.has("MANAGE_MESSAGES"))
476 throw "I do not have the *Manage Messages* permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000477 // Allow the owner to purge
pineafan63fc5e22022-08-04 22:04:10 +0100478 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000479 // Check if the user has manage_messages permission
Skyler Grey75ea9172022-08-06 10:22:23 +0100480 if (!member.permissions.has("MANAGE_MESSAGES"))
481 throw "You do not have the *Manage Messages* permission";
pineafanc1c18792022-08-03 21:41:36 +0100482 // Allow purge
pineafan63fc5e22022-08-04 22:04:10 +0100483 return true;
484};
pineafan4f164f32022-02-26 22:07:12 +0000485
Skyler Grey75ea9172022-08-06 10:22:23 +0100486export { command, callback, check };