blob: 0b7723e910cdb21f0d9b810d9b2c910b3a60bffc [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import confirmationMessage from "../../utils/confirmationMessage.js";
2import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
3import { LoadingEmbed } from "../../utils/defaults.js";
4import Discord, {
5 ActionRowBuilder,
6 ButtonBuilder,
7 ButtonStyle,
8 ContextMenuCommandBuilder,
9 GuildMember,
10 GuildTextBasedChannel,
11 Message,
TheCodedProf4b17af32023-04-23 16:18:22 -040012 MessageContextMenuCommandInteraction,
13 PermissionFlagsBits
Skyler Greyda16adf2023-03-05 10:22:12 +000014} from "discord.js";
PineaFana34d04b2023-01-03 22:05:42 +000015import client from "../../utils/client.js";
Skyler Greyda16adf2023-03-05 10:22:12 +000016import { messageException } from "../../utils/createTemporaryStorage.js";
PineaFana34d04b2023-01-03 22:05:42 +000017
Skyler Greyf25451c2023-04-23 21:26:11 +000018const command = new ContextMenuCommandBuilder()
19 .setName("Purge up to Here")
20 .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages);
PineaFana34d04b2023-01-03 22:05:42 +000021
22async function waitForButton(m: Discord.Message, member: Discord.GuildMember): Promise<boolean> {
23 let component;
24 try {
Skyler Greyf4f21c42023-03-08 14:36:29 +000025 component = await m.awaitMessageComponent({
Skyler Greyda16adf2023-03-05 10:22:12 +000026 time: 200000,
27 filter: (i) => i.user.id === member.id && i.channel!.id === m.channel.id && i.message.id === m.id
28 });
PineaFana34d04b2023-01-03 22:05:42 +000029 } catch (e) {
30 return false;
31 }
Skyler Greyf4f21c42023-03-08 14:36:29 +000032 await component.deferUpdate();
PineaFana34d04b2023-01-03 22:05:42 +000033 return true;
34}
35
PineaFana34d04b2023-01-03 22:05:42 +000036const callback = async (interaction: MessageContextMenuCommandInteraction) => {
37 await interaction.targetMessage.fetch();
38 const targetMessage = interaction.targetMessage;
39 const targetMember: Discord.User = targetMessage.author;
40 let allowedMessage: Discord.Message | undefined = undefined;
41 const channel = interaction.channel;
42 if (!channel) return;
43 await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
PineaFana34d04b2023-01-03 22:05:42 +000044 const history: Discord.Collection<string, Discord.Message> = await channel.messages.fetch({ limit: 100 });
45 if (Date.now() - targetMessage.createdTimestamp > 2 * 7 * 24 * 60 * 60 * 1000) {
Skyler Greyda16adf2023-03-05 10:22:12 +000046 const m = await interaction.editReply({
47 embeds: [
48 new EmojiEmbed()
49 .setTitle("Purge")
50 .setDescription(
51 "The message you selected is older than 2 weeks. Discord only allows bots to delete messages that are 2 weeks old or younger."
52 )
53 .setEmoji("CHANNEL.PURGE.RED")
54 .setStatus("Danger")
55 ],
56 components: [
57 new ActionRowBuilder<ButtonBuilder>().addComponents(
58 new ButtonBuilder()
59 .setCustomId("oldest")
60 .setLabel("Select first allowed message")
61 .setStyle(ButtonStyle.Primary)
62 )
63 ]
64 });
65 if (!(await waitForButton(m, interaction.member as Discord.GuildMember))) return;
PineaFana34d04b2023-01-03 22:05:42 +000066 } else if (!history.has(targetMessage.id)) {
Skyler Greyda16adf2023-03-05 10:22:12 +000067 const m = await interaction.editReply({
68 embeds: [
69 new EmojiEmbed()
70 .setTitle("Purge")
71 .setDescription(
72 "The message you selected is not in the last 100 messages in this channel. Discord only allows bots to delete 100 messages at a time."
73 )
74 .setEmoji("CHANNEL.PURGE.YELLOW")
75 .setStatus("Warning")
76 ],
77 components: [
78 new ActionRowBuilder<ButtonBuilder>().addComponents(
79 new ButtonBuilder()
80 .setCustomId("oldest")
81 .setLabel("Select first allowed message")
82 .setStyle(ButtonStyle.Primary)
83 )
84 ]
85 });
86 if (!(await waitForButton(m, interaction.member as Discord.GuildMember))) return;
PineaFana34d04b2023-01-03 22:05:42 +000087 } else {
88 allowedMessage = targetMessage;
89 }
90
91 if (!allowedMessage) {
92 // Find the oldest message thats younger than 2 weeks
Skyler Greyda16adf2023-03-05 10:22:12 +000093 const messages = history.filter((m) => Date.now() - m.createdTimestamp < 2 * 7 * 24 * 60 * 60 * 1000);
PineaFana34d04b2023-01-03 22:05:42 +000094 allowedMessage = messages.sort((a, b) => a.createdTimestamp - b.createdTimestamp).first();
95 }
96
97 if (!allowedMessage) {
Skyler Greyda16adf2023-03-05 10:22:12 +000098 await interaction.editReply({
99 embeds: [
100 new EmojiEmbed()
101 .setTitle("Purge")
102 .setDescription(
103 "There are no valid messages in the last 100 messages. (No messages younger than 2 weeks)"
104 )
105 .setEmoji("CHANNEL.PURGE.RED")
106 .setStatus("Danger")
107 ],
108 components: []
109 });
PineaFana34d04b2023-01-03 22:05:42 +0000110 return;
111 }
112
Skyler Greyda16adf2023-03-05 10:22:12 +0000113 let reason: string | null = null;
PineaFana34d04b2023-01-03 22:05:42 +0000114 let confirmation;
115 let chosen = false;
116 let timedOut = false;
117 let deleteSelected = true;
118 let deleteUser = false;
119 do {
120 confirmation = await new confirmationMessage(interaction)
121 .setEmoji("CHANNEL.PURGE.RED")
122 .setTitle("Purge")
123 .setDescription(
124 `[[Selected Message]](${allowedMessage.url})\n\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000125 (reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*") +
126 "\n\n" +
127 `Are you sure you want to delete all messages from below the selected message?`
PineaFana34d04b2023-01-03 22:05:42 +0000128 )
129 .addCustomBoolean(
130 "includeSelected",
131 "Include selected message",
132 false,
133 undefined,
134 "The selected message will be deleted as well.",
135 "The selected message will not be deleted.",
136 "CONTROL." + (deleteSelected ? "TICK" : "CROSS"),
137 deleteSelected
138 )
139 .addCustomBoolean(
140 "onlySelectedUser",
141 "Only selected user",
142 false,
143 undefined,
144 `Only messages from <@${targetMember.id}> will be deleted.`,
145 `All messages will be deleted.`,
146 "CONTROL." + (deleteUser ? "TICK" : "CROSS"),
147 deleteUser
148 )
149 .setColor("Danger")
150 .addReasonButton(reason ?? "")
PineaFan0d06edc2023-01-17 22:10:31 +0000151 .setFailedMessage("No changes were made", "Success", "CHANNEL.PURGE.GREEN")
Skyler Greyda16adf2023-03-05 10:22:12 +0000152 .send(true);
153 reason = reason ?? "";
PineaFana34d04b2023-01-03 22:05:42 +0000154 if (confirmation.cancelled) timedOut = true;
155 else if (confirmation.success !== undefined) chosen = true;
156 else if (confirmation.newReason) reason = confirmation.newReason;
157 else if (confirmation.components) {
158 deleteSelected = confirmation.components["includeSelected"]!.active;
159 deleteUser = confirmation.components["onlySelectedUser"]!.active;
160 }
161 } while (!chosen && !timedOut);
PineaFan0d06edc2023-01-17 22:10:31 +0000162 if (timedOut || !confirmation.success) return;
PineaFana34d04b2023-01-03 22:05:42 +0000163 const filteredMessages = history
Skyler Greyda16adf2023-03-05 10:22:12 +0000164 .filter((m) => m.createdTimestamp >= allowedMessage!.createdTimestamp) // older than selected
165 .filter((m) => (deleteUser ? m.author.id === targetMember.id : true)) // only selected user
166 .filter((m) => (deleteSelected ? true : m.id !== allowedMessage!.id)); // include selected
PineaFana34d04b2023-01-03 22:05:42 +0000167
168 const deleted = await (channel as GuildTextBasedChannel).bulkDelete(filteredMessages, true);
169 if (deleted.size === 0) {
170 return await interaction.editReply({
171 embeds: [
172 new EmojiEmbed()
173 .setEmoji("CHANNEL.PURGE.RED")
174 .setTitle("Purge")
175 .setDescription("No messages were deleted")
176 .setStatus("Danger")
177 ],
178 components: []
179 });
180 }
181 if (deleteUser) {
182 await client.database.history.create(
183 "purge",
184 interaction.guild!.id,
185 targetMember,
186 interaction.user,
187 reason === "" ? "*No reason provided*" : reason,
188 null,
189 null,
190 deleted.size.toString()
191 );
192 }
193 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
194 const data = {
195 meta: {
196 type: "channelPurge",
197 displayName: "Channel Purged",
198 calculateType: "messageDelete",
199 color: NucleusColors.red,
200 emoji: "PUNISH.BAN.RED",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500201 timestamp: Date.now()
PineaFana34d04b2023-01-03 22:05:42 +0000202 },
203 list: {
204 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
205 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
206 channel: entry(interaction.channel!.id, renderChannel(interaction.channel! as Discord.GuildChannel)),
207 messagesCleared: entry(deleted.size.toString(), deleted.size.toString())
208 },
209 hidden: {
210 guild: interaction.guild!.id
211 }
212 };
Skyler Greyf4f21c42023-03-08 14:36:29 +0000213 await log(data);
Skyler Greyda16adf2023-03-05 10:22:12 +0000214 const messages: Message[] = deleted
215 .map((m) => m)
216 .filter((m) => m instanceof Message)
217 .map((m) => m as Message);
218 if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id);
219 const messageArray: Message[] = messages
220 .filter(
221 (message) =>
222 !message!.components.some((component) =>
223 component.components.some((child) => child.customId?.includes("transcript") ?? false)
224 )
pineafande618c42023-02-28 18:03:18 +0000225 )
Skyler Greyda16adf2023-03-05 10:22:12 +0000226 .map((message) => message as Message);
227 const transcript = await client.database.transcripts.createTranscript(
Skyler Greye0c511b2023-03-06 10:30:17 +0000228 "purge",
Skyler Greyda16adf2023-03-05 10:22:12 +0000229 messageArray,
230 interaction,
231 interaction.member as GuildMember
232 );
Samuel Shuertcc63dee2023-03-03 18:54:29 -0500233
TheCodedProf75c51be2023-03-03 17:18:18 -0500234 const [code, key, iv] = await client.database.transcripts.create(transcript);
Samuel Shuertcc63dee2023-03-03 18:54:29 -0500235
pineafande618c42023-02-28 18:03:18 +0000236 await interaction.editReply({
PineaFana34d04b2023-01-03 22:05:42 +0000237 embeds: [
238 new EmojiEmbed()
239 .setEmoji("CHANNEL.PURGE.GREEN")
240 .setTitle("Purge")
241 .setDescription("Messages cleared")
242 .setStatus("Success")
243 ],
244 components: [
245 new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
Skyler Greyda16adf2023-03-05 10:22:12 +0000246 new ButtonBuilder()
247 .setLabel("View")
248 .setStyle(ButtonStyle.Link)
249 .setURL(`https://clicks.codes/nucleus/transcript/${code}?key=${key}&iv=${iv}`)
250 .setDisabled(!code)
PineaFana34d04b2023-01-03 22:05:42 +0000251 ])
252 ]
pineafande618c42023-02-28 18:03:18 +0000253 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000254};
PineaFana34d04b2023-01-03 22:05:42 +0000255
256const check = async (_interaction: MessageContextMenuCommandInteraction) => {
257 return true;
Skyler Greyda16adf2023-03-05 10:22:12 +0000258};
PineaFana34d04b2023-01-03 22:05:42 +0000259
Skyler Greyda16adf2023-03-05 10:22:12 +0000260export { command, callback, check };