blob: fd8e6b8cf3d6b010f7fbaef668ba1e2d46af7c62 [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
12 .setName("purge")
pineafan8b4b17f2022-02-27 20:42:52 +000013 .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)
pineafan1dc15722022-03-14 21:27:34 +000019 .setMaxValue(100))
pineafan8b4b17f2022-02-27 20:42:52 +000020 .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> => {
pineafan5d1908e2022-02-28 21:34:47 +000024 let user = interaction.options.getMember("user") as GuildMember ?? null
pineafan377794f2022-04-18 19:01:01 +010025 let 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: [],
36 ephemeral: true,
37 })
38 }
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
52 })
pineafan5d1908e2022-02-28 21:34:47 +000053 let deleted = [] as Discord.Message[]
pineafan8b4b17f2022-02-27 20:42:52 +000054 while (true) {
55 let m = await interaction.editReply({
56 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")
77 ]),
78 new Discord.MessageActionRow().addComponents([
79 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 ]
100 })
101 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) {
112 ms = ms.filter(m => m.author.id === user.id)
113 }
114 messages = await (channel as TextChannel).bulkDelete(ms, true);
115 })
pineafane625d782022-05-09 18:04:32 +0100116 if (messages) {
117 deleted = deleted.concat(messages.map(m => m))
118 }
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: []
129 })
pineafan4edb7762022-06-26 19:21:04 +0100130 if (user) {
131 try { await client.database.history.create("purge", interaction.guild.id, user, interaction.options.getString("reason"), null, null, deleted.length) } catch {}
132 }
pineafan5d1908e2022-02-28 21:34:47 +0000133 let attachmentObject;
134 try {
pineafane625d782022-05-09 18:04:32 +0100135 // @ts-ignore
pineafan4edb7762022-06-26 19:21:04 +0100136 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger
pineafane625d782022-05-09 18:04:32 +0100137 let data = {
138 meta: {
139 type: 'channelPurge',
140 displayName: 'Channel Purged',
141 calculateType: 'messageDelete',
142 color: NucleusColors.red,
143 emoji: "PUNISH.BAN.RED",
144 timestamp: new Date().getTime()
145 },
146 list: {
pineafanda6e5342022-07-03 10:03:16 +0100147 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
pineafane625d782022-05-09 18:04:32 +0100148 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
149 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
150 messagesCleared: entry(deleted.length, deleted.length),
151 },
152 hidden: {
153 guild: interaction.guild.id
154 }
155 }
pineafan4edb7762022-06-26 19:21:04 +0100156 log(data);
pineafan5d1908e2022-02-28 21:34:47 +0000157 let out = ""
158 deleted.reverse().forEach(message => {
159 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`
160 let lines = message.content.split("\n")
161 lines.forEach(line => {out += `> ${line}\n`})
162 out += `\n\n`
163 })
164 attachmentObject = {
165 attachment: Buffer.from(out),
166 name: `purge-${channel.id}-${Date.now()}.txt`,
167 description: "Purge log"
168 }
169 } catch {}
pineafan4edb7762022-06-26 19:21:04 +0100170 let m = await interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000171 .setEmoji(`CHANNEL.PURGE.GREEN`)
172 .setTitle(`Purge`)
173 .setDescription("Messages cleared")
174 .setStatus("Success")
175 ], components: [new Discord.MessageActionRow().addComponents([
176 new Discord.MessageButton()
177 .setCustomId("download")
178 .setLabel("Download transcript")
179 .setStyle("SUCCESS")
180 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
181 ])]})
182 let component;
183 try {
pineafanc6158ab2022-06-17 16:34:07 +0100184 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 300000});
pineafan5d1908e2022-02-28 21:34:47 +0000185 } catch {}
186 if (component && component.customId === "download") {
pineafan4edb7762022-06-26 19:21:04 +0100187 interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000188 .setEmoji("CHANNEL.PURGE.GREEN")
189 .setTitle(`Purge`)
190 .setDescription("Uploaded")
191 .setStatus("Success")
192 ], components: [], files: [attachmentObject]})
193 } else {
pineafan4edb7762022-06-26 19:21:04 +0100194 interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000195 .setEmoji("CHANNEL.PURGE.GREEN")
196 .setTitle(`Purge`)
197 .setDescription("Messages cleared")
198 .setStatus("Success")
199 ], components: []})
200 }
201 return
pineafan8b4b17f2022-02-27 20:42:52 +0000202 } else {
pineafan377794f2022-04-18 19:01:01 +0100203 let confirmation = await new confirmationMessage(interaction)
pineafan8b4b17f2022-02-27 20:42:52 +0000204 .setEmoji("CHANNEL.PURGE.RED")
205 .setTitle("Purge")
206 .setDescription(keyValueList({
pineafane625d782022-05-09 18:04:32 +0100207 "channel": `<#${channel.id}>`,
pineafan8b4b17f2022-02-27 20:42:52 +0000208 "amount": interaction.options.getInteger("amount").toString(),
209 "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
210 }))
211 .setColor("Danger")
pineafan377794f2022-04-18 19:01:01 +0100212 .send()
213 if (confirmation.success) {
pineafan5d1908e2022-02-28 21:34:47 +0000214 let messages;
pineafan8b4b17f2022-02-27 20:42:52 +0000215 try {
pineafan377794f2022-04-18 19:01:01 +0100216 if (!user) {
217 let toDelete = await (interaction.channel as TextChannel).messages.fetch({limit: interaction.options.getInteger("amount")})
218 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
219 } else {
220 let toDelete = (await (await (interaction.channel as TextChannel).messages.fetch({limit: 100}))
221 .filter(m => m.author.id === user.id)).first(interaction.options.getInteger("amount"))
222 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
223 }
pineafan5d1908e2022-02-28 21:34:47 +0000224 } catch(e) {
pineafan4edb7762022-06-26 19:21:04 +0100225 await interaction.editReply({embeds: [new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000226 .setEmoji("CHANNEL.PURGE.RED")
227 .setTitle(`Purge`)
228 .setDescription("Something went wrong and no messages were deleted")
229 .setStatus("Danger")
230 ], components: []})
231 }
pineafan4edb7762022-06-26 19:21:04 +0100232 if (user) {
233 try { await client.database.history.create("purge", interaction.guild.id, user, interaction.options.getString("reason"), null, null, messages.size) } catch {}
234 }
pineafan5d1908e2022-02-28 21:34:47 +0000235 let attachmentObject;
236 try {
pineafane625d782022-05-09 18:04:32 +0100237 // @ts-ignore
pineafan4edb7762022-06-26 19:21:04 +0100238 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger
pineafane625d782022-05-09 18:04:32 +0100239 let data = {
240 meta: {
241 type: 'channelPurge',
242 displayName: 'Channel Purged',
243 calculateType: 'messageDelete',
244 color: NucleusColors.red,
245 emoji: "PUNISH.BAN.RED",
246 timestamp: new Date().getTime()
247 },
248 list: {
pineafanda6e5342022-07-03 10:03:16 +0100249 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
pineafane625d782022-05-09 18:04:32 +0100250 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
251 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
252 messagesCleared: entry(messages.size, messages.size),
253 },
254 hidden: {
255 guild: interaction.guild.id
256 }
257 }
pineafan4edb7762022-06-26 19:21:04 +0100258 log(data);
pineafan5d1908e2022-02-28 21:34:47 +0000259 let out = ""
260 messages.reverse().forEach(message => {
261 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`
262 let lines = message.content.split("\n")
263 lines.forEach(line => {out += `> ${line}\n`})
264 out += `\n\n`
265 })
266 attachmentObject = {
267 attachment: Buffer.from(out),
268 name: `purge-${channel.id}-${Date.now()}.txt`,
269 description: `Purge log`
270 }
271 } catch {}
pineafan4edb7762022-06-26 19:21:04 +0100272 let m = await interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000273 .setEmoji(`CHANNEL.PURGE.GREEN`)
274 .setTitle(`Purge`)
275 .setDescription("Messages cleared")
276 .setStatus("Success")
277 ], components: [new Discord.MessageActionRow().addComponents([
278 new Discord.MessageButton()
279 .setCustomId("download")
280 .setLabel("Download transcript")
281 .setStyle("SUCCESS")
282 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
283 ])]})
284 let component;
285 try {
pineafanc6158ab2022-06-17 16:34:07 +0100286 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 300000});
pineafan5d1908e2022-02-28 21:34:47 +0000287 } catch {}
288 if (component && component.customId === "download") {
pineafan4edb7762022-06-26 19:21:04 +0100289 interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000290 .setEmoji("CHANNEL.PURGE.GREEN")
291 .setTitle(`Purge`)
pineafan377794f2022-04-18 19:01:01 +0100292 .setDescription("Transcript uploaded above")
pineafan5d1908e2022-02-28 21:34:47 +0000293 .setStatus("Success")
294 ], components: [], files: [attachmentObject]})
295 } else {
pineafan4edb7762022-06-26 19:21:04 +0100296 interaction.editReply({embeds: [new EmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000297 .setEmoji("CHANNEL.PURGE.GREEN")
298 .setTitle(`Purge`)
299 .setDescription("Messages cleared")
300 .setStatus("Success")
301 ], components: []})
302 }
pineafan8b4b17f2022-02-27 20:42:52 +0000303 } else {
pineafan4edb7762022-06-26 19:21:04 +0100304 await interaction.editReply({embeds: [new EmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000305 .setEmoji("CHANNEL.PURGE.GREEN")
306 .setTitle(`Purge`)
307 .setDescription("No changes were made")
308 .setStatus("Success")
309 ], components: []})
310 }
311 }
pineafan4f164f32022-02-26 22:07:12 +0000312}
313
314const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
pineafan663dc472022-05-10 18:13:47 +0100315 let member = (interaction.member as GuildMember)
PineappleFan5fe720d2022-05-19 12:01:49 +0100316 let me = (interaction.guild.me as GuildMember)
pineafan8b4b17f2022-02-27 20:42:52 +0000317 // Allow the owner to purge
pineafan663dc472022-05-10 18:13:47 +0100318 if (member.id == interaction.guild.ownerId) return true
pineafan8b4b17f2022-02-27 20:42:52 +0000319 // Check if the user has manage_messages permission
pineafan4edb7762022-06-26 19:21:04 +0100320 if (! member.permissions.has("MANAGE_MESSAGES")) throw "You do not have the Manage messages permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000321 // Check if nucleus has the manage_messages permission
pineafan4edb7762022-06-26 19:21:04 +0100322 if (! me.permissions.has("MANAGE_MESSAGES")) throw "I do not have the Manage messages permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000323 // Allow warn
324 return true
pineafan4f164f32022-02-26 22:07:12 +0000325}
326
pineafan8b4b17f2022-02-27 20:42:52 +0000327export { command, callback, check };