blob: 09b8914ca841c677d449f427570986e6d5428aa0 [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,
TheCodedProf5b53a8c2023-02-03 15:40:26 -050011 Message,
TheCodedProf5b53a8c2023-02-03 15:40:26 -050012 ModalBuilder,
TheCodedProf5b53a8c2023-02-03 15:40:26 -050013 RoleSelectMenuBuilder,
14 RoleSelectMenuInteraction,
15 StringSelectMenuBuilder,
16 StringSelectMenuInteraction,
17 StringSelectMenuOptionBuilder,
18 TextInputBuilder,
19 TextInputStyle,
20 UserSelectMenuBuilder,
21 UserSelectMenuInteraction
22} from "discord.js";
TheCodedProf8b3da212023-02-02 15:09:55 -050023import type { SlashCommandSubcommandBuilder } from "discord.js";
24import { LoadingEmbed } from "../../utils/defaults.js";
25import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
26import client from "../../utils/client.js";
27import getEmojiByName from "../../utils/getEmojiByName.js";
TheCodedProf5b53a8c2023-02-03 15:40:26 -050028import { modalInteractionCollector } from "../../utils/dualCollector.js";
TheCodedProf1f675042023-02-16 17:01:29 -050029import listToAndMore from "../../utils/listToAndMore.js";
TheCodedProf8b3da212023-02-02 15:09:55 -050030
TheCodedProf486bca32023-02-02 16:49:44 -050031
TheCodedProf8b3da212023-02-02 15:09:55 -050032const command = (builder: SlashCommandSubcommandBuilder) =>
33 builder.setName("automod").setDescription("Setting for automatic moderation features");
34
35
36const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id);
37
TheCodedProf486bca32023-02-02 16:49:44 -050038const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise<string[]> => {
39
40 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 -050041 let closed;
42 do {
43 let render: string[] = []
44 let mapped: string[] = [];
45 let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder;
46 switch(type) {
PineaFanb0d0c242023-02-05 10:59:45 +000047 case "member": {
TheCodedProf486bca32023-02-02 16:49:44 -050048 menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25);
49 mapped = await Promise.all(ids.map(async (id) => { return (await client.users.fetch(id).then(user => user.tag)) || "Unknown User" }));
50 render = ids.map(id => client.logger.renderUser(id))
51 break;
PineaFanb0d0c242023-02-05 10:59:45 +000052 }
53 case "role": {
TheCodedProf486bca32023-02-02 16:49:44 -050054 menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25);
PineaFanb0d0c242023-02-05 10:59:45 +000055 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 -050056 render = ids.map(id => client.logger.renderRole(id, interaction.guild!))
57 break;
PineaFanb0d0c242023-02-05 10:59:45 +000058 }
59 case "channel": {
TheCodedProf486bca32023-02-02 16:49:44 -050060 menu = new ChannelSelectMenuBuilder().setCustomId("channel").setPlaceholder("Select channels").setMaxValues(25);
PineaFanb0d0c242023-02-05 10:59:45 +000061 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 -050062 render = ids.map(id => client.logger.renderChannel(id))
63 break;
PineaFanb0d0c242023-02-05 10:59:45 +000064 }
TheCodedProf486bca32023-02-02 16:49:44 -050065 }
66 const removeOptions = new ActionRowBuilder<StringSelectMenuBuilder>()
67 .addComponents(
68 new StringSelectMenuBuilder()
69 .setCustomId("remove")
70 .setPlaceholder("Remove")
71 .addOptions(mapped.map((name, i) => new StringSelectMenuOptionBuilder().setLabel(name).setValue(ids[i]!)))
72 .setDisabled(ids.length === 0)
73 );
74
75 const embed = new EmojiEmbed()
76 .setTitle(title)
77 .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN"))
78 .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None"))
79 .setStatus("Success");
PineaFanb0d0c242023-02-05 10:59:45 +000080 const components: ActionRowBuilder<
TheCodedProf486bca32023-02-02 16:49:44 -050081 StringSelectMenuBuilder |
82 ButtonBuilder |
83 ChannelSelectMenuBuilder |
84 UserSelectMenuBuilder |
85 RoleSelectMenuBuilder
86 >[] = [new ActionRowBuilder<typeof menu>().addComponents(menu)]
87 if(ids.length > 0) components.push(removeOptions);
88 components.push(back);
89
90 await interaction.editReply({embeds: [embed], components: components})
91
92 let i: AnySelectMenuInteraction | ButtonInteraction;
93 try {
TheCodedProf5b53a8c2023-02-03 15:40:26 -050094 i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000});
TheCodedProf486bca32023-02-02 16:49:44 -050095 } catch(e) {
96 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +000097 continue;
TheCodedProf486bca32023-02-02 16:49:44 -050098 }
99
100 if(i.isButton()) {
101 await i.deferUpdate();
102 if(i.customId === "back") {
103 closed = true;
104 break;
105 }
106 } else if(i.isStringSelectMenu()) {
107 await i.deferUpdate();
108 if(i.customId === "remove") {
109 ids = ids.filter(id => id !== (i as StringSelectMenuInteraction).values[0]);
110 if(ids.length === 0) {
111 menu.data.disabled = true;
112 }
113 }
114 } else {
115 await i.deferUpdate();
116 if(i.customId === "user") {
117 ids = ids.concat((i as UserSelectMenuInteraction).values);
118 } else if(i.customId === "role") {
119 ids = ids.concat((i as RoleSelectMenuInteraction).values);
120 } else if(i.customId === "channel") {
121 ids = ids.concat((i as ChannelSelectMenuInteraction).values);
122 }
123 }
124
125 } while(!closed)
126 return ids;
127}
TheCodedProf8b3da212023-02-02 15:09:55 -0500128
129const imageMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
130 NSFW: boolean,
131 size: boolean
132}): Promise<{NSFW: boolean, size: boolean}> => {
133 let closed = false;
134 do {
135 const options = new ActionRowBuilder<ButtonBuilder>()
136 .addComponents(
137 new ButtonBuilder()
138 .setCustomId("back")
139 .setLabel("Back")
140 .setStyle(ButtonStyle.Secondary)
141 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
142 new ButtonBuilder()
143 .setCustomId("nsfw")
144 .setLabel("NSFW")
145 .setStyle(current.NSFW ? ButtonStyle.Success : ButtonStyle.Danger)
146 .setEmoji(emojiFromBoolean(current.NSFW, "id") as APIMessageComponentEmoji),
147 new ButtonBuilder()
148 .setCustomId("size")
149 .setLabel("Size")
150 .setStyle(current.size ? ButtonStyle.Success : ButtonStyle.Danger)
151 .setEmoji(emojiFromBoolean(current.size, "id") as APIMessageComponentEmoji)
152 )
153
154 const embed = new EmojiEmbed()
155 .setTitle("Image Settings")
156 .setDescription(
157 `${emojiFromBoolean(current.NSFW)} **NSFW**\n` +
158 `${emojiFromBoolean(current.size)} **Size**\n`
159 )
160
161 await interaction.editReply({embeds: [embed], components: [options]});
162
163 let i: ButtonInteraction;
164 try {
165 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
166 } catch (e) {
167 return current;
168 }
169 await i.deferUpdate();
170 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000171 case "back": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500172 closed = true;
173 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000174 }
175 case "nsfw": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500176 current.NSFW = !current.NSFW;
177 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000178 }
179 case "size": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500180 current.size = !current.size;
181 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000182 }
TheCodedProf8b3da212023-02-02 15:09:55 -0500183 }
184 } while(!closed);
185 return current;
186}
187
188const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
189 enabled: boolean,
190 words: {strict: string[], loose: string[]},
TheCodedProf486bca32023-02-02 16:49:44 -0500191 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500192}): Promise<{
193 enabled: boolean,
194 words: {strict: string[], loose: string[]},
TheCodedProf486bca32023-02-02 16:49:44 -0500195 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500196}> => {
TheCodedProf486bca32023-02-02 16:49:44 -0500197 let closed = false;
198 do {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500199 const buttons = new ActionRowBuilder<ButtonBuilder>()
200 .addComponents(
201 new ButtonBuilder()
202 .setCustomId("back")
203 .setLabel("Back")
204 .setStyle(ButtonStyle.Secondary)
205 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
206 new ButtonBuilder()
207 .setCustomId("enabled")
208 .setLabel("Enabled")
209 .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
210 .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji),
211 );
212
213 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
214 .addComponents(
215 new StringSelectMenuBuilder()
216 .setCustomId("edit")
217 .setPlaceholder("Edit... ")
218 .addOptions(
219 new StringSelectMenuOptionBuilder()
220 .setLabel("Words")
221 .setDescription("Edit your list of words to filter")
222 .setValue("words"),
223 new StringSelectMenuOptionBuilder()
224 .setLabel("Allowed Users")
225 .setDescription("Users who will be unaffected by the word filter")
226 .setValue("allowedUsers"),
227 new StringSelectMenuOptionBuilder()
228 .setLabel("Allowed Roles")
229 .setDescription("Roles that will be unaffected by the word filter")
230 .setValue("allowedRoles"),
231 new StringSelectMenuOptionBuilder()
232 .setLabel("Allowed Channels")
233 .setDescription("Channels where the word filter will not apply")
234 .setValue("allowedChannels")
235 )
236 .setDisabled(!current.enabled)
237 );
238
239 const embed = new EmojiEmbed()
240 .setTitle("Word Filters")
241 .setDescription(
242 `${emojiFromBoolean(current.enabled)} **Enabled**\n` +
243 `**Strict Words:** ${listToAndMore(current.words.strict, 5)}\n` +
244 `**Loose Words:** ${listToAndMore(current.words.loose, 5)}\n\n` +
245 `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
246 `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
247 `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
248 )
249 .setStatus("Success")
250 .setEmoji("GUILD.SETTINGS.GREEN")
251
252 await interaction.editReply({embeds: [embed], components: [selectMenu, buttons]});
253
254 let i: ButtonInteraction | StringSelectMenuInteraction;
255 try {
256 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
257 } catch (e) {
258 closed = true;
259 break;
260 }
261
262 if(i.isButton()) {
263 await i.deferUpdate();
264 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000265 case "back": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500266 closed = true;
267 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000268 }
269 case "enabled": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500270 current.enabled = !current.enabled;
271 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000272 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500273 }
274 } else {
275 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000276 case "words": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500277 await interaction.editReply({embeds: [new EmojiEmbed()
278 .setTitle("Word Filter")
279 .setDescription("Modal opened. If you can't see it, click back and try again.")
280 .setStatus("Success")
281 .setEmoji("GUILD.SETTINGS.GREEN")
282 ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
283 .setLabel("Back")
284 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
285 .setStyle(ButtonStyle.Primary)
286 .setCustomId("back")
287 )]})
288 const modal = new ModalBuilder()
289 .setTitle("Word Filter")
290 .setCustomId("wordFilter")
291 .addComponents(
292 new ActionRowBuilder<TextInputBuilder>()
293 .addComponents(
294 new TextInputBuilder()
295 .setCustomId("wordStrict")
296 .setLabel("Strict Words")
297 .setPlaceholder("Matches anywhere in the message, including surrounded by other characters")
PineaFanb0d0c242023-02-05 10:59:45 +0000298 .setValue(current.words.strict.join(", "))
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500299 .setStyle(TextInputStyle.Paragraph)
300 .setRequired(false)
301 ),
302 new ActionRowBuilder<TextInputBuilder>()
303 .addComponents(
304 new TextInputBuilder()
305 .setCustomId("wordLoose")
306 .setLabel("Loose Words")
307 .setPlaceholder("Matches only if the word is by itself, surrounded by spaces or punctuation")
PineaFanb0d0c242023-02-05 10:59:45 +0000308 .setValue(current.words.loose.join(", "))
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500309 .setStyle(TextInputStyle.Paragraph)
310 .setRequired(false)
311 )
312 )
313
314 await i.showModal(modal);
315 let out;
316 try {
TheCodedProf01cba762023-02-18 15:55:05 -0500317 out = await modalInteractionCollector(m, interaction.user);
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500318 } catch (e) {
319 break;
320 }
321 if (!out) break;
322 if(out.isButton()) break;
323 current.words.strict = out.fields.getTextInputValue("wordStrict")
324 .split(",").map(s => s.trim()).filter(s => s.length > 0);
325 current.words.loose = out.fields.getTextInputValue("wordLoose")
326 .split(",").map(s => s.trim()).filter(s => s.length > 0);
327 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000328 }
329 case "allowedUsers": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500330 await i.deferUpdate();
331 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter");
332 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000333 }
334 case "allowedRoles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500335 await i.deferUpdate();
336 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter");
337 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000338 }
339 case "allowedChannels": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500340 await i.deferUpdate();
341 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter");
342 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000343 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500344 }
345 }
TheCodedProf486bca32023-02-02 16:49:44 -0500346 } while(!closed);
347 return current;
TheCodedProf8b3da212023-02-02 15:09:55 -0500348}
349
350const inviteMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
351 enabled: boolean,
TheCodedProf486bca32023-02-02 16:49:44 -0500352 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500353}): Promise<{
354 enabled: boolean,
TheCodedProf486bca32023-02-02 16:49:44 -0500355 allowed: {users: string[], roles: string[], channels: string[]}
TheCodedProf8b3da212023-02-02 15:09:55 -0500356}> => {
357
TheCodedProf486bca32023-02-02 16:49:44 -0500358 let closed = false;
359 do {
360 const buttons = new ActionRowBuilder<ButtonBuilder>()
361 .addComponents(
362 new ButtonBuilder()
363 .setCustomId("back")
364 .setLabel("Back")
365 .setStyle(ButtonStyle.Secondary)
366 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
367 new ButtonBuilder()
368 .setCustomId("enabled")
369 .setLabel(current.enabled ? "Enabled" : "Disabled")
370 .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
371 .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji)
372 );
373 const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
374 .addComponents(
375 new StringSelectMenuBuilder()
376 .setCustomId("toEdit")
377 .setPlaceholder("Edit your allow list")
378 .addOptions(
379 new StringSelectMenuOptionBuilder()
380 .setLabel("Users")
381 .setDescription("Users that are allowed to send invites")
382 .setValue("users"),
383 new StringSelectMenuOptionBuilder()
384 .setLabel("Roles")
385 .setDescription("Roles that are allowed to send invites")
386 .setValue("roles"),
387 new StringSelectMenuOptionBuilder()
388 .setLabel("Channels")
389 .setDescription("Channels that anyone is allowed to send invites in")
390 .setValue("channels")
391 ).setDisabled(!current.enabled)
392 )
393
394 const embed = new EmojiEmbed()
395 .setTitle("Invite Settings")
396 .setDescription(
397 "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + `\n\n` +
398 `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` +
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500399 `**Users:** ` + listToAndMore(current.allowed.users.map(user => `<@${user}>`), 5) + `\n` +
400 `**Roles:** ` + listToAndMore(current.allowed.roles.map(role => `<@&${role}>`), 5) + `\n` +
401 `**Channels:** ` + listToAndMore(current.allowed.channels.map(channel => `<#${channel}>`), 5)
TheCodedProf486bca32023-02-02 16:49:44 -0500402 )
403 .setStatus("Success")
404 .setEmoji("GUILD.SETTINGS.GREEN")
405
406
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500407 await interaction.editReply({embeds: [embed], components: [menu, buttons]});
TheCodedProf486bca32023-02-02 16:49:44 -0500408
409 let i: ButtonInteraction | StringSelectMenuInteraction;
410 try {
411 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
412 } catch (e) {
413 return current;
414 }
415
416 if(i.isButton()) {
417 await i.deferUpdate();
418 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000419 case "back": {
TheCodedProf486bca32023-02-02 16:49:44 -0500420 closed = true;
421 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000422 }
423 case "enabled": {
TheCodedProf486bca32023-02-02 16:49:44 -0500424 current.enabled = !current.enabled;
425 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000426 }
TheCodedProf486bca32023-02-02 16:49:44 -0500427 }
428 } else {
429 await i.deferUpdate();
430 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000431 case "users": {
TheCodedProf486bca32023-02-02 16:49:44 -0500432 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings");
433 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000434 }
435 case "roles": {
TheCodedProf486bca32023-02-02 16:49:44 -0500436 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings");
437 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000438 }
439 case "channels": {
TheCodedProf486bca32023-02-02 16:49:44 -0500440 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings");
441 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000442 }
TheCodedProf486bca32023-02-02 16:49:44 -0500443 }
444 }
445
446 } while(!closed);
447 return current;
TheCodedProf8b3da212023-02-02 15:09:55 -0500448}
449
450const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, current: {
451 mass: number,
452 everyone: boolean,
453 roles: boolean,
454 allowed: {
455 roles: string[],
456 rolesToMention: string[],
457 users: string[],
458 channels: string[]
459 }
460}): Promise<{
461 mass: number,
462 everyone: boolean,
463 roles: boolean,
464 allowed: {
465 roles: string[],
466 rolesToMention: string[],
467 users: string[],
468 channels: string[]
469 }
470}> => {
TheCodedProf486bca32023-02-02 16:49:44 -0500471 let closed = false;
472
473 do {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500474
475 const buttons = new ActionRowBuilder<ButtonBuilder>()
476 .addComponents(
477 new ButtonBuilder()
478 .setCustomId("back")
479 .setLabel("Back")
480 .setStyle(ButtonStyle.Secondary)
481 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
482 new ButtonBuilder()
483 .setCustomId("everyone")
484 .setLabel(current.everyone ? "Everyone" : "No one")
485 .setStyle(current.everyone ? ButtonStyle.Success : ButtonStyle.Danger)
486 .setEmoji(emojiFromBoolean(current.everyone, "id") as APIMessageComponentEmoji),
487 new ButtonBuilder()
488 .setCustomId("roles")
489 .setLabel(current.roles ? "Roles" : "No roles")
490 .setStyle(current.roles ? ButtonStyle.Success : ButtonStyle.Danger)
491 .setEmoji(emojiFromBoolean(current.roles, "id") as APIMessageComponentEmoji)
492 );
493 const menu = new ActionRowBuilder<StringSelectMenuBuilder>()
494 .addComponents(
495 new StringSelectMenuBuilder()
496 .setCustomId("toEdit")
497 .setPlaceholder("Edit mention settings")
498 .addOptions(
499 new StringSelectMenuOptionBuilder()
500 .setLabel("Mass Mention Amount")
501 .setDescription("The amount of mentions before the bot will delete the message")
502 .setValue("mass"),
503 new StringSelectMenuOptionBuilder()
504 .setLabel("Roles")
505 .setDescription("Roles that are able to be mentioned")
506 .setValue("roles"),
507 )
508 )
509
510 const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
511 .addComponents(
512 new StringSelectMenuBuilder()
513 .setCustomId("allowed")
514 .setPlaceholder("Edit exceptions")
515 .addOptions(
516 new StringSelectMenuOptionBuilder()
517 .setLabel("Users")
518 .setDescription("Users that are unaffected by the mention filter")
519 .setValue("users"),
520 new StringSelectMenuOptionBuilder()
521 .setLabel("Roles")
522 .setDescription("Roles that are unaffected by the mention filter")
523 .setValue("roles"),
524 new StringSelectMenuOptionBuilder()
525 .setLabel("Channels")
526 .setDescription("Channels where anyone is unaffected by the mention filter")
527 .setValue("channels")
528 )
529 )
530
531 const embed = new EmojiEmbed()
532 .setTitle("Mention Settings")
533 .setDescription(
534 `Log when members mention:\n` +
535 `${emojiFromBoolean(true)} **${current.mass}+ members** in one message\n` +
536 `${emojiFromBoolean(current.everyone)} **Everyone**\n` +
537 `${emojiFromBoolean(current.roles)} **Roles**\n` +
538 (current.allowed.rolesToMention.length > 0 ? `> *Except for ${listToAndMore(current.allowed.rolesToMention.map(r => `<@&${r}>`), 3)}*\n` : "") +
539 "\n" +
540 `Except if...\n` +
541 `> ${current.allowed.users.length > 0 ? `Member is: ${listToAndMore(current.allowed.users.map(u => `<@${u}>`), 3)}\n` : ""}` +
542 `> ${current.allowed.roles.length > 0 ? `Member has role: ${listToAndMore(current.allowed.roles.map(r => `<@&${r}>`), 3)}\n` : ""}` +
543 `> ${current.allowed.channels.length > 0 ? `In channel: ${listToAndMore(current.allowed.channels.map(c => `<#${c}>`), 3)}\n` : ""}`
544 )
545 .setStatus("Success")
546 .setEmoji("GUILD.SETTINGS.GREEN")
547
548 await interaction.editReply({embeds: [embed], components: [menu, allowedMenu, buttons]});
549
550 let i: ButtonInteraction | StringSelectMenuInteraction;
551 try {
552 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | StringSelectMenuInteraction;
553 } catch (e) {
554 closed = true;
555 break;
556 }
557
558 if(i.isButton()) {
559 await i.deferUpdate();
560 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000561 case "back": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500562 closed = true;
563 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000564 }
565 case "everyone": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500566 current.everyone = !current.everyone;
567 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000568 }
569 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500570 current.roles = !current.roles;
571 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000572 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500573 }
574 } else {
575 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000576 case "toEdit": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500577 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000578 case "mass": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500579 await interaction.editReply({embeds: [new EmojiEmbed()
580 .setTitle("Word Filter")
581 .setDescription("Modal opened. If you can't see it, click back and try again.")
582 .setStatus("Success")
583 .setEmoji("GUILD.SETTINGS.GREEN")
584 ], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
585 .setLabel("Back")
586 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
587 .setStyle(ButtonStyle.Primary)
588 .setCustomId("back")
589 )]})
590 const modal = new ModalBuilder()
591 .setTitle("Mass Mention Amount")
592 .setCustomId("mass")
593 .addComponents(
594 new ActionRowBuilder<TextInputBuilder>()
595 .addComponents(
596 new TextInputBuilder()
597 .setCustomId("mass")
598 .setPlaceholder("Amount")
599 .setMinLength(1)
600 .setMaxLength(3)
601 .setStyle(TextInputStyle.Short)
602 )
603 )
604 await i.showModal(modal);
605 let out;
606 try {
TheCodedProf01cba762023-02-18 15:55:05 -0500607 out = await modalInteractionCollector(m, interaction.user);
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500608 } catch (e) {
609 break;
610 }
611 if (!out) break;
612 if(out.isButton()) break;
613 current.mass = parseInt(out.fields.getTextInputValue("mass"));
614 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000615 }
616 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500617 await i.deferUpdate();
618 current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings");
619 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000620 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500621 }
622 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000623 }
624 case "allowed": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500625 await i.deferUpdate();
626 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000627 case "users": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500628 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
629 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000630 }
631 case "roles": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500632 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
633 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000634 }
635 case "channels": {
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500636 current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings");
637 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000638 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500639 }
640 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000641 }
TheCodedProf5b53a8c2023-02-03 15:40:26 -0500642 }
643 }
644
TheCodedProf486bca32023-02-02 16:49:44 -0500645 } while(!closed);
646 return current
TheCodedProf8b3da212023-02-02 15:09:55 -0500647}
648
TheCodedProf1807fb32023-02-20 14:33:48 -0500649const cleanMenu = async (interaction: StringSelectMenuInteraction, m: Message, current?: {
650 channels?: string[],
651 allowed?: {
TheCodedProfad0b8202023-02-14 14:27:09 -0500652 roles: string[],
TheCodedProff8ef7942023-03-03 15:32:32 -0500653 users: string[]
TheCodedProfad0b8202023-02-14 14:27:09 -0500654 }
655}): Promise<{
656 channels: string[],
657 allowed: {
658 roles: string[],
TheCodedProff8ef7942023-03-03 15:32:32 -0500659 users: string[]
TheCodedProfad0b8202023-02-14 14:27:09 -0500660 }
661}> => {
662 let closed = false;
TheCodedProff8ef7942023-03-03 15:32:32 -0500663 if(!current) current = {channels: [], allowed: {roles: [], users: []}};
TheCodedProfad0b8202023-02-14 14:27:09 -0500664 if(!current.channels) current.channels = [];
TheCodedProff8ef7942023-03-03 15:32:32 -0500665 if(!current.allowed) current.allowed = {roles: [], users: []};
TheCodedProfad0b8202023-02-14 14:27:09 -0500666
667 const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
668 .addComponents(
669 new ChannelSelectMenuBuilder()
670 .setCustomId("toAdd")
671 .setPlaceholder("Select a channel")
672 )
673
674 const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
675 .addComponents(
676 new StringSelectMenuBuilder()
677 .setCustomId("allowed")
678 .setPlaceholder("Edit exceptions")
679 .addOptions(
680 new StringSelectMenuOptionBuilder()
681 .setLabel("Users")
682 .setDescription("Users that are unaffected by the mention filter")
683 .setValue("users"),
684 new StringSelectMenuOptionBuilder()
685 .setLabel("Roles")
686 .setDescription("Roles that are unaffected by the mention filter")
687 .setValue("roles")
688 )
689 )
690
691 do {
692
693 const buttons = new ActionRowBuilder<ButtonBuilder>()
694 .addComponents(
695 new ButtonBuilder()
696 .setCustomId("back")
697 .setLabel("Back")
698 .setStyle(ButtonStyle.Primary)
699 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
700 )
701
702 const embed = new EmojiEmbed()
703 .setTitle("Clean Settings")
704 .setEmoji("GUILD.SETTINGS.GREEN")
705 .setDescription(
706 `Current clean channels:\n\n` +
707 `${current.channels.length > 0 ? listToAndMore(current.channels.map(c => `<#${c}>`), 10) : "None"}\n\n`
708 )
709 .setStatus("Success")
710
711
712 await interaction.editReply({embeds: [embed], components: [channelMenu, allowedMenu, buttons]});
713
714 let i: ButtonInteraction | ChannelSelectMenuInteraction;
715 try {
716 i = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction | ChannelSelectMenuInteraction;
717 } catch (e) {
718 closed = true;
719 break;
720 }
721 await i.deferUpdate();
722 if(i.isButton()) {
723 switch (i.customId) {
724 case "back": {
725 closed = true;
726 break;
727 }
728 }
729 } else {
730 switch (i.customId) {
731 case "toAdd": {
TheCodedProf1807fb32023-02-20 14:33:48 -0500732 const channelEmbed = new EmojiEmbed()
TheCodedProfad0b8202023-02-14 14:27:09 -0500733 .setTitle("Clean Settings")
734 .setDescription(`Editing <#${i.values[0]}>`)
735 .setEmoji("GUILD.SETTINGS.GREEN")
736 .setStatus("Success")
TheCodedProf1807fb32023-02-20 14:33:48 -0500737 const channelButtons = new ActionRowBuilder<ButtonBuilder>()
TheCodedProfad0b8202023-02-14 14:27:09 -0500738 .addComponents(
739 new ButtonBuilder()
740 .setCustomId("back")
741 .setLabel("Back")
742 .setStyle(ButtonStyle.Primary)
743 .setEmoji(getEmojiByName("CONTROL.LEFT", "id")),
744 new ButtonBuilder()
745 .setCustomId("switch")
746 .setLabel(current.channels.includes(i.values[0]!) ? "Remove" : "Add")
747 .setStyle(current.channels.includes(i.values[0]!) ? ButtonStyle.Danger : ButtonStyle.Success)
748 )
749
750 await i.editReply({embeds: [channelEmbed], components: [channelButtons]});
751 let j: ButtonInteraction;
752 try {
753 j = await m.awaitMessageComponent({filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, time: 300000}) as ButtonInteraction;
754 } catch (e) {
755 closed = true;
756 break;
757 }
758 await j.deferUpdate();
759 switch (j.customId) {
760 case "back": {
761 break;
762 }
763 case "switch": {
764 if(current.channels.includes(i.values[0]!)) {
765 current.channels.splice(current.channels.indexOf(i.values[0]!), 1);
766 } else {
767 current.channels.push(i.values[0]!);
768 }
769 }
770 }
771 break;
772 }
773 case "allowed": {
774 switch (i.values[0]) {
775 case "users": {
TheCodedProff8ef7942023-03-03 15:32:32 -0500776 current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
TheCodedProfad0b8202023-02-14 14:27:09 -0500777 break;
778 }
779 case "roles": {
780 current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
781 break;
782 }
783 }
784 break;
785 }
786 }
787 }
788
789 } while(!closed);
790
TheCodedProf1807fb32023-02-20 14:33:48 -0500791 return current as {
792 channels: string[],
793 allowed: {
794 roles: string[],
TheCodedProff8ef7942023-03-03 15:32:32 -0500795 users: string[]
TheCodedProf1807fb32023-02-20 14:33:48 -0500796 }
797 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500798
799}
800
TheCodedProf8b3da212023-02-02 15:09:55 -0500801const callback = async (interaction: CommandInteraction): Promise<void> => {
802 if (!interaction.guild) return;
803 const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
804 const config = (await client.database.guilds.read(interaction.guild.id)).filters;
805
806 let closed = false;
807
808 const button = new ActionRowBuilder<ButtonBuilder>()
809 .addComponents(
810 new ButtonBuilder()
811 .setCustomId("save")
812 .setLabel("Save")
813 .setStyle(ButtonStyle.Success)
814 )
815
816 do {
817
818 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
819 .addComponents(
820 new StringSelectMenuBuilder()
821 .setCustomId("filter")
822 .setPlaceholder("Select a filter to edit")
823 .addOptions(
824 new StringSelectMenuOptionBuilder()
825 .setLabel("Invites")
826 .setDescription("Automatically delete messages containing server invites")
827 .setValue("invites"),
828 new StringSelectMenuOptionBuilder()
829 .setLabel("Mentions")
830 .setDescription("Deletes messages with excessive mentions")
831 .setValue("mentions"),
832 new StringSelectMenuOptionBuilder()
833 .setLabel("Words")
834 .setDescription("Delete messages containing filtered words")
835 .setValue("words"),
836 new StringSelectMenuOptionBuilder()
837 .setLabel("Malware")
838 .setDescription("Automatically delete files and links containing malware")
839 .setValue("malware"),
840 new StringSelectMenuOptionBuilder()
841 .setLabel("Images")
842 .setDescription("Checks performed on images (NSFW, size checking, etc.)")
TheCodedProfad0b8202023-02-14 14:27:09 -0500843 .setValue("images"),
844 new StringSelectMenuOptionBuilder()
845 .setLabel("Clean")
846 .setDescription("Automatically delete new messages in specific channels")
847 .setValue("clean")
TheCodedProf8b3da212023-02-02 15:09:55 -0500848 )
849 );
850
851 const embed = new EmojiEmbed()
852 .setTitle("Automod Settings")
853 .setDescription(
TheCodedProf486bca32023-02-02 16:49:44 -0500854 `${emojiFromBoolean(config.invite.enabled)} **Invites**\n` +
TheCodedProf8b3da212023-02-02 15:09:55 -0500855 `${emojiFromBoolean(config.pings.everyone || config.pings.mass > 0 || config.pings.roles)} **Mentions**\n` +
856 `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` +
857 `${emojiFromBoolean(config.malware)} **Malware**\n` +
TheCodedProfad0b8202023-02-14 14:27:09 -0500858 `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n` +
859 `${emojiFromBoolean(config.clean.channels.length > 0)} **Clean**\n`
TheCodedProf8b3da212023-02-02 15:09:55 -0500860 )
TheCodedProf486bca32023-02-02 16:49:44 -0500861 .setStatus("Success")
862 .setEmoji("GUILD.SETTINGS.GREEN")
TheCodedProf8b3da212023-02-02 15:09:55 -0500863
864
865 await interaction.editReply({embeds: [embed], components: [selectMenu, button]});
866
867 let i: StringSelectMenuInteraction | ButtonInteraction;
868 try {
869 i = await m.awaitMessageComponent({filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 300000}) as StringSelectMenuInteraction | ButtonInteraction;
870 } catch (e) {
871 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000872 continue;
TheCodedProf8b3da212023-02-02 15:09:55 -0500873 }
TheCodedProfad0b8202023-02-14 14:27:09 -0500874 await i.deferUpdate();
TheCodedProf8b3da212023-02-02 15:09:55 -0500875 if(i.isButton()) {
TheCodedProf8b3da212023-02-02 15:09:55 -0500876 await client.database.guilds.write(interaction.guild.id, {filters: config});
877 } else {
878 switch(i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000879 case "invites": {
TheCodedProf486bca32023-02-02 16:49:44 -0500880 config.invite = await inviteMenu(i, m, config.invite);
TheCodedProf8b3da212023-02-02 15:09:55 -0500881 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000882 }
883 case "mentions": {
TheCodedProf486bca32023-02-02 16:49:44 -0500884 config.pings = await mentionMenu(i, m, config.pings);
TheCodedProf8b3da212023-02-02 15:09:55 -0500885 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000886 }
887 case "words": {
TheCodedProf486bca32023-02-02 16:49:44 -0500888 config.wordFilter = await wordMenu(i, m, config.wordFilter);
TheCodedProf8b3da212023-02-02 15:09:55 -0500889 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000890 }
891 case "malware": {
TheCodedProf8b3da212023-02-02 15:09:55 -0500892 config.malware = !config.malware;
893 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000894 }
895 case "images": {
896 const next = await imageMenu(i, m, config.images);
897 config.images = next;
TheCodedProf8b3da212023-02-02 15:09:55 -0500898 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000899 }
TheCodedProfad0b8202023-02-14 14:27:09 -0500900 case "clean": {
901 const next = await cleanMenu(i, m, config.clean);
902 config.clean = next;
903 break;
904 }
TheCodedProf8b3da212023-02-02 15:09:55 -0500905 }
906 }
907
TheCodedProf01cba762023-02-18 15:55:05 -0500908 } while(!closed);
909 await interaction.deleteReply()
TheCodedProf8b3da212023-02-02 15:09:55 -0500910
911};
912
913const check = (interaction: CommandInteraction, _partial: boolean = false) => {
914 const member = interaction.member as Discord.GuildMember;
915 if (!member.permissions.has("ManageMessages"))
916 return "You must have the *Manage Messages* permission to use this command";
917 return true;
918};
919
920export { command };
921export { callback };
922export { check };