blob: 31a21c94f655d7d4f9ed7b463f92bd98de8d485a [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
TheCodedProf4b17af32023-04-23 16:18:22 -040018const command = new ContextMenuCommandBuilder().setName("Purge up to Here").setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages);
PineaFana34d04b2023-01-03 22:05:42 +000019
20async function waitForButton(m: Discord.Message, member: Discord.GuildMember): Promise<boolean> {
21 let component;
22 try {
Skyler Greyf4f21c42023-03-08 14:36:29 +000023 component = await m.awaitMessageComponent({
Skyler Greyda16adf2023-03-05 10:22:12 +000024 time: 200000,
25 filter: (i) => i.user.id === member.id && i.channel!.id === m.channel.id && i.message.id === m.id
26 });
PineaFana34d04b2023-01-03 22:05:42 +000027 } catch (e) {
28 return false;
29 }
Skyler Greyf4f21c42023-03-08 14:36:29 +000030 await component.deferUpdate();
PineaFana34d04b2023-01-03 22:05:42 +000031 return true;
32}
33
PineaFana34d04b2023-01-03 22:05:42 +000034const callback = async (interaction: MessageContextMenuCommandInteraction) => {
35 await interaction.targetMessage.fetch();
36 const targetMessage = interaction.targetMessage;
37 const targetMember: Discord.User = targetMessage.author;
38 let allowedMessage: Discord.Message | undefined = undefined;
39 const channel = interaction.channel;
40 if (!channel) return;
41 await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
PineaFana34d04b2023-01-03 22:05:42 +000042 const history: Discord.Collection<string, Discord.Message> = await channel.messages.fetch({ limit: 100 });
43 if (Date.now() - targetMessage.createdTimestamp > 2 * 7 * 24 * 60 * 60 * 1000) {
Skyler Greyda16adf2023-03-05 10:22:12 +000044 const m = await interaction.editReply({
45 embeds: [
46 new EmojiEmbed()
47 .setTitle("Purge")
48 .setDescription(
49 "The message you selected is older than 2 weeks. Discord only allows bots to delete messages that are 2 weeks old or younger."
50 )
51 .setEmoji("CHANNEL.PURGE.RED")
52 .setStatus("Danger")
53 ],
54 components: [
55 new ActionRowBuilder<ButtonBuilder>().addComponents(
56 new ButtonBuilder()
57 .setCustomId("oldest")
58 .setLabel("Select first allowed message")
59 .setStyle(ButtonStyle.Primary)
60 )
61 ]
62 });
63 if (!(await waitForButton(m, interaction.member as Discord.GuildMember))) return;
PineaFana34d04b2023-01-03 22:05:42 +000064 } else if (!history.has(targetMessage.id)) {
Skyler Greyda16adf2023-03-05 10:22:12 +000065 const m = await interaction.editReply({
66 embeds: [
67 new EmojiEmbed()
68 .setTitle("Purge")
69 .setDescription(
70 "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."
71 )
72 .setEmoji("CHANNEL.PURGE.YELLOW")
73 .setStatus("Warning")
74 ],
75 components: [
76 new ActionRowBuilder<ButtonBuilder>().addComponents(
77 new ButtonBuilder()
78 .setCustomId("oldest")
79 .setLabel("Select first allowed message")
80 .setStyle(ButtonStyle.Primary)
81 )
82 ]
83 });
84 if (!(await waitForButton(m, interaction.member as Discord.GuildMember))) return;
PineaFana34d04b2023-01-03 22:05:42 +000085 } else {
86 allowedMessage = targetMessage;
87 }
88
89 if (!allowedMessage) {
90 // Find the oldest message thats younger than 2 weeks
Skyler Greyda16adf2023-03-05 10:22:12 +000091 const messages = history.filter((m) => Date.now() - m.createdTimestamp < 2 * 7 * 24 * 60 * 60 * 1000);
PineaFana34d04b2023-01-03 22:05:42 +000092 allowedMessage = messages.sort((a, b) => a.createdTimestamp - b.createdTimestamp).first();
93 }
94
95 if (!allowedMessage) {
Skyler Greyda16adf2023-03-05 10:22:12 +000096 await interaction.editReply({
97 embeds: [
98 new EmojiEmbed()
99 .setTitle("Purge")
100 .setDescription(
101 "There are no valid messages in the last 100 messages. (No messages younger than 2 weeks)"
102 )
103 .setEmoji("CHANNEL.PURGE.RED")
104 .setStatus("Danger")
105 ],
106 components: []
107 });
PineaFana34d04b2023-01-03 22:05:42 +0000108 return;
109 }
110
Skyler Greyda16adf2023-03-05 10:22:12 +0000111 let reason: string | null = null;
PineaFana34d04b2023-01-03 22:05:42 +0000112 let confirmation;
113 let chosen = false;
114 let timedOut = false;
115 let deleteSelected = true;
116 let deleteUser = false;
117 do {
118 confirmation = await new confirmationMessage(interaction)
119 .setEmoji("CHANNEL.PURGE.RED")
120 .setTitle("Purge")
121 .setDescription(
122 `[[Selected Message]](${allowedMessage.url})\n\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000123 (reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*") +
124 "\n\n" +
125 `Are you sure you want to delete all messages from below the selected message?`
PineaFana34d04b2023-01-03 22:05:42 +0000126 )
127 .addCustomBoolean(
128 "includeSelected",
129 "Include selected message",
130 false,
131 undefined,
132 "The selected message will be deleted as well.",
133 "The selected message will not be deleted.",
134 "CONTROL." + (deleteSelected ? "TICK" : "CROSS"),
135 deleteSelected
136 )
137 .addCustomBoolean(
138 "onlySelectedUser",
139 "Only selected user",
140 false,
141 undefined,
142 `Only messages from <@${targetMember.id}> will be deleted.`,
143 `All messages will be deleted.`,
144 "CONTROL." + (deleteUser ? "TICK" : "CROSS"),
145 deleteUser
146 )
147 .setColor("Danger")
148 .addReasonButton(reason ?? "")
PineaFan0d06edc2023-01-17 22:10:31 +0000149 .setFailedMessage("No changes were made", "Success", "CHANNEL.PURGE.GREEN")
Skyler Greyda16adf2023-03-05 10:22:12 +0000150 .send(true);
151 reason = reason ?? "";
PineaFana34d04b2023-01-03 22:05:42 +0000152 if (confirmation.cancelled) timedOut = true;
153 else if (confirmation.success !== undefined) chosen = true;
154 else if (confirmation.newReason) reason = confirmation.newReason;
155 else if (confirmation.components) {
156 deleteSelected = confirmation.components["includeSelected"]!.active;
157 deleteUser = confirmation.components["onlySelectedUser"]!.active;
158 }
159 } while (!chosen && !timedOut);
PineaFan0d06edc2023-01-17 22:10:31 +0000160 if (timedOut || !confirmation.success) return;
PineaFana34d04b2023-01-03 22:05:42 +0000161 const filteredMessages = history
Skyler Greyda16adf2023-03-05 10:22:12 +0000162 .filter((m) => m.createdTimestamp >= allowedMessage!.createdTimestamp) // older than selected
163 .filter((m) => (deleteUser ? m.author.id === targetMember.id : true)) // only selected user
164 .filter((m) => (deleteSelected ? true : m.id !== allowedMessage!.id)); // include selected
PineaFana34d04b2023-01-03 22:05:42 +0000165
166 const deleted = await (channel as GuildTextBasedChannel).bulkDelete(filteredMessages, true);
167 if (deleted.size === 0) {
168 return await interaction.editReply({
169 embeds: [
170 new EmojiEmbed()
171 .setEmoji("CHANNEL.PURGE.RED")
172 .setTitle("Purge")
173 .setDescription("No messages were deleted")
174 .setStatus("Danger")
175 ],
176 components: []
177 });
178 }
179 if (deleteUser) {
180 await client.database.history.create(
181 "purge",
182 interaction.guild!.id,
183 targetMember,
184 interaction.user,
185 reason === "" ? "*No reason provided*" : reason,
186 null,
187 null,
188 deleted.size.toString()
189 );
190 }
191 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
192 const data = {
193 meta: {
194 type: "channelPurge",
195 displayName: "Channel Purged",
196 calculateType: "messageDelete",
197 color: NucleusColors.red,
198 emoji: "PUNISH.BAN.RED",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500199 timestamp: Date.now()
PineaFana34d04b2023-01-03 22:05:42 +0000200 },
201 list: {
202 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
203 purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
204 channel: entry(interaction.channel!.id, renderChannel(interaction.channel! as Discord.GuildChannel)),
205 messagesCleared: entry(deleted.size.toString(), deleted.size.toString())
206 },
207 hidden: {
208 guild: interaction.guild!.id
209 }
210 };
Skyler Greyf4f21c42023-03-08 14:36:29 +0000211 await log(data);
Skyler Greyda16adf2023-03-05 10:22:12 +0000212 const messages: Message[] = deleted
213 .map((m) => m)
214 .filter((m) => m instanceof Message)
215 .map((m) => m as Message);
216 if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id);
217 const messageArray: Message[] = messages
218 .filter(
219 (message) =>
220 !message!.components.some((component) =>
221 component.components.some((child) => child.customId?.includes("transcript") ?? false)
222 )
pineafande618c42023-02-28 18:03:18 +0000223 )
Skyler Greyda16adf2023-03-05 10:22:12 +0000224 .map((message) => message as Message);
225 const transcript = await client.database.transcripts.createTranscript(
Skyler Greye0c511b2023-03-06 10:30:17 +0000226 "purge",
Skyler Greyda16adf2023-03-05 10:22:12 +0000227 messageArray,
228 interaction,
229 interaction.member as GuildMember
230 );
Samuel Shuertcc63dee2023-03-03 18:54:29 -0500231
TheCodedProf75c51be2023-03-03 17:18:18 -0500232 const [code, key, iv] = await client.database.transcripts.create(transcript);
Samuel Shuertcc63dee2023-03-03 18:54:29 -0500233
pineafande618c42023-02-28 18:03:18 +0000234 await interaction.editReply({
PineaFana34d04b2023-01-03 22:05:42 +0000235 embeds: [
236 new EmojiEmbed()
237 .setEmoji("CHANNEL.PURGE.GREEN")
238 .setTitle("Purge")
239 .setDescription("Messages cleared")
240 .setStatus("Success")
241 ],
242 components: [
243 new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
Skyler Greyda16adf2023-03-05 10:22:12 +0000244 new ButtonBuilder()
245 .setLabel("View")
246 .setStyle(ButtonStyle.Link)
247 .setURL(`https://clicks.codes/nucleus/transcript/${code}?key=${key}&iv=${iv}`)
248 .setDisabled(!code)
PineaFana34d04b2023-01-03 22:05:42 +0000249 ])
250 ]
pineafande618c42023-02-28 18:03:18 +0000251 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000252};
PineaFana34d04b2023-01-03 22:05:42 +0000253
254const check = async (_interaction: MessageContextMenuCommandInteraction) => {
255 return true;
Skyler Greyda16adf2023-03-05 10:22:12 +0000256};
PineaFana34d04b2023-01-03 22:05:42 +0000257
Skyler Greyda16adf2023-03-05 10:22:12 +0000258export { command, callback, check };