blob: ab5e037e147d06969da683da3b2243e0f828000c [file] [log] [blame]
TheCodedProf8b3da212023-02-02 15:09:55 -05001import type Discord from "discord.js";
TheCodedProf5b53a8c2023-02-03 15:40:26 -05002import { ActionRowBuilder,
3 AnySelectMenuInteraction,
4 APIMessageComponentEmoji,
5 ButtonBuilder,
6 ButtonInteraction,
7 ButtonStyle,
8 ChannelSelectMenuBuilder,
9 ChannelSelectMenuInteraction,
10 CommandInteraction,
11 Interaction,
12 Message,
13 MessageComponentInteraction,
14 ModalBuilder,
15 ModalSubmitInteraction,
16 RoleSelectMenuBuilder,
17 RoleSelectMenuInteraction,
18 StringSelectMenuBuilder,
19 StringSelectMenuInteraction,
20 StringSelectMenuOptionBuilder,
21 TextInputBuilder,
22 TextInputStyle,
23 UserSelectMenuBuilder,
24 UserSelectMenuInteraction
25} from "discord.js";
TheCodedProf8b3da212023-02-02 15:09:55 -050026import type { SlashCommandSubcommandBuilder } from "discord.js";
27import { LoadingEmbed } from "../../utils/defaults.js";
28import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
29import client from "../../utils/client.js";
30import getEmojiByName from "../../utils/getEmojiByName.js";
TheCodedProf5b53a8c2023-02-03 15:40:26 -050031import { modalInteractionCollector } from "../../utils/dualCollector.js";
TheCodedProf8b3da212023-02-02 15:09:55 -050032
TheCodedProf486bca32023-02-02 16:49:44 -050033
TheCodedProf8b3da212023-02-02 15:09:55 -050034const command = (builder: SlashCommandSubcommandBuilder) =>
35 builder.setName("automod").setDescription("Setting for automatic moderation features");
36
37
38const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id);
39
TheCodedProf486bca32023-02-02 16:49:44 -050040const listToAndMore = (list: string[], max: number) => {
41 // PineappleFan, Coded, Mini (and 10 more)
42 if(list.length > max) {
43 return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`;
44 }
45 return list.join(", ");
46}
47
48const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise<string[]> => {
49
50 const back = new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder().setCustomId("back").setLabel("Back").setStyle(ButtonStyle.Secondary).setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji));
TheCodedProf486bca32023-02-02 16:49:44 -050051 let closed;
52 do {
53 let render: string[] = []
54 let mapped: string[] = [];
55 let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder;
56 switch(type) {
PineaFanb0d0c242023-02-05 10:59:45 +000057 case "member": {
TheCodedProf486bca32023-02-02 16:49:44 -050058 menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25);
59 mapped = await Promise.all(ids.map(async (id) => { return (await client.users.fetch(id).then(user => user.tag)) || "Unknown User" }));
60 render = ids.map(id => client.logger.renderUser(id))
61 break;
PineaFanb0d0c242023-02-05 10:59:45 +000062 }
63 case "role": {
TheCodedProf486bca32023-02-02 16:49:44 -050064 menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25);
PineaFanb0d0c242023-02-05 10:59:45 +000065 mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.roles.fetch(id).then(role => role?.name ?? "Unknown Role"))}));
TheCodedProf486bca32023-02-02 16:49:44 -050066 render = ids.map(id => client.logger.renderRole(id, interaction.guild!))
67 break;
PineaFanb0d0c242023-02-05 10:59:45 +000068 }
69 case "channel": {
TheCodedProf486bca32023-02-02 16:49:44 -050070 menu = new ChannelSelectMenuBuilder().setCustomId("channel").setPlaceholder("Select channels").setMaxValues(25);
PineaFanb0d0c242023-02-05 10:59:45 +000071 mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.channels.fetch(id).then(channel => channel?.name ?? "Unknown Role")) }));
TheCodedProf486bca32023-02-02 16:49:44 -050072 render = ids.map(id => client.logger.renderChannel(id))
73 break;
PineaFanb0d0c242023-02-05 10:59:45 +000074 }
TheCodedProf486bca32023-02-02 16:49:44 -050075 }
76 const removeOptions = new ActionRowBuilder<StringSelectMenuBuilder>()
77 .addComponents(
78 new StringSelectMenuBuilder()
79 .setCustomId("remove")
80 .setPlaceholder("Remove")
81 .addOptions(mapped.map((name, i) => new StringSelectMenuOptionBuilder().setLabel(name).setValue(ids[i]!)))
82 .setDisabled(ids.length === 0)
83 );
84
85 const embed = new EmojiEmbed()
86 .setTitle(title)
87 .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN"))
88 .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None"))
89 .setStatus("Success");
PineaFanb0d0c242023-02-05 10:59:45 +000090 const components: ActionRowBuilder<
TheCodedProf486bca32023-02-02 16:49:44 -050091 StringSelectMenuBuilder |
92 ButtonBuilder |
93 ChannelSelectMenuBuilder |
94 UserSelectMenuBuilder |
95 RoleSelectMenuBuilder
96 >[] = [new ActionRowBuilder<typeof menu>().addComponents(menu)]
97 if(ids.length > 0) components.push(removeOptions);
98 components.push(back);
99
100 await interaction.editReply({embeds: [embed], components: components})
101
102 let i: AnySelectMenuInteraction | ButtonInteraction;
103 try {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500104 i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000});
TheCodedProf486bca32023-02-02 16:49:44 -0500105 } catch(e) {
106 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000107 continue;
TheCodedProf486bca32023-02-02 16:49:44 -0500108 }
109
110 if(i.isButton()) {
111 await i.deferUpdate();
112 if(i.customId === "back") {
113 closed = true;
114 break;
115 }
116 } else if(i.isStringSelectMenu()) {
117 await i.deferUpdate();
118 if(i.customId === "remove") {
119 ids = ids.filter(id => id !== (i as StringSelectMenuInteraction).values[0]);
120 if(ids.length === 0) {
121 menu.data.disabled = true;
122 }
123 }
124 } else {
125 await i.deferUpdate();
126 if(i.customId === "user") {
127 ids = ids.concat((i as UserSelectMenuInteraction).values);
128 } else if(i.customId === "role") {
129 ids = ids.concat((i as RoleSelectMenuInteraction).values);
130 } else if(i.customId === "channel") {
131 ids = ids.concat((i as ChannelSelectMenuInteraction).values);
132 }
133 }
134
135 } while(!closed)
136 return ids;
137}
TheCodedProf8b3da212023-02-02 15:09:55 -0500138
139const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
140 NSFW: boolean,
141 size: boolean
142}): Promise<{NSFW: boolean, size: boolean}> => {
143 let closed = false;
144 do {
145 const options = new ActionRowBuilder<ButtonBuilder>()
146 .addComponents(
147 new ButtonBuilder()
148 .setCustomId("back")
149 .setLabel("Back")
150 .setStyle(ButtonStyle.Secondary)
151 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
152 new ButtonBuilder()
153 .setCustomId("nsfw")
154 .setLabel("NSFW")
155 .setStyle(current.NSFW ? ButtonStyle.Success : ButtonStyle.Danger)
156 .setEmoji(emojiFromBoolean(current.NSFW, "id") as APIMessageComponentEmoji),
157 new ButtonBuilder()
158 .setCustomId("size")
159 .setLabel("Size")
160 .setStyle(current.size ? ButtonStyle.Success : ButtonStyle.Danger)
161 .setEmoji(emojiFromBoolean(current.size, "id") as APIMessageComponentEmoji)
162 )
163
164 const embed = new EmojiEmbed()
165 .setTitle("Image Settings")
166 .setDescription(
167 `${emojiFromBoolean(current.NSFW)} **NSFW**\n` +
168 `${emojiFromBoolean(current.size)} **Size**\n`
169 )
170
171 await interaction.editReply({embeds: [embed], components: [options]});
172
173 let i: ButtonInteraction;
174 try {
175 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
176 } catch (e) {
177 return current;
178 }
179 await i.deferUpdate();
180 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000181 case "back": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500182 closed = true;
183 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000184 }
185 case "nsfw": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500186 current.NSFW = !current.NSFW;
187 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000188 }
189 case "size": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500190 current.size = !current.size;
191 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000192 }
TheCodedProf8b3da212023-02-02 15:09:55 -0500193 }
194 } while(!closed);
195 return current;
196}
197
198const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
199 enabled: boolean,
200 words: {strict: string[], loose: string[]},
TheCodedProf486bca32023-02-02 16:49:44 -0500201 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500202}): Promise<{
203 enabled: boolean,
204 words: {strict: string[], loose: string[]},
TheCodedProf486bca32023-02-02 16:49:44 -0500205 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500206}> => {
TheCodedProf486bca32023-02-02 16:49:44 -0500207 let closed = false;
208 do {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500209 const buttons = new ActionRowBuilder<ButtonBuilder>()
210 .addComponents(
211 new ButtonBuilder()
212 .setCustomId("back")
213 .setLabel("Back")
214 .setStyle(ButtonStyle.Secondary)
215 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
216 new ButtonBuilder()
217 .setCustomId("enabled")
218 .setLabel("Enabled")
219 .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
220 .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji),
221 );
222
223 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
224 .addComponents(
225 new StringSelectMenuBuilder()
226 .setCustomId("edit")
227 .setPlaceholder("Edit... ")
228 .addOptions(
229 new StringSelectMenuOptionBuilder()
230 .setLabel("Words")
231 .setDescription("Edit your list of words to filter")
232 .setValue("words"),
233 new StringSelectMenuOptionBuilder()
234 .setLabel("Allowed Users")
235 .setDescription("Users who will be unaffected by the word filter")
236 .setValue("allowedUsers"),
237 new StringSelectMenuOptionBuilder()
238 .setLabel("Allowed Roles")
239 .setDescription("Roles that will be unaffected by the word filter")
240 .setValue("allowedRoles"),
241 new StringSelectMenuOptionBuilder()
242 .setLabel("Allowed Channels")
243 .setDescription("Channels where the word filter will not apply")
244 .setValue("allowedChannels")
245 )
246 .setDisabled(!current.enabled)
247 );
248
249 const embed = new EmojiEmbed()
250 .setTitle("Word Filters")
251 .setDescription(
252 `${emojiFromBoolean(current.enabled)} **Enabled**\n` +
253 `**Strict Words:** ${listToAndMore(current.words.strict, 5)}\n` +
254 `**Loose Words:** ${listToAndMore(current.words.loose, 5)}\n\n` +
255 `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
256 `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
257 `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
258 )
259 .setStatus("Success")
260 .setEmoji("GUILD.SETTINGS.GREEN")
261
262 await interaction.editReply({embeds: [embed], components: [selectMenu, buttons]});
263
264 let i: ButtonInteraction | StringSelectMenuInteraction;
265 try {
266 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
267 } catch (e) {
268 closed = true;
269 break;
270 }
271
272 if(i.isButton()) {
273 await i.deferUpdate();
274 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000275 case "back": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500276 closed = true;
277 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000278 }
279 case "enabled": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500280 current.enabled = !current.enabled;
281 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000282 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500283 }
284 } else {
285 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000286 case "words": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500287 await interaction.editReply({embeds: [new EmojiEmbed()
288 .setTitle("Word Filter")
289 .setDescription("Modal opened. If you can't see it, click back and try again.")
290 .setStatus("Success")
291 .setEmoji("GUILD.SETTINGS.GREEN")
292 ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
293 .setLabel("Back")
294 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
295 .setStyle(ButtonStyle.Primary)
296 .setCustomId("back")
297 )]})
298 const modal = new ModalBuilder()
299 .setTitle("Word Filter")
300 .setCustomId("wordFilter")
301 .addComponents(
302 new ActionRowBuilder<TextInputBuilder>()
303 .addComponents(
304 new TextInputBuilder()
305 .setCustomId("wordStrict")
306 .setLabel("Strict Words")
307 .setPlaceholder("Matches anywhere in the message, including surrounded by other characters")
PineaFanb0d0c242023-02-05 10:59:45 +0000308 .setValue(current.words.strict.join(", "))
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500309 .setStyle(TextInputStyle.Paragraph)
310 .setRequired(false)
311 ),
312 new ActionRowBuilder<TextInputBuilder>()
313 .addComponents(
314 new TextInputBuilder()
315 .setCustomId("wordLoose")
316 .setLabel("Loose Words")
317 .setPlaceholder("Matches only if the word is by itself, surrounded by spaces or punctuation")
PineaFanb0d0c242023-02-05 10:59:45 +0000318 .setValue(current.words.loose.join(", "))
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500319 .setStyle(TextInputStyle.Paragraph)
320 .setRequired(false)
321 )
322 )
323
324 await i.showModal(modal);
325 let out;
326 try {
327 out = await modalInteractionCollector(
328 m,
329 (m: Interaction) =>
330 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
331 (m) => m.customId === "back"
332 );
333 } catch (e) {
334 break;
335 }
336 if (!out) break;
337 if(out.isButton()) break;
338 current.words.strict = out.fields.getTextInputValue("wordStrict")
339 .split(",").map(s => s.trim()).filter(s => s.length > 0);
340 current.words.loose = out.fields.getTextInputValue("wordLoose")
341 .split(",").map(s => s.trim()).filter(s => s.length > 0);
342 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000343 }
344 case "allowedUsers": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500345 await i.deferUpdate();
346 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter");
347 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000348 }
349 case "allowedRoles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500350 await i.deferUpdate();
351 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter");
352 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000353 }
354 case "allowedChannels": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500355 await i.deferUpdate();
356 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter");
357 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000358 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500359 }
360 }
TheCodedProf486bca32023-02-02 16:49:44 -0500361 } while(!closed);
362 return current;
TheCodedProf8b3da212023-02-02 15:09:55 -0500363}
364
365const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
366 enabled: boolean,
TheCodedProf486bca32023-02-02 16:49:44 -0500367 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500368}): Promise<{
369 enabled: boolean,
TheCodedProf486bca32023-02-02 16:49:44 -0500370 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500371}> => {
372
TheCodedProf486bca32023-02-02 16:49:44 -0500373 let closed = false;
374 do {
375 const buttons = new ActionRowBuilder<ButtonBuilder>()
376 .addComponents(
377 new ButtonBuilder()
378 .setCustomId("back")
379 .setLabel("Back")
380 .setStyle(ButtonStyle.Secondary)
381 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
382 new ButtonBuilder()
383 .setCustomId("enabled")
384 .setLabel(current.enabled ? "Enabled" : "Disabled")
385 .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
386 .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji)
387 );
388 const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
389 .addComponents(
390 new StringSelectMenuBuilder()
391 .setCustomId("toEdit")
392 .setPlaceholder("Edit your allow list")
393 .addOptions(
394 new StringSelectMenuOptionBuilder()
395 .setLabel("Users")
396 .setDescription("Users that are allowed to send invites")
397 .setValue("users"),
398 new StringSelectMenuOptionBuilder()
399 .setLabel("Roles")
400 .setDescription("Roles that are allowed to send invites")
401 .setValue("roles"),
402 new StringSelectMenuOptionBuilder()
403 .setLabel("Channels")
404 .setDescription("Channels that anyone is allowed to send invites in")
405 .setValue("channels")
406 ).setDisabled(!current.enabled)
407 )
408
409 const embed = new EmojiEmbed()
410 .setTitle("Invite Settings")
411 .setDescription(
412 "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + `\n\n` +
413 `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` +
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500414 `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
415 `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
416 `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
TheCodedProf486bca32023-02-02 16:49:44 -0500417 )
418 .setStatus("Success")
419 .setEmoji("GUILD.SETTINGS.GREEN")
420
421
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500422 await interaction.editReply({embeds: [embed], components: [menu, buttons]});
TheCodedProf486bca32023-02-02 16:49:44 -0500423
424 let i: ButtonInteraction | StringSelectMenuInteraction;
425 try {
426 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
427 } catch (e) {
428 return current;
429 }
430
431 if(i.isButton()) {
432 await i.deferUpdate();
433 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000434 case "back": {
TheCodedProf486bca32023-02-02 16:49:44 -0500435 closed = true;
436 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000437 }
438 case "enabled": {
TheCodedProf486bca32023-02-02 16:49:44 -0500439 current.enabled = !current.enabled;
440 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000441 }
TheCodedProf486bca32023-02-02 16:49:44 -0500442 }
443 } else {
444 await i.deferUpdate();
445 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000446 case "users": {
TheCodedProf486bca32023-02-02 16:49:44 -0500447 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings");
448 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000449 }
450 case "roles": {
TheCodedProf486bca32023-02-02 16:49:44 -0500451 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings");
452 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000453 }
454 case "channels": {
TheCodedProf486bca32023-02-02 16:49:44 -0500455 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings");
456 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000457 }
TheCodedProf486bca32023-02-02 16:49:44 -0500458 }
459 }
460
461 } while(!closed);
462 return current;
TheCodedProf8b3da212023-02-02 15:09:55 -0500463}
464
465const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
466 mass: number,
467 everyone: boolean,
468 roles: boolean,
469 allowed: {
470 roles: string[],
471 rolesToMention: string[],
472 users: string[],
473 channels: string[]
474 }
475}): Promise<{
476 mass: number,
477 everyone: boolean,
478 roles: boolean,
479 allowed: {
480 roles: string[],
481 rolesToMention: string[],
482 users: string[],
483 channels: string[]
484 }
485}> => {
TheCodedProf486bca32023-02-02 16:49:44 -0500486 let closed = false;
487
488 do {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500489
490 const buttons = new ActionRowBuilder<ButtonBuilder>()
491 .addComponents(
492 new ButtonBuilder()
493 .setCustomId("back")
494 .setLabel("Back")
495 .setStyle(ButtonStyle.Secondary)
496 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
497 new ButtonBuilder()
498 .setCustomId("everyone")
499 .setLabel(current.everyone ? "Everyone" : "No one")
500 .setStyle(current.everyone ? ButtonStyle.Success : ButtonStyle.Danger)
501 .setEmoji(emojiFromBoolean(current.everyone, "id") as APIMessageComponentEmoji),
502 new ButtonBuilder()
503 .setCustomId("roles")
504 .setLabel(current.roles ? "Roles" : "No roles")
505 .setStyle(current.roles ? ButtonStyle.Success : ButtonStyle.Danger)
506 .setEmoji(emojiFromBoolean(current.roles, "id") as APIMessageComponentEmoji)
507 );
508 const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
509 .addComponents(
510 new StringSelectMenuBuilder()
511 .setCustomId("toEdit")
512 .setPlaceholder("Edit mention settings")
513 .addOptions(
514 new StringSelectMenuOptionBuilder()
515 .setLabel("Mass Mention Amount")
516 .setDescription("The amount of mentions before the bot will delete the message")
517 .setValue("mass"),
518 new StringSelectMenuOptionBuilder()
519 .setLabel("Roles")
520 .setDescription("Roles that are able to be mentioned")
521 .setValue("roles"),
522 )
523 )
524
525 const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
526 .addComponents(
527 new StringSelectMenuBuilder()
528 .setCustomId("allowed")
529 .setPlaceholder("Edit exceptions")
530 .addOptions(
531 new StringSelectMenuOptionBuilder()
532 .setLabel("Users")
533 .setDescription("Users that are unaffected by the mention filter")
534 .setValue("users"),
535 new StringSelectMenuOptionBuilder()
536 .setLabel("Roles")
537 .setDescription("Roles that are unaffected by the mention filter")
538 .setValue("roles"),
539 new StringSelectMenuOptionBuilder()
540 .setLabel("Channels")
541 .setDescription("Channels where anyone is unaffected by the mention filter")
542 .setValue("channels")
543 )
544 )
545
546 const embed = new EmojiEmbed()
547 .setTitle("Mention Settings")
548 .setDescription(
549 `Log when members mention:\n` +
550 `${emojiFromBoolean(true)} **${current.mass}+ members** in one message\n` +
551 `${emojiFromBoolean(current.everyone)} **Everyone**\n` +
552 `${emojiFromBoolean(current.roles)} **Roles**\n` +
553 (current.allowed.rolesToMention.length > 0 ? `> *Except for ${listToAndMore(current.allowed.rolesToMention.map(r => `<@&${r}>`), 3)}*\n` : "") +
554 "\n" +
555 `Except if...\n` +
556 `> ${current.allowed.users.length > 0 ? `Member is: ${listToAndMore(current.allowed.users.map(u => `<@${u}>`), 3)}\n` : ""}` +
557 `> ${current.allowed.roles.length > 0 ? `Member has role: ${listToAndMore(current.allowed.roles.map(r => `<@&${r}>`), 3)}\n` : ""}` +
558 `> ${current.allowed.channels.length > 0 ? `In channel: ${listToAndMore(current.allowed.channels.map(c => `<#${c}>`), 3)}\n` : ""}`
559 )
560 .setStatus("Success")
561 .setEmoji("GUILD.SETTINGS.GREEN")
562
563 await interaction.editReply({embeds: [embed], components: [menu, allowedMenu, buttons]});
564
565 let i: ButtonInteraction | StringSelectMenuInteraction;
566 try {
567 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
568 } catch (e) {
569 closed = true;
570 break;
571 }
572
573 if(i.isButton()) {
574 await i.deferUpdate();
575 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000576 case "back": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500577 closed = true;
578 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000579 }
580 case "everyone": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500581 current.everyone = !current.everyone;
582 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000583 }
584 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500585 current.roles = !current.roles;
586 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000587 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500588 }
589 } else {
590 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000591 case "toEdit": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500592 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000593 case "mass": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500594 await interaction.editReply({embeds: [new EmojiEmbed()
595 .setTitle("Word Filter")
596 .setDescription("Modal opened. If you can't see it, click back and try again.")
597 .setStatus("Success")
598 .setEmoji("GUILD.SETTINGS.GREEN")
599 ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
600 .setLabel("Back")
601 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
602 .setStyle(ButtonStyle.Primary)
603 .setCustomId("back")
604 )]})
605 const modal = new ModalBuilder()
606 .setTitle("Mass Mention Amount")
607 .setCustomId("mass")
608 .addComponents(
609 new ActionRowBuilder<TextInputBuilder>()
610 .addComponents(
611 new TextInputBuilder()
612 .setCustomId("mass")
613 .setPlaceholder("Amount")
614 .setMinLength(1)
615 .setMaxLength(3)
616 .setStyle(TextInputStyle.Short)
617 )
618 )
619 await i.showModal(modal);
620 let out;
621 try {
622 out = await modalInteractionCollector(
623 m,
624 (m: Interaction) =>
625 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
626 (m) => m.customId === "back"
627 );
628 } catch (e) {
629 break;
630 }
631 if (!out) break;
632 if(out.isButton()) break;
633 current.mass = parseInt(out.fields.getTextInputValue("mass"));
634 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000635 }
636 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500637 await i.deferUpdate();
638 current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings");
639 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000640 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500641 }
642 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000643 }
644 case "allowed": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500645 await i.deferUpdate();
646 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000647 case "users": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500648 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
649 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000650 }
651 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500652 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
653 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000654 }
655 case "channels": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500656 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings");
657 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000658 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500659 }
660 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000661 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500662 }
663 }
664
TheCodedProf486bca32023-02-02 16:49:44 -0500665 } while(!closed);
666 return current
TheCodedProf8b3da212023-02-02 15:09:55 -0500667}
668
669const callback = async (interaction: CommandInteraction): Promise<void> => {
670 if (!interaction.guild) return;
671 const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
672 const config = (await client.database.guilds.read(interaction.guild.id)).filters;
673
674 let closed = false;
675
676 const button = new ActionRowBuilder<ButtonBuilder>()
677 .addComponents(
678 new ButtonBuilder()
679 .setCustomId("save")
680 .setLabel("Save")
681 .setStyle(ButtonStyle.Success)
682 )
683
684 do {
685
686 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
687 .addComponents(
688 new StringSelectMenuBuilder()
689 .setCustomId("filter")
690 .setPlaceholder("Select a filter to edit")
691 .addOptions(
692 new StringSelectMenuOptionBuilder()
693 .setLabel("Invites")
694 .setDescription("Automatically delete messages containing server invites")
695 .setValue("invites"),
696 new StringSelectMenuOptionBuilder()
697 .setLabel("Mentions")
698 .setDescription("Deletes messages with excessive mentions")
699 .setValue("mentions"),
700 new StringSelectMenuOptionBuilder()
701 .setLabel("Words")
702 .setDescription("Delete messages containing filtered words")
703 .setValue("words"),
704 new StringSelectMenuOptionBuilder()
705 .setLabel("Malware")
706 .setDescription("Automatically delete files and links containing malware")
707 .setValue("malware"),
708 new StringSelectMenuOptionBuilder()
709 .setLabel("Images")
710 .setDescription("Checks performed on images (NSFW, size checking, etc.)")
711 .setValue("images")
712 )
713 );
714
715 const embed = new EmojiEmbed()
716 .setTitle("Automod Settings")
717 .setDescription(
TheCodedProf486bca32023-02-02 16:49:44 -0500718 `${emojiFromBoolean(config.invite.enabled)} **Invites**\n` +
TheCodedProf8b3da212023-02-02 15:09:55 -0500719 `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` +
720 `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` +
721 `${emojiFromBoolean(config.malware)} **Malware**\n` +
TheCodedProf486bca32023-02-02 16:49:44 -0500722 `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n`
TheCodedProf8b3da212023-02-02 15:09:55 -0500723 )
TheCodedProf486bca32023-02-02 16:49:44 -0500724 .setStatus("Success")
725 .setEmoji("GUILD.SETTINGS.GREEN")
TheCodedProf8b3da212023-02-02 15:09:55 -0500726
727
728 await interaction.editReply({embeds: [embed], components: [selectMenu, button]});
729
730 let i: StringSelectMenuInteraction | ButtonInteraction;
731 try {
732 i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 300000}) as StringSelectMenuInteraction | ButtonInteraction;
733 } catch (e) {
734 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000735 continue;
TheCodedProf8b3da212023-02-02 15:09:55 -0500736 }
TheCodedProf8b3da212023-02-02 15:09:55 -0500737 if(i.isButton()) {
738 await i.deferUpdate();
739 await client.database.guilds.write(interaction.guild.id, {filters: config});
740 } else {
741 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000742 case "invites": {
TheCodedProf486bca32023-02-02 16:49:44 -0500743 await i.deferUpdate();
744 config.invite = await inviteMenu(i, m, config.invite);
TheCodedProf8b3da212023-02-02 15:09:55 -0500745 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000746 }
747 case "mentions": {
TheCodedProf486bca32023-02-02 16:49:44 -0500748 await i.deferUpdate();
749 config.pings = await mentionMenu(i, m, config.pings);
TheCodedProf8b3da212023-02-02 15:09:55 -0500750 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000751 }
752 case "words": {
TheCodedProf486bca32023-02-02 16:49:44 -0500753 await i.deferUpdate();
754 config.wordFilter = await wordMenu(i, m, config.wordFilter);
TheCodedProf8b3da212023-02-02 15:09:55 -0500755 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000756 }
757 case "malware": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500758 await i.deferUpdate();
759 config.malware = !config.malware;
760 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000761 }
762 case "images": {
763 const next = await imageMenu(i, m, config.images);
764 config.images = next;
TheCodedProf8b3da212023-02-02 15:09:55 -0500765 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000766 }
TheCodedProf8b3da212023-02-02 15:09:55 -0500767 }
768 }
769
770 } while(!closed)
771
772};
773
774const check = (interaction: CommandInteraction, _partial: boolean = false) => {
775 const member = interaction.member as Discord.GuildMember;
776 if (!member.permissions.has("ManageMessages"))
777 return "You must have the *Manage Messages* permission to use this command";
778 return true;
779};
780
781export { command };
782export { callback };
783export { check };