blob: 924e5075cd73f67bb1ac8540b9952bf9a6995071 [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";
pineafan377794f2022-04-18 19:01:01 +01005import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +00006import keyValueList from "../../utils/generateKeyValueList.js";
7import getEmojiByName from "../../utils/getEmojiByName.js";
pineafan4f164f32022-02-26 22:07:12 +00008
9const command = (builder: SlashCommandSubcommandBuilder) =>
10 builder
11 .setName("purge")
pineafan8b4b17f2022-02-27 20:42:52 +000012 .setDescription("Bulk deletes messages in a channel")
13 .addIntegerOption(option => option
14 .setName("amount")
15 .setDescription("The amount of messages to delete")
16 .setRequired(false)
17 .setMinValue(1)
pineafan1dc15722022-03-14 21:27:34 +000018 .setMaxValue(100))
pineafan8b4b17f2022-02-27 20:42:52 +000019 .addUserOption(option => option.setName("user").setDescription("The user to purge messages from").setRequired(false))
20 .addStringOption(option => option.setName("reason").setDescription("The reason for the purge").setRequired(false))
pineafan4f164f32022-02-26 22:07:12 +000021
pineafan8b4b17f2022-02-27 20:42:52 +000022const callback = async (interaction: CommandInteraction) => {
pineafan5d1908e2022-02-28 21:34:47 +000023 let user = interaction.options.getMember("user") as GuildMember ?? null
pineafan377794f2022-04-18 19:01:01 +010024 let channel = (interaction.channel as GuildChannel)
pineafan8b4b17f2022-02-27 20:42:52 +000025 if (!(["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(channel.type.toString()))) {
26 return await interaction.reply({
27 embeds: [
pineafan377794f2022-04-18 19:01:01 +010028 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000029 .setEmoji("CHANNEL.PURGE.RED")
30 .setTitle("Purge")
31 .setDescription("You cannot purge this channel")
32 .setStatus("Danger")
33 ],
34 components: [],
35 ephemeral: true,
36 })
37 }
38 // TODO:[Modals] Replace this with a modal
39 if ( !interaction.options.getInteger("amount") ) {
40 await interaction.reply({
41 embeds: [
pineafan377794f2022-04-18 19:01:01 +010042 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000043 .setEmoji("CHANNEL.PURGE.RED")
44 .setTitle("Purge")
45 .setDescription("Select how many messages to delete")
46 .setStatus("Danger")
47 ],
48 components: [],
49 ephemeral: true,
50 fetchReply: true
51 })
pineafan5d1908e2022-02-28 21:34:47 +000052 let deleted = [] as Discord.Message[]
pineafan8b4b17f2022-02-27 20:42:52 +000053 while (true) {
54 let m = await interaction.editReply({
55 embeds: [
pineafan377794f2022-04-18 19:01:01 +010056 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000057 .setEmoji("CHANNEL.PURGE.RED")
58 .setTitle("Purge")
59 .setDescription("Select how many messages to delete. You can continue clicking until all messages are cleared.")
60 .setStatus("Danger")
61 ],
62 components: [
63 new Discord.MessageActionRow().addComponents([
64 new Discord.MessageButton()
65 .setCustomId("1")
66 .setLabel("1")
67 .setStyle("SECONDARY"),
68 new Discord.MessageButton()
69 .setCustomId("3")
70 .setLabel("3")
71 .setStyle("SECONDARY"),
72 new Discord.MessageButton()
73 .setCustomId("5")
74 .setLabel("5")
75 .setStyle("SECONDARY")
76 ]),
77 new Discord.MessageActionRow().addComponents([
78 new Discord.MessageButton()
79 .setCustomId("10")
80 .setLabel("10")
81 .setStyle("SECONDARY"),
82 new Discord.MessageButton()
83 .setCustomId("25")
84 .setLabel("25")
85 .setStyle("SECONDARY"),
86 new Discord.MessageButton()
87 .setCustomId("50")
88 .setLabel("50")
89 .setStyle("SECONDARY")
90 ]),
91 new Discord.MessageActionRow().addComponents([
92 new Discord.MessageButton()
93 .setCustomId("done")
94 .setLabel("Done")
95 .setStyle("SUCCESS")
96 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
97 ])
98 ]
99 })
100 let component;
101 try {
102 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
103 } catch (e) { break; }
104 component.deferUpdate();
105 if (component.customId === "done") break;
106 let amount;
107 try { amount = parseInt(component.customId); } catch { break; }
pineafan5d1908e2022-02-28 21:34:47 +0000108 let messages;
pineafane625d782022-05-09 18:04:32 +0100109 await (interaction.channel as TextChannel).messages.fetch({limit: amount}).then(async (ms) => {
pineafan5d1908e2022-02-28 21:34:47 +0000110 if (user) {
111 ms = ms.filter(m => m.author.id === user.id)
112 }
113 messages = await (channel as TextChannel).bulkDelete(ms, true);
114 })
pineafane625d782022-05-09 18:04:32 +0100115 if (messages) {
116 deleted = deleted.concat(messages.map(m => m))
117 }
pineafan8b4b17f2022-02-27 20:42:52 +0000118 }
119 if (deleted.length === 0) return await interaction.editReply({
120 embeds: [
pineafan377794f2022-04-18 19:01:01 +0100121 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000122 .setEmoji("CHANNEL.PURGE.RED")
123 .setTitle("Purge")
124 .setDescription("No messages were deleted")
125 .setStatus("Danger")
126 ],
127 components: []
128 })
pineafan5d1908e2022-02-28 21:34:47 +0000129 let attachmentObject;
130 try {
pineafane625d782022-05-09 18:04:32 +0100131 // @ts-ignore
132 const { log, NucleusColors, entry, renderUser, renderChannel } = interaction.user.client.logger
133 let data = {
134 meta: {
135 type: 'channelPurge',
136 displayName: 'Channel Purged',
137 calculateType: 'messageDelete',
138 color: NucleusColors.red,
139 emoji: "PUNISH.BAN.RED",
140 timestamp: new Date().getTime()
141 },
142 list: {
143 id: entry(interaction.user.id, `\`${interaction.user.id}\``),
144 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
145 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
146 messagesCleared: entry(deleted.length, deleted.length),
147 },
148 hidden: {
149 guild: interaction.guild.id
150 }
151 }
152 log(data, interaction.user.client);
pineafan5d1908e2022-02-28 21:34:47 +0000153 let out = ""
154 deleted.reverse().forEach(message => {
155 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`
156 let lines = message.content.split("\n")
157 lines.forEach(line => {out += `> ${line}\n`})
158 out += `\n\n`
159 })
160 attachmentObject = {
161 attachment: Buffer.from(out),
162 name: `purge-${channel.id}-${Date.now()}.txt`,
163 description: "Purge log"
164 }
165 } catch {}
pineafan377794f2022-04-18 19:01:01 +0100166 let m = await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000167 .setEmoji(`CHANNEL.PURGE.GREEN`)
168 .setTitle(`Purge`)
169 .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"))
177 ])]})
178 let component;
179 try {
180 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
181 } catch {}
182 if (component && component.customId === "download") {
pineafan377794f2022-04-18 19:01:01 +0100183 interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000184 .setEmoji("CHANNEL.PURGE.GREEN")
185 .setTitle(`Purge`)
186 .setDescription("Uploaded")
187 .setStatus("Success")
188 ], components: [], files: [attachmentObject]})
189 } else {
pineafan377794f2022-04-18 19:01:01 +0100190 interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000191 .setEmoji("CHANNEL.PURGE.GREEN")
192 .setTitle(`Purge`)
193 .setDescription("Messages cleared")
194 .setStatus("Success")
195 ], components: []})
196 }
197 return
pineafan8b4b17f2022-02-27 20:42:52 +0000198 } else {
pineafan377794f2022-04-18 19:01:01 +0100199 let 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")
208 // pluralize("day", interaction.options.getInteger("amount"))
209 // const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
pineafan377794f2022-04-18 19:01:01 +0100210 .send()
211 if (confirmation.success) {
pineafan5d1908e2022-02-28 21:34:47 +0000212 let messages;
pineafan8b4b17f2022-02-27 20:42:52 +0000213 try {
pineafan377794f2022-04-18 19:01:01 +0100214 if (!user) {
215 let toDelete = await (interaction.channel as TextChannel).messages.fetch({limit: interaction.options.getInteger("amount")})
216 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
217 } else {
218 let toDelete = (await (await (interaction.channel as TextChannel).messages.fetch({limit: 100}))
219 .filter(m => m.author.id === user.id)).first(interaction.options.getInteger("amount"))
220 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
221 }
pineafan5d1908e2022-02-28 21:34:47 +0000222 } catch(e) {
pineafan377794f2022-04-18 19:01:01 +0100223 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000224 .setEmoji("CHANNEL.PURGE.RED")
225 .setTitle(`Purge`)
226 .setDescription("Something went wrong and no messages were deleted")
227 .setStatus("Danger")
228 ], components: []})
229 }
pineafan5d1908e2022-02-28 21:34:47 +0000230 let attachmentObject;
231 try {
pineafane625d782022-05-09 18:04:32 +0100232 // @ts-ignore
233 const { log, NucleusColors, entry, renderUser, renderChannel } = interaction.user.client.logger
234 let data = {
235 meta: {
236 type: 'channelPurge',
237 displayName: 'Channel Purged',
238 calculateType: 'messageDelete',
239 color: NucleusColors.red,
240 emoji: "PUNISH.BAN.RED",
241 timestamp: new Date().getTime()
242 },
243 list: {
244 id: entry(interaction.user.id, `\`${interaction.user.id}\``),
245 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
246 channel: entry(interaction.channel.id, renderChannel(interaction.channel)),
247 messagesCleared: entry(messages.size, messages.size),
248 },
249 hidden: {
250 guild: interaction.guild.id
251 }
252 }
253 log(data, interaction.user.client);
pineafan5d1908e2022-02-28 21:34:47 +0000254 let out = ""
255 messages.reverse().forEach(message => {
256 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`
257 let lines = message.content.split("\n")
258 lines.forEach(line => {out += `> ${line}\n`})
259 out += `\n\n`
260 })
261 attachmentObject = {
262 attachment: Buffer.from(out),
263 name: `purge-${channel.id}-${Date.now()}.txt`,
264 description: `Purge log`
265 }
266 } catch {}
pineafan377794f2022-04-18 19:01:01 +0100267 let m = await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000268 .setEmoji(`CHANNEL.PURGE.GREEN`)
269 .setTitle(`Purge`)
270 .setDescription("Messages cleared")
271 .setStatus("Success")
272 ], components: [new Discord.MessageActionRow().addComponents([
273 new Discord.MessageButton()
274 .setCustomId("download")
275 .setLabel("Download transcript")
276 .setStyle("SUCCESS")
277 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
278 ])]})
279 let component;
280 try {
281 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
282 } catch {}
283 if (component && component.customId === "download") {
pineafan377794f2022-04-18 19:01:01 +0100284 interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000285 .setEmoji("CHANNEL.PURGE.GREEN")
286 .setTitle(`Purge`)
pineafan377794f2022-04-18 19:01:01 +0100287 .setDescription("Transcript uploaded above")
pineafan5d1908e2022-02-28 21:34:47 +0000288 .setStatus("Success")
289 ], components: [], files: [attachmentObject]})
290 } else {
pineafan377794f2022-04-18 19:01:01 +0100291 interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000292 .setEmoji("CHANNEL.PURGE.GREEN")
293 .setTitle(`Purge`)
294 .setDescription("Messages cleared")
295 .setStatus("Success")
296 ], components: []})
297 }
pineafan8b4b17f2022-02-27 20:42:52 +0000298 } else {
pineafan377794f2022-04-18 19:01:01 +0100299 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000300 .setEmoji("CHANNEL.PURGE.GREEN")
301 .setTitle(`Purge`)
302 .setDescription("No changes were made")
303 .setStatus("Success")
304 ], components: []})
305 }
306 }
pineafan4f164f32022-02-26 22:07:12 +0000307}
308
309const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
pineafan663dc472022-05-10 18:13:47 +0100310 let member = (interaction.member as GuildMember)
PineappleFan5fe720d2022-05-19 12:01:49 +0100311 let me = (interaction.guild.me as GuildMember)
pineafan8b4b17f2022-02-27 20:42:52 +0000312 // Allow the owner to purge
pineafan663dc472022-05-10 18:13:47 +0100313 if (member.id == interaction.guild.ownerId) return true
pineafan8b4b17f2022-02-27 20:42:52 +0000314 // Check if the user has manage_messages permission
pineafan663dc472022-05-10 18:13:47 +0100315 if (! member.permissions.has("MANAGE_MESSAGES")) throw "You do not have the `manage_messages` permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000316 // Check if nucleus has the manage_messages permission
PineappleFan5fe720d2022-05-19 12:01:49 +0100317 if (! me.permissions.has("MANAGE_MESSAGES")) throw "I do not have the `manage_messages` permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000318 // Allow warn
319 return true
pineafan4f164f32022-02-26 22:07:12 +0000320}
321
pineafan8b4b17f2022-02-27 20:42:52 +0000322export { command, callback, check };