blob: 8e006b0227e6b8ce6d5b6e9827953cef5679c2a2 [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));
51
52 let closed;
53 do {
54 let render: string[] = []
55 let mapped: string[] = [];
56 let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder;
57 switch(type) {
58 case "member":
59 menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25);
60 mapped = await Promise.all(ids.map(async (id) => { return (await client.users.fetch(id).then(user => user.tag)) || "Unknown User" }));
61 render = ids.map(id => client.logger.renderUser(id))
62 break;
63 case "role":
64 menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25);
65 mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.roles.fetch(id).then(role => role?.name)) || "Unknown Role" }));
66 render = ids.map(id => client.logger.renderRole(id, interaction.guild!))
67 break;
68 case "channel":
69 menu = new ChannelSelectMenuBuilder().setCustomId("channel").setPlaceholder("Select channels").setMaxValues(25);
70 mapped = await Promise.all(ids.map(async (id) => { return (await interaction.guild!.channels.fetch(id).then(channel => channel?.name)) || "Unknown Channel" }));
71 render = ids.map(id => client.logger.renderChannel(id))
72 break;
73 }
74 const removeOptions = new ActionRowBuilder<StringSelectMenuBuilder>()
75 .addComponents(
76 new StringSelectMenuBuilder()
77 .setCustomId("remove")
78 .setPlaceholder("Remove")
79 .addOptions(mapped.map((name, i) => new StringSelectMenuOptionBuilder().setLabel(name).setValue(ids[i]!)))
80 .setDisabled(ids.length === 0)
81 );
82
83 const embed = new EmojiEmbed()
84 .setTitle(title)
85 .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN"))
86 .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None"))
87 .setStatus("Success");
88 let components: ActionRowBuilder<
89 StringSelectMenuBuilder |
90 ButtonBuilder |
91 ChannelSelectMenuBuilder |
92 UserSelectMenuBuilder |
93 RoleSelectMenuBuilder
94 >[] = [new ActionRowBuilder<typeof menu>().addComponents(menu)]
95 if(ids.length > 0) components.push(removeOptions);
96 components.push(back);
97
98 await interaction.editReply({embeds: [embed], components: components})
99
100 let i: AnySelectMenuInteraction | ButtonInteraction;
101 try {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500102 i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000});
TheCodedProf486bca32023-02-02 16:49:44 -0500103 } catch(e) {
104 closed = true;
105 break;
106 }
107
108 if(i.isButton()) {
109 await i.deferUpdate();
110 if(i.customId === "back") {
111 closed = true;
112 break;
113 }
114 } else if(i.isStringSelectMenu()) {
115 await i.deferUpdate();
116 if(i.customId === "remove") {
117 ids = ids.filter(id => id !== (i as StringSelectMenuInteraction).values[0]);
118 if(ids.length === 0) {
119 menu.data.disabled = true;
120 }
121 }
122 } else {
123 await i.deferUpdate();
124 if(i.customId === "user") {
125 ids = ids.concat((i as UserSelectMenuInteraction).values);
126 } else if(i.customId === "role") {
127 ids = ids.concat((i as RoleSelectMenuInteraction).values);
128 } else if(i.customId === "channel") {
129 ids = ids.concat((i as ChannelSelectMenuInteraction).values);
130 }
131 }
132
133 } while(!closed)
134 return ids;
135}
TheCodedProf8b3da212023-02-02 15:09:55 -0500136
137const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
138 NSFW: boolean,
139 size: boolean
140}): Promise<{NSFW: boolean, size: boolean}> => {
141 let closed = false;
142 do {
143 const options = new ActionRowBuilder<ButtonBuilder>()
144 .addComponents(
145 new ButtonBuilder()
146 .setCustomId("back")
147 .setLabel("Back")
148 .setStyle(ButtonStyle.Secondary)
149 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
150 new ButtonBuilder()
151 .setCustomId("nsfw")
152 .setLabel("NSFW")
153 .setStyle(current.NSFW ? ButtonStyle.Success : ButtonStyle.Danger)
154 .setEmoji(emojiFromBoolean(current.NSFW, "id") as APIMessageComponentEmoji),
155 new ButtonBuilder()
156 .setCustomId("size")
157 .setLabel("Size")
158 .setStyle(current.size ? ButtonStyle.Success : ButtonStyle.Danger)
159 .setEmoji(emojiFromBoolean(current.size, "id") as APIMessageComponentEmoji)
160 )
161
162 const embed = new EmojiEmbed()
163 .setTitle("Image Settings")
164 .setDescription(
165 `${emojiFromBoolean(current.NSFW)} **NSFW**\n` +
166 `${emojiFromBoolean(current.size)} **Size**\n`
167 )
168
169 await interaction.editReply({embeds: [embed], components: [options]});
170
171 let i: ButtonInteraction;
172 try {
173 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
174 } catch (e) {
175 return current;
176 }
177 await i.deferUpdate();
178 switch(i.customId) {
179 case "back":
180 closed = true;
181 break;
182 case "nsfw":
183 current.NSFW = !current.NSFW;
184 break;
185 case "size":
186 current.size = !current.size;
187 break;
188 }
189 } while(!closed);
190 return current;
191}
192
193const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
194 enabled: boolean,
195 words: {strict: string[], loose: string[]},
TheCodedProf486bca32023-02-02 16:49:44 -0500196 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500197}): Promise<{
198 enabled: boolean,
199 words: {strict: string[], loose: string[]},
TheCodedProf486bca32023-02-02 16:49:44 -0500200 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500201}> => {
TheCodedProf486bca32023-02-02 16:49:44 -0500202 let closed = false;
203 do {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500204 const buttons = new ActionRowBuilder<ButtonBuilder>()
205 .addComponents(
206 new ButtonBuilder()
207 .setCustomId("back")
208 .setLabel("Back")
209 .setStyle(ButtonStyle.Secondary)
210 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
211 new ButtonBuilder()
212 .setCustomId("enabled")
213 .setLabel("Enabled")
214 .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
215 .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji),
216 );
217
218 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
219 .addComponents(
220 new StringSelectMenuBuilder()
221 .setCustomId("edit")
222 .setPlaceholder("Edit... ")
223 .addOptions(
224 new StringSelectMenuOptionBuilder()
225 .setLabel("Words")
226 .setDescription("Edit your list of words to filter")
227 .setValue("words"),
228 new StringSelectMenuOptionBuilder()
229 .setLabel("Allowed Users")
230 .setDescription("Users who will be unaffected by the word filter")
231 .setValue("allowedUsers"),
232 new StringSelectMenuOptionBuilder()
233 .setLabel("Allowed Roles")
234 .setDescription("Roles that will be unaffected by the word filter")
235 .setValue("allowedRoles"),
236 new StringSelectMenuOptionBuilder()
237 .setLabel("Allowed Channels")
238 .setDescription("Channels where the word filter will not apply")
239 .setValue("allowedChannels")
240 )
241 .setDisabled(!current.enabled)
242 );
243
244 const embed = new EmojiEmbed()
245 .setTitle("Word Filters")
246 .setDescription(
247 `${emojiFromBoolean(current.enabled)} **Enabled**\n` +
248 `**Strict Words:** ${listToAndMore(current.words.strict, 5)}\n` +
249 `**Loose Words:** ${listToAndMore(current.words.loose, 5)}\n\n` +
250 `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
251 `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
252 `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
253 )
254 .setStatus("Success")
255 .setEmoji("GUILD.SETTINGS.GREEN")
256
257 await interaction.editReply({embeds: [embed], components: [selectMenu, buttons]});
258
259 let i: ButtonInteraction | StringSelectMenuInteraction;
260 try {
261 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
262 } catch (e) {
263 closed = true;
264 break;
265 }
266
267 if(i.isButton()) {
268 await i.deferUpdate();
269 switch(i.customId) {
270 case "back":
271 closed = true;
272 break;
273 case "enabled":
274 current.enabled = !current.enabled;
275 break;
276 }
277 } else {
278 switch(i.values[0]) {
279 case "words":
280 await interaction.editReply({embeds: [new EmojiEmbed()
281 .setTitle("Word Filter")
282 .setDescription("Modal opened. If you can't see it, click back and try again.")
283 .setStatus("Success")
284 .setEmoji("GUILD.SETTINGS.GREEN")
285 ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
286 .setLabel("Back")
287 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
288 .setStyle(ButtonStyle.Primary)
289 .setCustomId("back")
290 )]})
291 const modal = new ModalBuilder()
292 .setTitle("Word Filter")
293 .setCustomId("wordFilter")
294 .addComponents(
295 new ActionRowBuilder<TextInputBuilder>()
296 .addComponents(
297 new TextInputBuilder()
298 .setCustomId("wordStrict")
299 .setLabel("Strict Words")
300 .setPlaceholder("Matches anywhere in the message, including surrounded by other characters")
301 .setValue(current.words.strict.join(", ") ?? "")
302 .setStyle(TextInputStyle.Paragraph)
303 .setRequired(false)
304 ),
305 new ActionRowBuilder<TextInputBuilder>()
306 .addComponents(
307 new TextInputBuilder()
308 .setCustomId("wordLoose")
309 .setLabel("Loose Words")
310 .setPlaceholder("Matches only if the word is by itself, surrounded by spaces or punctuation")
311 .setValue(current.words.loose.join(", ") ?? "")
312 .setStyle(TextInputStyle.Paragraph)
313 .setRequired(false)
314 )
315 )
316
317 await i.showModal(modal);
318 let out;
319 try {
320 out = await modalInteractionCollector(
321 m,
322 (m: Interaction) =>
323 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
324 (m) => m.customId === "back"
325 );
326 } catch (e) {
327 break;
328 }
329 if (!out) break;
330 if(out.isButton()) break;
331 current.words.strict = out.fields.getTextInputValue("wordStrict")
332 .split(",").map(s => s.trim()).filter(s => s.length > 0);
333 current.words.loose = out.fields.getTextInputValue("wordLoose")
334 .split(",").map(s => s.trim()).filter(s => s.length > 0);
335 break;
336 case "allowedUsers":
337 await i.deferUpdate();
338 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter");
339 break;
340 case "allowedRoles":
341 await i.deferUpdate();
342 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter");
343 break;
344 case "allowedChannels":
345 await i.deferUpdate();
346 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter");
347 break;
348 }
349 }
TheCodedProf486bca32023-02-02 16:49:44 -0500350 } while(!closed);
351 return current;
TheCodedProf8b3da212023-02-02 15:09:55 -0500352}
353
354const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
355 enabled: boolean,
TheCodedProf486bca32023-02-02 16:49:44 -0500356 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500357}): Promise<{
358 enabled: boolean,
TheCodedProf486bca32023-02-02 16:49:44 -0500359 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500360}> => {
361
TheCodedProf486bca32023-02-02 16:49:44 -0500362 let closed = false;
363 do {
364 const buttons = new ActionRowBuilder<ButtonBuilder>()
365 .addComponents(
366 new ButtonBuilder()
367 .setCustomId("back")
368 .setLabel("Back")
369 .setStyle(ButtonStyle.Secondary)
370 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
371 new ButtonBuilder()
372 .setCustomId("enabled")
373 .setLabel(current.enabled ? "Enabled" : "Disabled")
374 .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
375 .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji)
376 );
377 const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
378 .addComponents(
379 new StringSelectMenuBuilder()
380 .setCustomId("toEdit")
381 .setPlaceholder("Edit your allow list")
382 .addOptions(
383 new StringSelectMenuOptionBuilder()
384 .setLabel("Users")
385 .setDescription("Users that are allowed to send invites")
386 .setValue("users"),
387 new StringSelectMenuOptionBuilder()
388 .setLabel("Roles")
389 .setDescription("Roles that are allowed to send invites")
390 .setValue("roles"),
391 new StringSelectMenuOptionBuilder()
392 .setLabel("Channels")
393 .setDescription("Channels that anyone is allowed to send invites in")
394 .setValue("channels")
395 ).setDisabled(!current.enabled)
396 )
397
398 const embed = new EmojiEmbed()
399 .setTitle("Invite Settings")
400 .setDescription(
401 "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + `\n\n` +
402 `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` +
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500403 `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
404 `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
405 `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
TheCodedProf486bca32023-02-02 16:49:44 -0500406 )
407 .setStatus("Success")
408 .setEmoji("GUILD.SETTINGS.GREEN")
409
410
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500411 await interaction.editReply({embeds: [embed], components: [menu, buttons]});
TheCodedProf486bca32023-02-02 16:49:44 -0500412
413 let i: ButtonInteraction | StringSelectMenuInteraction;
414 try {
415 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
416 } catch (e) {
417 return current;
418 }
419
420 if(i.isButton()) {
421 await i.deferUpdate();
422 switch(i.customId) {
423 case "back":
424 closed = true;
425 break;
426 case "enabled":
427 current.enabled = !current.enabled;
428 break;
429 }
430 } else {
431 await i.deferUpdate();
432 switch(i.values[0]) {
433 case "users":
434 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings");
435 break;
436 case "roles":
437 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings");
438 break;
439 case "channels":
440 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings");
441 break;
442 }
443 }
444
445 } while(!closed);
446 return current;
TheCodedProf8b3da212023-02-02 15:09:55 -0500447}
448
449const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
450 mass: number,
451 everyone: boolean,
452 roles: boolean,
453 allowed: {
454 roles: string[],
455 rolesToMention: string[],
456 users: string[],
457 channels: string[]
458 }
459}): Promise<{
460 mass: number,
461 everyone: boolean,
462 roles: boolean,
463 allowed: {
464 roles: string[],
465 rolesToMention: string[],
466 users: string[],
467 channels: string[]
468 }
469}> => {
TheCodedProf486bca32023-02-02 16:49:44 -0500470 let closed = false;
471
472 do {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500473
474 const buttons = new ActionRowBuilder<ButtonBuilder>()
475 .addComponents(
476 new ButtonBuilder()
477 .setCustomId("back")
478 .setLabel("Back")
479 .setStyle(ButtonStyle.Secondary)
480 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
481 new ButtonBuilder()
482 .setCustomId("everyone")
483 .setLabel(current.everyone ? "Everyone" : "No one")
484 .setStyle(current.everyone ? ButtonStyle.Success : ButtonStyle.Danger)
485 .setEmoji(emojiFromBoolean(current.everyone, "id") as APIMessageComponentEmoji),
486 new ButtonBuilder()
487 .setCustomId("roles")
488 .setLabel(current.roles ? "Roles" : "No roles")
489 .setStyle(current.roles ? ButtonStyle.Success : ButtonStyle.Danger)
490 .setEmoji(emojiFromBoolean(current.roles, "id") as APIMessageComponentEmoji)
491 );
492 const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
493 .addComponents(
494 new StringSelectMenuBuilder()
495 .setCustomId("toEdit")
496 .setPlaceholder("Edit mention settings")
497 .addOptions(
498 new StringSelectMenuOptionBuilder()
499 .setLabel("Mass Mention Amount")
500 .setDescription("The amount of mentions before the bot will delete the message")
501 .setValue("mass"),
502 new StringSelectMenuOptionBuilder()
503 .setLabel("Roles")
504 .setDescription("Roles that are able to be mentioned")
505 .setValue("roles"),
506 )
507 )
508
509 const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
510 .addComponents(
511 new StringSelectMenuBuilder()
512 .setCustomId("allowed")
513 .setPlaceholder("Edit exceptions")
514 .addOptions(
515 new StringSelectMenuOptionBuilder()
516 .setLabel("Users")
517 .setDescription("Users that are unaffected by the mention filter")
518 .setValue("users"),
519 new StringSelectMenuOptionBuilder()
520 .setLabel("Roles")
521 .setDescription("Roles that are unaffected by the mention filter")
522 .setValue("roles"),
523 new StringSelectMenuOptionBuilder()
524 .setLabel("Channels")
525 .setDescription("Channels where anyone is unaffected by the mention filter")
526 .setValue("channels")
527 )
528 )
529
530 const embed = new EmojiEmbed()
531 .setTitle("Mention Settings")
532 .setDescription(
533 `Log when members mention:\n` +
534 `${emojiFromBoolean(true)} **${current.mass}+ members** in one message\n` +
535 `${emojiFromBoolean(current.everyone)} **Everyone**\n` +
536 `${emojiFromBoolean(current.roles)} **Roles**\n` +
537 (current.allowed.rolesToMention.length > 0 ? `> *Except for ${listToAndMore(current.allowed.rolesToMention.map(r => `<@&${r}>`), 3)}*\n` : "") +
538 "\n" +
539 `Except if...\n` +
540 `> ${current.allowed.users.length > 0 ? `Member is: ${listToAndMore(current.allowed.users.map(u => `<@${u}>`), 3)}\n` : ""}` +
541 `> ${current.allowed.roles.length > 0 ? `Member has role: ${listToAndMore(current.allowed.roles.map(r => `<@&${r}>`), 3)}\n` : ""}` +
542 `> ${current.allowed.channels.length > 0 ? `In channel: ${listToAndMore(current.allowed.channels.map(c => `<#${c}>`), 3)}\n` : ""}`
543 )
544 .setStatus("Success")
545 .setEmoji("GUILD.SETTINGS.GREEN")
546
547 await interaction.editReply({embeds: [embed], components: [menu, allowedMenu, buttons]});
548
549 let i: ButtonInteraction | StringSelectMenuInteraction;
550 try {
551 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
552 } catch (e) {
553 closed = true;
554 break;
555 }
556
557 if(i.isButton()) {
558 await i.deferUpdate();
559 switch (i.customId) {
560 case "back":
561 closed = true;
562 break;
563 case "everyone":
564 current.everyone = !current.everyone;
565 break;
566 case "roles":
567 current.roles = !current.roles;
568 break;
569 }
570 } else {
571 switch (i.customId) {
572 case "toEdit":
573 switch (i.values[0]) {
574 case "mass":
575 await interaction.editReply({embeds: [new EmojiEmbed()
576 .setTitle("Word Filter")
577 .setDescription("Modal opened. If you can't see it, click back and try again.")
578 .setStatus("Success")
579 .setEmoji("GUILD.SETTINGS.GREEN")
580 ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
581 .setLabel("Back")
582 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
583 .setStyle(ButtonStyle.Primary)
584 .setCustomId("back")
585 )]})
586 const modal = new ModalBuilder()
587 .setTitle("Mass Mention Amount")
588 .setCustomId("mass")
589 .addComponents(
590 new ActionRowBuilder<TextInputBuilder>()
591 .addComponents(
592 new TextInputBuilder()
593 .setCustomId("mass")
594 .setPlaceholder("Amount")
595 .setMinLength(1)
596 .setMaxLength(3)
597 .setStyle(TextInputStyle.Short)
598 )
599 )
600 await i.showModal(modal);
601 let out;
602 try {
603 out = await modalInteractionCollector(
604 m,
605 (m: Interaction) =>
606 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
607 (m) => m.customId === "back"
608 );
609 } catch (e) {
610 break;
611 }
612 if (!out) break;
613 if(out.isButton()) break;
614 current.mass = parseInt(out.fields.getTextInputValue("mass"));
615 break;
616 case "roles":
617 await i.deferUpdate();
618 current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings");
619 break;
620 }
621 break;
622 case "allowed":
623 await i.deferUpdate();
624 switch (i.values[0]) {
625 case "users":
626 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
627 break;
628 case "roles":
629 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
630 break;
631 case "channels":
632 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings");
633 break;
634 }
635 break;
636 }
637 }
638
TheCodedProf486bca32023-02-02 16:49:44 -0500639 } while(!closed);
640 return current
TheCodedProf8b3da212023-02-02 15:09:55 -0500641}
642
643const callback = async (interaction: CommandInteraction): Promise<void> => {
644 if (!interaction.guild) return;
645 const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
646 const config = (await client.database.guilds.read(interaction.guild.id)).filters;
647
648 let closed = false;
649
650 const button = new ActionRowBuilder<ButtonBuilder>()
651 .addComponents(
652 new ButtonBuilder()
653 .setCustomId("save")
654 .setLabel("Save")
655 .setStyle(ButtonStyle.Success)
656 )
657
658 do {
659
660 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
661 .addComponents(
662 new StringSelectMenuBuilder()
663 .setCustomId("filter")
664 .setPlaceholder("Select a filter to edit")
665 .addOptions(
666 new StringSelectMenuOptionBuilder()
667 .setLabel("Invites")
668 .setDescription("Automatically delete messages containing server invites")
669 .setValue("invites"),
670 new StringSelectMenuOptionBuilder()
671 .setLabel("Mentions")
672 .setDescription("Deletes messages with excessive mentions")
673 .setValue("mentions"),
674 new StringSelectMenuOptionBuilder()
675 .setLabel("Words")
676 .setDescription("Delete messages containing filtered words")
677 .setValue("words"),
678 new StringSelectMenuOptionBuilder()
679 .setLabel("Malware")
680 .setDescription("Automatically delete files and links containing malware")
681 .setValue("malware"),
682 new StringSelectMenuOptionBuilder()
683 .setLabel("Images")
684 .setDescription("Checks performed on images (NSFW, size checking, etc.)")
685 .setValue("images")
686 )
687 );
688
689 const embed = new EmojiEmbed()
690 .setTitle("Automod Settings")
691 .setDescription(
TheCodedProf486bca32023-02-02 16:49:44 -0500692 `${emojiFromBoolean(config.invite.enabled)} **Invites**\n` +
TheCodedProf8b3da212023-02-02 15:09:55 -0500693 `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` +
694 `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` +
695 `${emojiFromBoolean(config.malware)} **Malware**\n` +
TheCodedProf486bca32023-02-02 16:49:44 -0500696 `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n`
TheCodedProf8b3da212023-02-02 15:09:55 -0500697 )
TheCodedProf486bca32023-02-02 16:49:44 -0500698 .setStatus("Success")
699 .setEmoji("GUILD.SETTINGS.GREEN")
TheCodedProf8b3da212023-02-02 15:09:55 -0500700
701
702 await interaction.editReply({embeds: [embed], components: [selectMenu, button]});
703
704 let i: StringSelectMenuInteraction | ButtonInteraction;
705 try {
706 i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 300000}) as StringSelectMenuInteraction | ButtonInteraction;
707 } catch (e) {
708 closed = true;
709 return;
710 }
711 if(!i) return;
712 if(i.isButton()) {
713 await i.deferUpdate();
714 await client.database.guilds.write(interaction.guild.id, {filters: config});
715 } else {
716 switch(i.values[0]) {
717 case "invites":
TheCodedProf486bca32023-02-02 16:49:44 -0500718 await i.deferUpdate();
719 config.invite = await inviteMenu(i, m, config.invite);
TheCodedProf8b3da212023-02-02 15:09:55 -0500720 break;
721 case "mentions":
TheCodedProf486bca32023-02-02 16:49:44 -0500722 await i.deferUpdate();
723 config.pings = await mentionMenu(i, m, config.pings);
TheCodedProf8b3da212023-02-02 15:09:55 -0500724 break;
725 case "words":
TheCodedProf486bca32023-02-02 16:49:44 -0500726 await i.deferUpdate();
727 config.wordFilter = await wordMenu(i, m, config.wordFilter);
TheCodedProf8b3da212023-02-02 15:09:55 -0500728 break;
729 case "malware":
730 await i.deferUpdate();
731 config.malware = !config.malware;
732 break;
733 case "images":
734 let next = await imageMenu(i, m, config.images);
735 if(next) config.images = next;
736 break;
737 }
738 }
739
740 } while(!closed)
741
742};
743
744const check = (interaction: CommandInteraction, _partial: boolean = false) => {
745 const member = interaction.member as Discord.GuildMember;
746 if (!member.permissions.has("ManageMessages"))
747 return "You must have the *Manage Messages* permission to use this command";
748 return true;
749};
750
751export { command };
752export { callback };
753export { check };