blob: c3cac04b65f0258c90e6772854a07da999a7c053 [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";
TheCodedProf1f675042023-02-16 17:01:29 -050032import listToAndMore from "../../utils/listToAndMore.js";
TheCodedProf8b3da212023-02-02 15:09:55 -050033
TheCodedProf486bca32023-02-02 16:49:44 -050034
TheCodedProf8b3da212023-02-02 15:09:55 -050035const command = (builder: SlashCommandSubcommandBuilder) =>
36 builder.setName("automod").setDescription("Setting for automatic moderation features");
37
38
39const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id);
40
TheCodedProf486bca32023-02-02 16:49:44 -050041const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise<string[]> => {
42
43 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 -050044 let closed;
45 do {
46 let render: string[] = []
47 let mapped: string[] = [];
48 let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder;
49 switch(type) {
PineaFanb0d0c242023-02-05 10:59:45 +000050 case "member": {
TheCodedProf486bca32023-02-02 16:49:44 -050051 menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25);
52 mapped = await Promise.all(ids.map(async (id) => { return (await client.users.fetch(id).then(user => user.tag)) || "Unknown User" }));
53 render = ids.map(id => client.logger.renderUser(id))
54 break;
PineaFanb0d0c242023-02-05 10:59:45 +000055 }
56 case "role": {
TheCodedProf486bca32023-02-02 16:49:44 -050057 menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25);
PineaFanb0d0c242023-02-05 10:59:45 +000058 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 -050059 render = ids.map(id => client.logger.renderRole(id, interaction.guild!))
60 break;
PineaFanb0d0c242023-02-05 10:59:45 +000061 }
62 case "channel": {
TheCodedProf486bca32023-02-02 16:49:44 -050063 menu = new ChannelSelectMenuBuilder().setCustomId("channel").setPlaceholder("Select channels").setMaxValues(25);
PineaFanb0d0c242023-02-05 10:59:45 +000064 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 -050065 render = ids.map(id => client.logger.renderChannel(id))
66 break;
PineaFanb0d0c242023-02-05 10:59:45 +000067 }
TheCodedProf486bca32023-02-02 16:49:44 -050068 }
69 const removeOptions = new ActionRowBuilder<StringSelectMenuBuilder>()
70 .addComponents(
71 new StringSelectMenuBuilder()
72 .setCustomId("remove")
73 .setPlaceholder("Remove")
74 .addOptions(mapped.map((name, i) => new StringSelectMenuOptionBuilder().setLabel(name).setValue(ids[i]!)))
75 .setDisabled(ids.length === 0)
76 );
77
78 const embed = new EmojiEmbed()
79 .setTitle(title)
80 .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN"))
81 .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None"))
82 .setStatus("Success");
PineaFanb0d0c242023-02-05 10:59:45 +000083 const components: ActionRowBuilder<
TheCodedProf486bca32023-02-02 16:49:44 -050084 StringSelectMenuBuilder |
85 ButtonBuilder |
86 ChannelSelectMenuBuilder |
87 UserSelectMenuBuilder |
88 RoleSelectMenuBuilder
89 >[] = [new ActionRowBuilder<typeof menu>().addComponents(menu)]
90 if(ids.length > 0) components.push(removeOptions);
91 components.push(back);
92
93 await interaction.editReply({embeds: [embed], components: components})
94
95 let i: AnySelectMenuInteraction | ButtonInteraction;
96 try {
TheCodedProf5b53a8c2023-02-03 15:40:26 -050097 i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000});
TheCodedProf486bca32023-02-02 16:49:44 -050098 } catch(e) {
99 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000100 continue;
TheCodedProf486bca32023-02-02 16:49:44 -0500101 }
102
103 if(i.isButton()) {
104 await i.deferUpdate();
105 if(i.customId === "back") {
106 closed = true;
107 break;
108 }
109 } else if(i.isStringSelectMenu()) {
110 await i.deferUpdate();
111 if(i.customId === "remove") {
112 ids = ids.filter(id => id !== (i as StringSelectMenuInteraction).values[0]);
113 if(ids.length === 0) {
114 menu.data.disabled = true;
115 }
116 }
117 } else {
118 await i.deferUpdate();
119 if(i.customId === "user") {
120 ids = ids.concat((i as UserSelectMenuInteraction).values);
121 } else if(i.customId === "role") {
122 ids = ids.concat((i as RoleSelectMenuInteraction).values);
123 } else if(i.customId === "channel") {
124 ids = ids.concat((i as ChannelSelectMenuInteraction).values);
125 }
126 }
127
128 } while(!closed)
129 return ids;
130}
TheCodedProf8b3da212023-02-02 15:09:55 -0500131
132const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
133 NSFW: boolean,
134 size: boolean
135}): Promise<{NSFW: boolean, size: boolean}> => {
136 let closed = false;
137 do {
138 const options = new ActionRowBuilder<ButtonBuilder>()
139 .addComponents(
140 new ButtonBuilder()
141 .setCustomId("back")
142 .setLabel("Back")
143 .setStyle(ButtonStyle.Secondary)
144 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
145 new ButtonBuilder()
146 .setCustomId("nsfw")
147 .setLabel("NSFW")
148 .setStyle(current.NSFW ? ButtonStyle.Success : ButtonStyle.Danger)
149 .setEmoji(emojiFromBoolean(current.NSFW, "id") as APIMessageComponentEmoji),
150 new ButtonBuilder()
151 .setCustomId("size")
152 .setLabel("Size")
153 .setStyle(current.size ? ButtonStyle.Success : ButtonStyle.Danger)
154 .setEmoji(emojiFromBoolean(current.size, "id") as APIMessageComponentEmoji)
155 )
156
157 const embed = new EmojiEmbed()
158 .setTitle("Image Settings")
159 .setDescription(
160 `${emojiFromBoolean(current.NSFW)} **NSFW**\n` +
161 `${emojiFromBoolean(current.size)} **Size**\n`
162 )
163
164 await interaction.editReply({embeds: [embed], components: [options]});
165
166 let i: ButtonInteraction;
167 try {
168 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
169 } catch (e) {
170 return current;
171 }
172 await i.deferUpdate();
173 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000174 case "back": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500175 closed = true;
176 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000177 }
178 case "nsfw": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500179 current.NSFW = !current.NSFW;
180 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000181 }
182 case "size": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500183 current.size = !current.size;
184 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000185 }
TheCodedProf8b3da212023-02-02 15:09:55 -0500186 }
187 } while(!closed);
188 return current;
189}
190
191const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
192 enabled: boolean,
193 words: {strict: string[], loose: string[]},
TheCodedProf486bca32023-02-02 16:49:44 -0500194 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500195}): Promise<{
196 enabled: boolean,
197 words: {strict: string[], loose: string[]},
TheCodedProf486bca32023-02-02 16:49:44 -0500198 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500199}> => {
TheCodedProf486bca32023-02-02 16:49:44 -0500200 let closed = false;
201 do {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500202 const buttons = new ActionRowBuilder<ButtonBuilder>()
203 .addComponents(
204 new ButtonBuilder()
205 .setCustomId("back")
206 .setLabel("Back")
207 .setStyle(ButtonStyle.Secondary)
208 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
209 new ButtonBuilder()
210 .setCustomId("enabled")
211 .setLabel("Enabled")
212 .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
213 .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji),
214 );
215
216 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
217 .addComponents(
218 new StringSelectMenuBuilder()
219 .setCustomId("edit")
220 .setPlaceholder("Edit... ")
221 .addOptions(
222 new StringSelectMenuOptionBuilder()
223 .setLabel("Words")
224 .setDescription("Edit your list of words to filter")
225 .setValue("words"),
226 new StringSelectMenuOptionBuilder()
227 .setLabel("Allowed Users")
228 .setDescription("Users who will be unaffected by the word filter")
229 .setValue("allowedUsers"),
230 new StringSelectMenuOptionBuilder()
231 .setLabel("Allowed Roles")
232 .setDescription("Roles that will be unaffected by the word filter")
233 .setValue("allowedRoles"),
234 new StringSelectMenuOptionBuilder()
235 .setLabel("Allowed Channels")
236 .setDescription("Channels where the word filter will not apply")
237 .setValue("allowedChannels")
238 )
239 .setDisabled(!current.enabled)
240 );
241
242 const embed = new EmojiEmbed()
243 .setTitle("Word Filters")
244 .setDescription(
245 `${emojiFromBoolean(current.enabled)} **Enabled**\n` +
246 `**Strict Words:** ${listToAndMore(current.words.strict, 5)}\n` +
247 `**Loose Words:** ${listToAndMore(current.words.loose, 5)}\n\n` +
248 `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
249 `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
250 `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
251 )
252 .setStatus("Success")
253 .setEmoji("GUILD.SETTINGS.GREEN")
254
255 await interaction.editReply({embeds: [embed], components: [selectMenu, buttons]});
256
257 let i: ButtonInteraction | StringSelectMenuInteraction;
258 try {
259 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
260 } catch (e) {
261 closed = true;
262 break;
263 }
264
265 if(i.isButton()) {
266 await i.deferUpdate();
267 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000268 case "back": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500269 closed = true;
270 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000271 }
272 case "enabled": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500273 current.enabled = !current.enabled;
274 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000275 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500276 }
277 } else {
278 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000279 case "words": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500280 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")
PineaFanb0d0c242023-02-05 10:59:45 +0000301 .setValue(current.words.strict.join(", "))
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500302 .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")
PineaFanb0d0c242023-02-05 10:59:45 +0000311 .setValue(current.words.loose.join(", "))
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500312 .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;
PineaFanb0d0c242023-02-05 10:59:45 +0000336 }
337 case "allowedUsers": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500338 await i.deferUpdate();
339 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter");
340 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000341 }
342 case "allowedRoles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500343 await i.deferUpdate();
344 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter");
345 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000346 }
347 case "allowedChannels": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500348 await i.deferUpdate();
349 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter");
350 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000351 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500352 }
353 }
TheCodedProf486bca32023-02-02 16:49:44 -0500354 } while(!closed);
355 return current;
TheCodedProf8b3da212023-02-02 15:09:55 -0500356}
357
358const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
359 enabled: boolean,
TheCodedProf486bca32023-02-02 16:49:44 -0500360 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500361}): Promise<{
362 enabled: boolean,
TheCodedProf486bca32023-02-02 16:49:44 -0500363 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500364}> => {
365
TheCodedProf486bca32023-02-02 16:49:44 -0500366 let closed = false;
367 do {
368 const buttons = new ActionRowBuilder<ButtonBuilder>()
369 .addComponents(
370 new ButtonBuilder()
371 .setCustomId("back")
372 .setLabel("Back")
373 .setStyle(ButtonStyle.Secondary)
374 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
375 new ButtonBuilder()
376 .setCustomId("enabled")
377 .setLabel(current.enabled ? "Enabled" : "Disabled")
378 .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
379 .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji)
380 );
381 const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
382 .addComponents(
383 new StringSelectMenuBuilder()
384 .setCustomId("toEdit")
385 .setPlaceholder("Edit your allow list")
386 .addOptions(
387 new StringSelectMenuOptionBuilder()
388 .setLabel("Users")
389 .setDescription("Users that are allowed to send invites")
390 .setValue("users"),
391 new StringSelectMenuOptionBuilder()
392 .setLabel("Roles")
393 .setDescription("Roles that are allowed to send invites")
394 .setValue("roles"),
395 new StringSelectMenuOptionBuilder()
396 .setLabel("Channels")
397 .setDescription("Channels that anyone is allowed to send invites in")
398 .setValue("channels")
399 ).setDisabled(!current.enabled)
400 )
401
402 const embed = new EmojiEmbed()
403 .setTitle("Invite Settings")
404 .setDescription(
405 "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + `\n\n` +
406 `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` +
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500407 `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
408 `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
409 `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
TheCodedProf486bca32023-02-02 16:49:44 -0500410 )
411 .setStatus("Success")
412 .setEmoji("GUILD.SETTINGS.GREEN")
413
414
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500415 await interaction.editReply({embeds: [embed], components: [menu, buttons]});
TheCodedProf486bca32023-02-02 16:49:44 -0500416
417 let i: ButtonInteraction | StringSelectMenuInteraction;
418 try {
419 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
420 } catch (e) {
421 return current;
422 }
423
424 if(i.isButton()) {
425 await i.deferUpdate();
426 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000427 case "back": {
TheCodedProf486bca32023-02-02 16:49:44 -0500428 closed = true;
429 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000430 }
431 case "enabled": {
TheCodedProf486bca32023-02-02 16:49:44 -0500432 current.enabled = !current.enabled;
433 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000434 }
TheCodedProf486bca32023-02-02 16:49:44 -0500435 }
436 } else {
437 await i.deferUpdate();
438 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000439 case "users": {
TheCodedProf486bca32023-02-02 16:49:44 -0500440 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings");
441 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000442 }
443 case "roles": {
TheCodedProf486bca32023-02-02 16:49:44 -0500444 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings");
445 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000446 }
447 case "channels": {
TheCodedProf486bca32023-02-02 16:49:44 -0500448 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings");
449 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000450 }
TheCodedProf486bca32023-02-02 16:49:44 -0500451 }
452 }
453
454 } while(!closed);
455 return current;
TheCodedProf8b3da212023-02-02 15:09:55 -0500456}
457
458const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
459 mass: number,
460 everyone: boolean,
461 roles: boolean,
462 allowed: {
463 roles: string[],
464 rolesToMention: string[],
465 users: string[],
466 channels: string[]
467 }
468}): Promise<{
469 mass: number,
470 everyone: boolean,
471 roles: boolean,
472 allowed: {
473 roles: string[],
474 rolesToMention: string[],
475 users: string[],
476 channels: string[]
477 }
478}> => {
TheCodedProf486bca32023-02-02 16:49:44 -0500479 let closed = false;
480
481 do {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500482
483 const buttons = new ActionRowBuilder<ButtonBuilder>()
484 .addComponents(
485 new ButtonBuilder()
486 .setCustomId("back")
487 .setLabel("Back")
488 .setStyle(ButtonStyle.Secondary)
489 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
490 new ButtonBuilder()
491 .setCustomId("everyone")
492 .setLabel(current.everyone ? "Everyone" : "No one")
493 .setStyle(current.everyone ? ButtonStyle.Success : ButtonStyle.Danger)
494 .setEmoji(emojiFromBoolean(current.everyone, "id") as APIMessageComponentEmoji),
495 new ButtonBuilder()
496 .setCustomId("roles")
497 .setLabel(current.roles ? "Roles" : "No roles")
498 .setStyle(current.roles ? ButtonStyle.Success : ButtonStyle.Danger)
499 .setEmoji(emojiFromBoolean(current.roles, "id") as APIMessageComponentEmoji)
500 );
501 const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
502 .addComponents(
503 new StringSelectMenuBuilder()
504 .setCustomId("toEdit")
505 .setPlaceholder("Edit mention settings")
506 .addOptions(
507 new StringSelectMenuOptionBuilder()
508 .setLabel("Mass Mention Amount")
509 .setDescription("The amount of mentions before the bot will delete the message")
510 .setValue("mass"),
511 new StringSelectMenuOptionBuilder()
512 .setLabel("Roles")
513 .setDescription("Roles that are able to be mentioned")
514 .setValue("roles"),
515 )
516 )
517
518 const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
519 .addComponents(
520 new StringSelectMenuBuilder()
521 .setCustomId("allowed")
522 .setPlaceholder("Edit exceptions")
523 .addOptions(
524 new StringSelectMenuOptionBuilder()
525 .setLabel("Users")
526 .setDescription("Users that are unaffected by the mention filter")
527 .setValue("users"),
528 new StringSelectMenuOptionBuilder()
529 .setLabel("Roles")
530 .setDescription("Roles that are unaffected by the mention filter")
531 .setValue("roles"),
532 new StringSelectMenuOptionBuilder()
533 .setLabel("Channels")
534 .setDescription("Channels where anyone is unaffected by the mention filter")
535 .setValue("channels")
536 )
537 )
538
539 const embed = new EmojiEmbed()
540 .setTitle("Mention Settings")
541 .setDescription(
542 `Log when members mention:\n` +
543 `${emojiFromBoolean(true)} **${current.mass}+ members** in one message\n` +
544 `${emojiFromBoolean(current.everyone)} **Everyone**\n` +
545 `${emojiFromBoolean(current.roles)} **Roles**\n` +
546 (current.allowed.rolesToMention.length > 0 ? `> *Except for ${listToAndMore(current.allowed.rolesToMention.map(r => `<@&${r}>`), 3)}*\n` : "") +
547 "\n" +
548 `Except if...\n` +
549 `> ${current.allowed.users.length > 0 ? `Member is: ${listToAndMore(current.allowed.users.map(u => `<@${u}>`), 3)}\n` : ""}` +
550 `> ${current.allowed.roles.length > 0 ? `Member has role: ${listToAndMore(current.allowed.roles.map(r => `<@&${r}>`), 3)}\n` : ""}` +
551 `> ${current.allowed.channels.length > 0 ? `In channel: ${listToAndMore(current.allowed.channels.map(c => `<#${c}>`), 3)}\n` : ""}`
552 )
553 .setStatus("Success")
554 .setEmoji("GUILD.SETTINGS.GREEN")
555
556 await interaction.editReply({embeds: [embed], components: [menu, allowedMenu, buttons]});
557
558 let i: ButtonInteraction | StringSelectMenuInteraction;
559 try {
560 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
561 } catch (e) {
562 closed = true;
563 break;
564 }
565
566 if(i.isButton()) {
567 await i.deferUpdate();
568 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000569 case "back": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500570 closed = true;
571 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000572 }
573 case "everyone": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500574 current.everyone = !current.everyone;
575 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000576 }
577 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500578 current.roles = !current.roles;
579 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000580 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500581 }
582 } else {
583 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000584 case "toEdit": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500585 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000586 case "mass": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500587 await interaction.editReply({embeds: [new EmojiEmbed()
588 .setTitle("Word Filter")
589 .setDescription("Modal opened. If you can't see it, click back and try again.")
590 .setStatus("Success")
591 .setEmoji("GUILD.SETTINGS.GREEN")
592 ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
593 .setLabel("Back")
594 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
595 .setStyle(ButtonStyle.Primary)
596 .setCustomId("back")
597 )]})
598 const modal = new ModalBuilder()
599 .setTitle("Mass Mention Amount")
600 .setCustomId("mass")
601 .addComponents(
602 new ActionRowBuilder<TextInputBuilder>()
603 .addComponents(
604 new TextInputBuilder()
605 .setCustomId("mass")
606 .setPlaceholder("Amount")
607 .setMinLength(1)
608 .setMaxLength(3)
609 .setStyle(TextInputStyle.Short)
610 )
611 )
612 await i.showModal(modal);
613 let out;
614 try {
615 out = await modalInteractionCollector(
616 m,
617 (m: Interaction) =>
618 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
619 (m) => m.customId === "back"
620 );
621 } catch (e) {
622 break;
623 }
624 if (!out) break;
625 if(out.isButton()) break;
626 current.mass = parseInt(out.fields.getTextInputValue("mass"));
627 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000628 }
629 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500630 await i.deferUpdate();
631 current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings");
632 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000633 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500634 }
635 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000636 }
637 case "allowed": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500638 await i.deferUpdate();
639 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000640 case "users": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500641 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
642 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000643 }
644 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500645 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
646 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000647 }
648 case "channels": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500649 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings");
650 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000651 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500652 }
653 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000654 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500655 }
656 }
657
TheCodedProf486bca32023-02-02 16:49:44 -0500658 } while(!closed);
659 return current
TheCodedProf8b3da212023-02-02 15:09:55 -0500660}
661
TheCodedProfad0b8202023-02-14 14:27:09 -0500662const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
663 channels: string[],
664 allowed: {
665 roles: string[],
666 user: string[]
667 }
668}): Promise<{
669 channels: string[],
670 allowed: {
671 roles: string[],
672 user: string[]
673 }
674}> => {
675 let closed = false;
676 if(!current) current = {channels: [], allowed: {roles: [], user: []}};
677 if(!current.channels) current.channels = [];
678 if(!current.allowed) current.allowed = {roles: [], user: []};
679
680 const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
681 .addComponents(
682 new ChannelSelectMenuBuilder()
683 .setCustomId("toAdd")
684 .setPlaceholder("Select a channel")
685 )
686
687 const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
688 .addComponents(
689 new StringSelectMenuBuilder()
690 .setCustomId("allowed")
691 .setPlaceholder("Edit exceptions")
692 .addOptions(
693 new StringSelectMenuOptionBuilder()
694 .setLabel("Users")
695 .setDescription("Users that are unaffected by the mention filter")
696 .setValue("users"),
697 new StringSelectMenuOptionBuilder()
698 .setLabel("Roles")
699 .setDescription("Roles that are unaffected by the mention filter")
700 .setValue("roles")
701 )
702 )
703
704 do {
705
706 const buttons = new ActionRowBuilder<ButtonBuilder>()
707 .addComponents(
708 new ButtonBuilder()
709 .setCustomId("back")
710 .setLabel("Back")
711 .setStyle(ButtonStyle.Primary)
712 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
713 )
714
715 const embed = new EmojiEmbed()
716 .setTitle("Clean Settings")
717 .setEmoji("GUILD.SETTINGS.GREEN")
718 .setDescription(
719 `Current clean channels:\n\n` +
720 `${current.channels.length > 0 ? listToAndMore(current.channels.map(c => `<#${c}>`), 10) : "None"}\n\n`
721 )
722 .setStatus("Success")
723
724
725 await interaction.editReply({embeds: [embed], components: [channelMenu, allowedMenu, buttons]});
726
727 let i: ButtonInteraction | ChannelSelectMenuInteraction;
728 try {
729 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | ChannelSelectMenuInteraction;
730 } catch (e) {
731 closed = true;
732 break;
733 }
734 await i.deferUpdate();
735 if(i.isButton()) {
736 switch (i.customId) {
737 case "back": {
738 closed = true;
739 break;
740 }
741 }
742 } else {
743 switch (i.customId) {
744 case "toAdd": {
745 let channelEmbed = new EmojiEmbed()
746 .setTitle("Clean Settings")
747 .setDescription(`Editing <#${i.values[0]}>`)
748 .setEmoji("GUILD.SETTINGS.GREEN")
749 .setStatus("Success")
750 let channelButtons = new ActionRowBuilder<ButtonBuilder>()
751 .addComponents(
752 new ButtonBuilder()
753 .setCustomId("back")
754 .setLabel("Back")
755 .setStyle(ButtonStyle.Primary)
756 .setEmoji(getEmojiByName("CONTROL.LEFT", "id")),
757 new ButtonBuilder()
758 .setCustomId("switch")
759 .setLabel(current.channels.includes(i.values[0]!) ? "Remove" : "Add")
760 .setStyle(current.channels.includes(i.values[0]!) ? ButtonStyle.Danger : ButtonStyle.Success)
761 )
762
763 await i.editReply({embeds: [channelEmbed], components: [channelButtons]});
764 let j: ButtonInteraction;
765 try {
766 j = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
767 } catch (e) {
768 closed = true;
769 break;
770 }
771 await j.deferUpdate();
772 switch (j.customId) {
773 case "back": {
774 break;
775 }
776 case "switch": {
777 if(current.channels.includes(i.values[0]!)) {
778 current.channels.splice(current.channels.indexOf(i.values[0]!), 1);
779 } else {
780 current.channels.push(i.values[0]!);
781 }
782 }
783 }
784 break;
785 }
786 case "allowed": {
787 switch (i.values[0]) {
788 case "users": {
789 current.allowed.user = await toSelectMenu(interaction, m, current.allowed.user, "member", "Mention Settings");
790 break;
791 }
792 case "roles": {
793 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
794 break;
795 }
796 }
797 break;
798 }
799 }
800 }
801
802 } while(!closed);
803
804 return current;
805
806}
807
TheCodedProf8b3da212023-02-02 15:09:55 -0500808const callback = async (interaction: CommandInteraction): Promise<void> => {
809 if (!interaction.guild) return;
810 const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
811 const config = (await client.database.guilds.read(interaction.guild.id)).filters;
812
813 let closed = false;
814
815 const button = new ActionRowBuilder<ButtonBuilder>()
816 .addComponents(
817 new ButtonBuilder()
818 .setCustomId("save")
819 .setLabel("Save")
820 .setStyle(ButtonStyle.Success)
821 )
822
823 do {
824
825 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
826 .addComponents(
827 new StringSelectMenuBuilder()
828 .setCustomId("filter")
829 .setPlaceholder("Select a filter to edit")
830 .addOptions(
831 new StringSelectMenuOptionBuilder()
832 .setLabel("Invites")
833 .setDescription("Automatically delete messages containing server invites")
834 .setValue("invites"),
835 new StringSelectMenuOptionBuilder()
836 .setLabel("Mentions")
837 .setDescription("Deletes messages with excessive mentions")
838 .setValue("mentions"),
839 new StringSelectMenuOptionBuilder()
840 .setLabel("Words")
841 .setDescription("Delete messages containing filtered words")
842 .setValue("words"),
843 new StringSelectMenuOptionBuilder()
844 .setLabel("Malware")
845 .setDescription("Automatically delete files and links containing malware")
846 .setValue("malware"),
847 new StringSelectMenuOptionBuilder()
848 .setLabel("Images")
849 .setDescription("Checks performed on images (NSFW, size checking, etc.)")
TheCodedProfad0b8202023-02-14 14:27:09 -0500850 .setValue("images"),
851 new StringSelectMenuOptionBuilder()
852 .setLabel("Clean")
853 .setDescription("Automatically delete new messages in specific channels")
854 .setValue("clean")
TheCodedProf8b3da212023-02-02 15:09:55 -0500855 )
856 );
857
858 const embed = new EmojiEmbed()
859 .setTitle("Automod Settings")
860 .setDescription(
TheCodedProf486bca32023-02-02 16:49:44 -0500861 `${emojiFromBoolean(config.invite.enabled)} **Invites**\n` +
TheCodedProf8b3da212023-02-02 15:09:55 -0500862 `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` +
863 `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` +
864 `${emojiFromBoolean(config.malware)} **Malware**\n` +
TheCodedProfad0b8202023-02-14 14:27:09 -0500865 `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n` +
866 `${emojiFromBoolean(config.clean.channels.length > 0)} **Clean**\n`
TheCodedProf8b3da212023-02-02 15:09:55 -0500867 )
TheCodedProf486bca32023-02-02 16:49:44 -0500868 .setStatus("Success")
869 .setEmoji("GUILD.SETTINGS.GREEN")
TheCodedProf8b3da212023-02-02 15:09:55 -0500870
871
872 await interaction.editReply({embeds: [embed], components: [selectMenu, button]});
873
874 let i: StringSelectMenuInteraction | ButtonInteraction;
875 try {
876 i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 300000}) as StringSelectMenuInteraction | ButtonInteraction;
877 } catch (e) {
878 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000879 continue;
TheCodedProf8b3da212023-02-02 15:09:55 -0500880 }
TheCodedProfad0b8202023-02-14 14:27:09 -0500881 await i.deferUpdate();
TheCodedProf8b3da212023-02-02 15:09:55 -0500882 if(i.isButton()) {
TheCodedProf8b3da212023-02-02 15:09:55 -0500883 await client.database.guilds.write(interaction.guild.id, {filters: config});
884 } else {
885 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000886 case "invites": {
TheCodedProf486bca32023-02-02 16:49:44 -0500887 config.invite = await inviteMenu(i, m, config.invite);
TheCodedProf8b3da212023-02-02 15:09:55 -0500888 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000889 }
890 case "mentions": {
TheCodedProf486bca32023-02-02 16:49:44 -0500891 config.pings = await mentionMenu(i, m, config.pings);
TheCodedProf8b3da212023-02-02 15:09:55 -0500892 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000893 }
894 case "words": {
TheCodedProf486bca32023-02-02 16:49:44 -0500895 config.wordFilter = await wordMenu(i, m, config.wordFilter);
TheCodedProf8b3da212023-02-02 15:09:55 -0500896 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000897 }
898 case "malware": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500899 config.malware = !config.malware;
900 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000901 }
902 case "images": {
903 const next = await imageMenu(i, m, config.images);
904 config.images = next;
TheCodedProf8b3da212023-02-02 15:09:55 -0500905 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000906 }
TheCodedProfad0b8202023-02-14 14:27:09 -0500907 case "clean": {
908 const next = await cleanMenu(i, m, config.clean);
909 config.clean = next;
910 break;
911 }
TheCodedProf8b3da212023-02-02 15:09:55 -0500912 }
913 }
914
915 } while(!closed)
916
917};
918
919const check = (interaction: CommandInteraction, _partial: boolean = false) => {
920 const member = interaction.member as Discord.GuildMember;
921 if (!member.permissions.has("ManageMessages"))
922 return "You must have the *Manage Messages* permission to use this command";
923 return true;
924};
925
926export { command };
927export { callback };
928export { check };