blob: 29c228fab0d62f5a9b6a0eedc74f54afd0a6353a [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";
pineafan377794f2022-04-18 19:01:01 +01008import { create, areTicketsEnabled } from "../../automations/createModActionTicket.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
pineafan8b4b17f2022-02-27 20:42:52 +000023const callback = async (interaction: CommandInteraction) => {
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: [
pineafan377794f2022-04-18 19:01:01 +010029 new generateEmojiEmbed()
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: [
pineafan377794f2022-04-18 19:01:01 +010043 new generateEmojiEmbed()
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: [
pineafan377794f2022-04-18 19:01:01 +010057 new generateEmojiEmbed()
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 {
103 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
104 } 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;
110 (interaction.channel as TextChannel).messages.fetch({limit: amount}).then(async (ms) => {
111 if (user) {
112 ms = ms.filter(m => m.author.id === user.id)
113 }
114 messages = await (channel as TextChannel).bulkDelete(ms, true);
115 })
pineafan377794f2022-04-18 19:01:01 +0100116 deleted = deleted.concat(messages.map(m => m))
pineafan8b4b17f2022-02-27 20:42:52 +0000117 }
118 if (deleted.length === 0) return await interaction.editReply({
119 embeds: [
pineafan377794f2022-04-18 19:01:01 +0100120 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000121 .setEmoji("CHANNEL.PURGE.RED")
122 .setTitle("Purge")
123 .setDescription("No messages were deleted")
124 .setStatus("Danger")
125 ],
126 components: []
127 })
pineafan5d1908e2022-02-28 21:34:47 +0000128 let attachmentObject;
129 try {
130 let out = ""
131 deleted.reverse().forEach(message => {
132 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`
133 let lines = message.content.split("\n")
134 lines.forEach(line => {out += `> ${line}\n`})
135 out += `\n\n`
136 })
137 attachmentObject = {
138 attachment: Buffer.from(out),
139 name: `purge-${channel.id}-${Date.now()}.txt`,
140 description: "Purge log"
141 }
142 } catch {}
pineafan377794f2022-04-18 19:01:01 +0100143 let m = await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000144 .setEmoji(`CHANNEL.PURGE.GREEN`)
145 .setTitle(`Purge`)
146 .setDescription("Messages cleared")
147 .setStatus("Success")
148 ], components: [new Discord.MessageActionRow().addComponents([
149 new Discord.MessageButton()
150 .setCustomId("download")
151 .setLabel("Download transcript")
152 .setStyle("SUCCESS")
153 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
154 ])]})
155 let component;
156 try {
157 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
158 } catch {}
159 if (component && component.customId === "download") {
pineafan377794f2022-04-18 19:01:01 +0100160 interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000161 .setEmoji("CHANNEL.PURGE.GREEN")
162 .setTitle(`Purge`)
163 .setDescription("Uploaded")
164 .setStatus("Success")
165 ], components: [], files: [attachmentObject]})
166 } else {
pineafan377794f2022-04-18 19:01:01 +0100167 interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000168 .setEmoji("CHANNEL.PURGE.GREEN")
169 .setTitle(`Purge`)
170 .setDescription("Messages cleared")
171 .setStatus("Success")
172 ], components: []})
173 }
174 return
pineafan8b4b17f2022-02-27 20:42:52 +0000175 } else {
pineafan377794f2022-04-18 19:01:01 +0100176 let confirmation = await new confirmationMessage(interaction)
pineafan8b4b17f2022-02-27 20:42:52 +0000177 .setEmoji("CHANNEL.PURGE.RED")
178 .setTitle("Purge")
179 .setDescription(keyValueList({
pineafan377794f2022-04-18 19:01:01 +0100180 "channel": `<#${channel.id}> (${(channel as GuildChannel).name})` + ("[This channel]"),
pineafan8b4b17f2022-02-27 20:42:52 +0000181 "amount": interaction.options.getInteger("amount").toString(),
182 "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
183 }))
184 .setColor("Danger")
185 // pluralize("day", interaction.options.getInteger("amount"))
186 // const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
pineafan377794f2022-04-18 19:01:01 +0100187 .send()
188 if (confirmation.success) {
pineafan5d1908e2022-02-28 21:34:47 +0000189 let messages;
pineafan8b4b17f2022-02-27 20:42:52 +0000190 try {
pineafan377794f2022-04-18 19:01:01 +0100191 if (!user) {
192 let toDelete = await (interaction.channel as TextChannel).messages.fetch({limit: interaction.options.getInteger("amount")})
193 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
194 } else {
195 let toDelete = (await (await (interaction.channel as TextChannel).messages.fetch({limit: 100}))
196 .filter(m => m.author.id === user.id)).first(interaction.options.getInteger("amount"))
197 messages = await (channel as TextChannel).bulkDelete(toDelete, true);
198 }
pineafan5d1908e2022-02-28 21:34:47 +0000199 } catch(e) {
pineafan377794f2022-04-18 19:01:01 +0100200 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000201 .setEmoji("CHANNEL.PURGE.RED")
202 .setTitle(`Purge`)
203 .setDescription("Something went wrong and no messages were deleted")
204 .setStatus("Danger")
205 ], components: []})
206 }
pineafan5d1908e2022-02-28 21:34:47 +0000207 let attachmentObject;
208 try {
209 let out = ""
210 messages.reverse().forEach(message => {
211 out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n`
212 let lines = message.content.split("\n")
213 lines.forEach(line => {out += `> ${line}\n`})
214 out += `\n\n`
215 })
216 attachmentObject = {
217 attachment: Buffer.from(out),
218 name: `purge-${channel.id}-${Date.now()}.txt`,
219 description: `Purge log`
220 }
221 } catch {}
pineafan377794f2022-04-18 19:01:01 +0100222 let m = await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000223 .setEmoji(`CHANNEL.PURGE.GREEN`)
224 .setTitle(`Purge`)
225 .setDescription("Messages cleared")
226 .setStatus("Success")
227 ], components: [new Discord.MessageActionRow().addComponents([
228 new Discord.MessageButton()
229 .setCustomId("download")
230 .setLabel("Download transcript")
231 .setStyle("SUCCESS")
232 .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
233 ])]})
234 let component;
235 try {
236 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
237 } catch {}
238 if (component && component.customId === "download") {
pineafan377794f2022-04-18 19:01:01 +0100239 interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000240 .setEmoji("CHANNEL.PURGE.GREEN")
241 .setTitle(`Purge`)
pineafan377794f2022-04-18 19:01:01 +0100242 .setDescription("Transcript uploaded above")
pineafan5d1908e2022-02-28 21:34:47 +0000243 .setStatus("Success")
244 ], components: [], files: [attachmentObject]})
245 } else {
pineafan377794f2022-04-18 19:01:01 +0100246 interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000247 .setEmoji("CHANNEL.PURGE.GREEN")
248 .setTitle(`Purge`)
249 .setDescription("Messages cleared")
250 .setStatus("Success")
251 ], components: []})
252 }
pineafan8b4b17f2022-02-27 20:42:52 +0000253 } else {
pineafan377794f2022-04-18 19:01:01 +0100254 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000255 .setEmoji("CHANNEL.PURGE.GREEN")
256 .setTitle(`Purge`)
257 .setDescription("No changes were made")
258 .setStatus("Success")
259 ], components: []})
260 }
261 }
pineafan4f164f32022-02-26 22:07:12 +0000262}
263
264const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
pineafan8b4b17f2022-02-27 20:42:52 +0000265 // Allow the owner to purge
266 if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true
267 // Check if the user has manage_messages permission
268 if (! (interaction.member as GuildMember).permissions.has("MANAGE_MESSAGES")) throw "You do not have the `manage_messages` permission";
269 // Check if nucleus has the manage_messages permission
270 if (! interaction.guild.me.permissions.has("MANAGE_MESSAGES")) throw "I do not have the `manage_messages` permission";
271 // Allow warn
272 return true
pineafan4f164f32022-02-26 22:07:12 +0000273}
274
pineafan8b4b17f2022-02-27 20:42:52 +0000275export { command, callback, check };