blob: 13e688bb5700a0257c08e638eb88a2dcdf577f1b [file] [log] [blame]
pineafan8b4b17f2022-02-27 20:42:52 +00001import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel } from "discord.js";
pineafan4f164f32022-02-26 22:07:12 +00002import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
3import { WrappedCheck } from "jshaiku";
pineafan8b4b17f2022-02-27 20:42:52 +00004import confirmationMessage from "../../utils/confirmationMessage.js";
pineafan4edb7762022-06-26 19:21:04 +01005import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +00006import keyValueList from "../../utils/generateKeyValueList.js";
7import getEmojiByName from "../../utils/getEmojiByName.js";
pineafan4edb7762022-06-26 19:21:04 +01008import client from "../../utils/client.js";
pineafan4f164f32022-02-26 22:07:12 +00009
10const command = (builder: SlashCommandSubcommandBuilder) =>
11 builder
pineafan63fc5e22022-08-04 22:04:10 +010012 .setName("purge")
13 .setDescription("Bulk deletes messages in a channel")
14 .addIntegerOption(option => option
15 .setName("amount")
16 .setDescription("The amount of messages to delete")
17 .setRequired(false)
18 .setMinValue(1)
19 .setMaxValue(100))
20 .addUserOption(option => option.setName("user").setDescription("The user to purge messages from").setRequired(false))
21 .addStringOption(option => option.setName("reason").setDescription("The reason for the purge").setRequired(false));
pineafan4f164f32022-02-26 22:07:12 +000022
pineafan6702cef2022-06-13 17:52:37 +010023const callback = async (interaction: CommandInteraction): Promise<any> => {
pineafan63fc5e22022-08-04 22:04:10 +010024 const user = interaction.options.getMember("user") as GuildMember ?? null;
25 const channel = (interaction.channel as GuildChannel);
pineafan8b4b17f2022-02-27 20:42:52 +000026 if (!(["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(channel.type.toString()))) {
27 return await interaction.reply({
28 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010029 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000030 .setEmoji("CHANNEL.PURGE.RED")
31 .setTitle("Purge")
32 .setDescription("You cannot purge this channel")
33 .setStatus("Danger")
34 ],
35 components: [],
pineafan63fc5e22022-08-04 22:04:10 +010036 ephemeral: true
37 });
pineafan8b4b17f2022-02-27 20:42:52 +000038 }
39 // TODO:[Modals] Replace this with a modal
40 if ( !interaction.options.getInteger("amount") ) {
41 await interaction.reply({
42 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010043 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000044 .setEmoji("CHANNEL.PURGE.RED")
45 .setTitle("Purge")
46 .setDescription("Select how many messages to delete")
47 .setStatus("Danger")
48 ],
49 components: [],
50 ephemeral: true,
51 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +010052 });
53 let deleted = [] as Discord.Message[];
pineafan8b4b17f2022-02-27 20:42:52 +000054 while (true) {
pineafan63fc5e22022-08-04 22:04:10 +010055 const m = await interaction.editReply({
pineafan8b4b17f2022-02-27 20:42:52 +000056 embeds: [
pineafan4edb7762022-06-26 19:21:04 +010057 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000058 .setEmoji("CHANNEL.PURGE.RED")
59 .setTitle("Purge")
60 .setDescription("Select how many messages to delete. You can continue clicking until all messages are cleared.")
61 .setStatus("Danger")
62 ],
63 components: [
64 new Discord.MessageActionRow().addComponents([
65 new Discord.MessageButton()
66 .setCustomId("1")
67 .setLabel("1")
68 .setStyle("SECONDARY"),
69 new Discord.MessageButton()
70 .setCustomId("3")
71 .setLabel("3")
72 .setStyle("SECONDARY"),
73 new Discord.MessageButton()
74 .setCustomId("5")
75 .setLabel("5")
76 .setStyle("SECONDARY")
pineafan63fc5e22022-08-04 22:04:10 +010077 ]),
78 new Discord.MessageActionRow().addComponents([
pineafan8b4b17f2022-02-27 20:42:52 +000079 new Discord.MessageButton()
80 .setCustomId("10")
81 .setLabel("10")
82 .setStyle("SECONDARY"),
83 new Discord.MessageButton()
84 .setCustomId("25")
85 .setLabel("25")
86 .setStyle("SECONDARY"),
87 new Discord.MessageButton()
88 .setCustomId("50")
89 .setLabel("50")
90 .setStyle("SECONDARY")
91 ]),
92 new Discord.MessageActionRow().addComponents([
93 new Discord.MessageButton()
94 .setCustomId("done")
95 .setLabel("Done")
96 .setStyle("SUCCESS")
97 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
98 ])
99 ]
pineafan63fc5e22022-08-04 22:04:10 +0100100 });
pineafan8b4b17f2022-02-27 20:42:52 +0000101 let component;
102 try {
pineafanc6158ab2022-06-17 16:34:07 +0100103 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 300000});
pineafan8b4b17f2022-02-27 20:42:52 +0000104 } catch (e) { break; }
105 component.deferUpdate();
106 if (component.customId === "done") break;
107 let amount;
108 try { amount = parseInt(component.customId); } catch { break; }
pineafan5d1908e2022-02-28 21:34:47 +0000109 let messages;
pineafane625d782022-05-09 18:04:32 +0100110 await (interaction.channel as TextChannel).messages.fetch({limit: amount}).then(async (ms) => {
pineafan5d1908e2022-02-28 21:34:47 +0000111 if (user) {
pineafan63fc5e22022-08-04 22:04:10 +0100112 ms = ms.filter(m => m.author.id === user.id);
pineafan5d1908e2022-02-28 21:34:47 +0000113 }
114 messages = await (channel as TextChannel).bulkDelete(ms, true);
pineafan63fc5e22022-08-04 22:04:10 +0100115 });
pineafane625d782022-05-09 18:04:32 +0100116 if (messages) {
pineafan63fc5e22022-08-04 22:04:10 +0100117 deleted = deleted.concat(messages.map(m => m));
pineafane625d782022-05-09 18:04:32 +0100118 }
pineafan8b4b17f2022-02-27 20:42:52 +0000119 }
120 if (deleted.length === 0) return await interaction.editReply({
121 embeds: [
pineafan4edb7762022-06-26 19:21:04 +0100122 new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000123 .setEmoji("CHANNEL.PURGE.RED")
124 .setTitle("Purge")
125 .setDescription("No messages were deleted")
126 .setStatus("Danger")
127 ],
128 components: []
pineafan63fc5e22022-08-04 22:04:10 +0100129 });
pineafan4edb7762022-06-26 19:21:04 +0100130 if (user) {
pineafan63fc5e22022-08-04 22:04:10 +0100131 await client.database.history.create("purge", interaction.guild.id, user, interaction.options.getString("reason"), null, null, deleted.length);
pineafan4edb7762022-06-26 19:21:04 +0100132 }
pineafan63fc5e22022-08-04 22:04:10 +0100133 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
134 const data = {
135 meta: {
136 type: "channelPurge",
137 displayName: "Channel Purged",
138 calculateType: "messageDelete",
139 color: NucleusColors.red,
140 emoji: "PUNISH.BAN.RED",
141 timestamp: new Date().getTime()
142 },
143 list: {
144 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
145 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
146 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
147 messagesCleared: entry(deleted.length, deleted.length)
148 },
149 hidden: {
150 guild: interaction.guild.id
pineafane625d782022-05-09 18:04:32 +0100151 }
pineafan63fc5e22022-08-04 22:04:10 +0100152 };
153 log(data);
154 let out = "";
155 deleted.reverse().forEach(message => {
156 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`;
157 const lines = message.content.split("\n");
158 lines.forEach(line => {out += `> ${line}\n`;});
159 out += "\n\n";
160 });
161 const attachmentObject = {
162 attachment: Buffer.from(out),
163 name: `purge-${channel.id}-${Date.now()}.txt`,
164 description: "Purge log"
165 };
166 const m = await interaction.editReply({embeds: [new EmojiEmbed()
167 .setEmoji("CHANNEL.PURGE.GREEN")
168 .setTitle("Purge")
pineafan5d1908e2022-02-28 21:34:47 +0000169 .setDescription("Messages cleared")
170 .setStatus("Success")
171 ], components: [new Discord.MessageActionRow().addComponents([
172 new Discord.MessageButton()
173 .setCustomId("download")
174 .setLabel("Download transcript")
175 .setStyle("SUCCESS")
176 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100177 ])]});
pineafan5d1908e2022-02-28 21:34:47 +0000178 let component;
179 try {
pineafanc6158ab2022-06-17 16:34:07 +0100180 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 300000});
pineafan63fc5e22022-08-04 22:04:10 +0100181 } catch { return; }
pineafan5d1908e2022-02-28 21:34:47 +0000182 if (component && component.customId === "download") {
pineafan4edb7762022-06-26 19:21:04 +0100183 interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000184 .setEmoji("CHANNEL.PURGE.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +0100185 .setTitle("Purge")
pineafan5d1908e2022-02-28 21:34:47 +0000186 .setDescription("Uploaded")
187 .setStatus("Success")
pineafan63fc5e22022-08-04 22:04:10 +0100188 ], components: [], files: [attachmentObject]});
pineafan5d1908e2022-02-28 21:34:47 +0000189 } else {
pineafan4edb7762022-06-26 19:21:04 +0100190 interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000191 .setEmoji("CHANNEL.PURGE.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +0100192 .setTitle("Purge")
pineafan5d1908e2022-02-28 21:34:47 +0000193 .setDescription("Messages cleared")
194 .setStatus("Success")
pineafan63fc5e22022-08-04 22:04:10 +0100195 ], components: []});
pineafan5d1908e2022-02-28 21:34:47 +0000196 }
pineafan63fc5e22022-08-04 22:04:10 +0100197 return;
pineafan8b4b17f2022-02-27 20:42:52 +0000198 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100199 const confirmation = await new confirmationMessage(interaction)
pineafan8b4b17f2022-02-27 20:42:52 +0000200 .setEmoji("CHANNEL.PURGE.RED")
201 .setTitle("Purge")
202 .setDescription(keyValueList({
pineafane625d782022-05-09 18:04:32 +0100203 "channel": `<#${channel.id}>`,
pineafan8b4b17f2022-02-27 20:42:52 +0000204 "amount": interaction.options.getInteger("amount").toString(),
205 "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
206 }))
207 .setColor("Danger")
pineafan63fc5e22022-08-04 22:04:10 +0100208 .send();
209 if (confirmation.cancelled) return;
pineafan377794f2022-04-18 19:01:01 +0100210 if (confirmation.success) {
pineafan5d1908e2022-02-28 21:34:47 +0000211 let messages;
pineafan8b4b17f2022-02-27 20:42:52 +0000212 try {
pineafan377794f2022-04-18 19:01:01 +0100213 if (!user) {
pineafan63fc5e22022-08-04 22:04:10 +0100214 const toDelete = await (interaction.channel as TextChannel).messages.fetch({limit: interaction.options.getInteger("amount")});
pineafan377794f2022-04-18 19:01:01 +0100215 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
216 } else {
pineafan63fc5e22022-08-04 22:04:10 +0100217 const toDelete = (await (await (interaction.channel as TextChannel).messages.fetch({limit: 100}))
218 .filter(m => m.author.id === user.id)).first(interaction.options.getInteger("amount"));
pineafan377794f2022-04-18 19:01:01 +0100219 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
220 }
pineafan5d1908e2022-02-28 21:34:47 +0000221 } catch(e) {
pineafan4edb7762022-06-26 19:21:04 +0100222 await interaction.editReply({embeds: [new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000223 .setEmoji("CHANNEL.PURGE.RED")
pineafan63fc5e22022-08-04 22:04:10 +0100224 .setTitle("Purge")
pineafan8b4b17f2022-02-27 20:42:52 +0000225 .setDescription("Something went wrong and no messages were deleted")
226 .setStatus("Danger")
pineafan63fc5e22022-08-04 22:04:10 +0100227 ], components: []});
pineafan8b4b17f2022-02-27 20:42:52 +0000228 }
pineafan4edb7762022-06-26 19:21:04 +0100229 if (user) {
pineafan63fc5e22022-08-04 22:04:10 +0100230 await client.database.history.create("purge", interaction.guild.id, user, interaction.options.getString("reason"), null, null, messages.size);
pineafan4edb7762022-06-26 19:21:04 +0100231 }
pineafan63fc5e22022-08-04 22:04:10 +0100232 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
233 const data = {
234 meta: {
235 type: "channelPurge",
236 displayName: "Channel Purged",
237 calculateType: "messageDelete",
238 color: NucleusColors.red,
239 emoji: "PUNISH.BAN.RED",
240 timestamp: new Date().getTime()
241 },
242 list: {
243 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
244 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
245 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
246 messagesCleared: entry(messages.size, messages.size)
247 },
248 hidden: {
249 guild: interaction.guild.id
pineafane625d782022-05-09 18:04:32 +0100250 }
pineafan63fc5e22022-08-04 22:04:10 +0100251 };
252 log(data);
253 let out = "";
254 messages.reverse().forEach(message => {
255 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`;
256 const lines = message.content.split("\n");
257 lines.forEach(line => {out += `> ${line}\n`;});
258 out += "\n\n";
259 });
260 const attachmentObject = {
261 attachment: Buffer.from(out),
262 name: `purge-${channel.id}-${Date.now()}.txt`,
263 description: "Purge log"
264 };
265 const m = await interaction.editReply({embeds: [new EmojiEmbed()
266 .setEmoji("CHANNEL.PURGE.GREEN")
267 .setTitle("Purge")
pineafan5d1908e2022-02-28 21:34:47 +0000268 .setDescription("Messages cleared")
269 .setStatus("Success")
270 ], components: [new Discord.MessageActionRow().addComponents([
271 new Discord.MessageButton()
272 .setCustomId("download")
273 .setLabel("Download transcript")
274 .setStyle("SUCCESS")
275 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100276 ])]});
pineafan5d1908e2022-02-28 21:34:47 +0000277 let component;
278 try {
pineafanc6158ab2022-06-17 16:34:07 +0100279 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 300000});
pineafan63fc5e22022-08-04 22:04:10 +0100280 } catch { return; }
pineafan5d1908e2022-02-28 21:34:47 +0000281 if (component && component.customId === "download") {
pineafan4edb7762022-06-26 19:21:04 +0100282 interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000283 .setEmoji("CHANNEL.PURGE.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +0100284 .setTitle("Purge")
pineafan377794f2022-04-18 19:01:01 +0100285 .setDescription("Transcript uploaded above")
pineafan5d1908e2022-02-28 21:34:47 +0000286 .setStatus("Success")
pineafan63fc5e22022-08-04 22:04:10 +0100287 ], components: [], files: [attachmentObject]});
pineafan5d1908e2022-02-28 21:34:47 +0000288 } else {
pineafan4edb7762022-06-26 19:21:04 +0100289 interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000290 .setEmoji("CHANNEL.PURGE.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +0100291 .setTitle("Purge")
pineafan5d1908e2022-02-28 21:34:47 +0000292 .setDescription("Messages cleared")
293 .setStatus("Success")
pineafan63fc5e22022-08-04 22:04:10 +0100294 ], components: []});
pineafan5d1908e2022-02-28 21:34:47 +0000295 }
pineafan8b4b17f2022-02-27 20:42:52 +0000296 } else {
pineafan4edb7762022-06-26 19:21:04 +0100297 await interaction.editReply({embeds: [new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000298 .setEmoji("CHANNEL.PURGE.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +0100299 .setTitle("Purge")
pineafan8b4b17f2022-02-27 20:42:52 +0000300 .setDescription("No changes were made")
301 .setStatus("Success")
pineafan63fc5e22022-08-04 22:04:10 +0100302 ], components: []});
pineafan8b4b17f2022-02-27 20:42:52 +0000303 }
304 }
pineafan63fc5e22022-08-04 22:04:10 +0100305};
pineafan4f164f32022-02-26 22:07:12 +0000306
307const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
pineafan63fc5e22022-08-04 22:04:10 +0100308 const member = (interaction.member as GuildMember);
309 const me = (interaction.guild.me as GuildMember);
pineafanc1c18792022-08-03 21:41:36 +0100310 // Check if nucleus has the manage_messages permission
311 if (! me.permissions.has("MANAGE_MESSAGES")) throw "I do not have the *Manage Messages* permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000312 // Allow the owner to purge
pineafan63fc5e22022-08-04 22:04:10 +0100313 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000314 // Check if the user has manage_messages permission
pineafane23c4ec2022-07-27 21:56:27 +0100315 if (! member.permissions.has("MANAGE_MESSAGES")) throw "You do not have the *Manage Messages* permission";
pineafanc1c18792022-08-03 21:41:36 +0100316 // Allow purge
pineafan63fc5e22022-08-04 22:04:10 +0100317 return true;
318};
pineafan4f164f32022-02-26 22:07:12 +0000319
pineafan8b4b17f2022-02-27 20:42:52 +0000320export { command, callback, check };