| import type Discord from "discord.js"; |
| import { |
| ActionRowBuilder, |
| AnySelectMenuInteraction, |
| APIMessageComponentEmoji, |
| ButtonBuilder, |
| ButtonInteraction, |
| ButtonStyle, |
| ChannelSelectMenuBuilder, |
| ChannelSelectMenuInteraction, |
| CommandInteraction, |
| Message, |
| ModalBuilder, |
| RoleSelectMenuBuilder, |
| RoleSelectMenuInteraction, |
| StringSelectMenuBuilder, |
| StringSelectMenuInteraction, |
| StringSelectMenuOptionBuilder, |
| TextInputBuilder, |
| TextInputStyle, |
| UserSelectMenuBuilder, |
| UserSelectMenuInteraction |
| } from "discord.js"; |
| import type { SlashCommandSubcommandBuilder } from "discord.js"; |
| import { LoadingEmbed } from "../../utils/defaults.js"; |
| import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; |
| import client from "../../utils/client.js"; |
| import getEmojiByName from "../../utils/getEmojiByName.js"; |
| import { modalInteractionCollector } from "../../utils/dualCollector.js"; |
| import listToAndMore from "../../utils/listToAndMore.js"; |
| import _ from "lodash"; |
| |
| const command = (builder: SlashCommandSubcommandBuilder) => |
| builder.setName("automod").setDescription("Setting for automatic moderation features"); |
| |
| const emojiFromBoolean = (bool: boolean, id?: string) => |
| bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id); |
| |
| const toSelectMenu = async ( |
| interaction: StringSelectMenuInteraction, |
| m: Message, |
| ids: string[], |
| type: "member" | "role" | "channel", |
| title: string |
| ): Promise<string[]> => { |
| const back = new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId("back") |
| .setLabel("Back") |
| .setStyle(ButtonStyle.Secondary) |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) |
| ); |
| let closed; |
| do { |
| let render: string[] = []; |
| let mapped: string[] = []; |
| let menu: UserSelectMenuBuilder | RoleSelectMenuBuilder | ChannelSelectMenuBuilder; |
| switch (type) { |
| case "member": { |
| menu = new UserSelectMenuBuilder().setCustomId("user").setPlaceholder("Select users").setMaxValues(25); |
| mapped = await Promise.all( |
| ids.map(async (id) => { |
| return (await client.users.fetch(id).then((user) => user.tag)) || "Unknown User"; |
| }) |
| ); |
| render = ids.map((id) => client.logger.renderUser(id)); |
| break; |
| } |
| case "role": { |
| menu = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder("Select roles").setMaxValues(25); |
| mapped = await Promise.all( |
| ids.map(async (id) => { |
| return await interaction.guild!.roles.fetch(id).then((role) => role?.name ?? "Unknown Role"); |
| }) |
| ); |
| render = ids.map((id) => client.logger.renderRole(id, interaction.guild!)); |
| break; |
| } |
| case "channel": { |
| menu = new ChannelSelectMenuBuilder() |
| .setCustomId("channel") |
| .setPlaceholder("Select channels") |
| .setMaxValues(25); |
| mapped = await Promise.all( |
| ids.map(async (id) => { |
| return await interaction |
| .guild!.channels.fetch(id) |
| .then((channel) => channel?.name ?? "Unknown Role"); |
| }) |
| ); |
| render = ids.map((id) => client.logger.renderChannel(id)); |
| break; |
| } |
| } |
| const removeOptions = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("remove") |
| .setPlaceholder("Remove") |
| .addOptions( |
| mapped.map((name, i) => new StringSelectMenuOptionBuilder().setLabel(name).setValue(ids[i]!)) |
| ) |
| .setDisabled(ids.length === 0) |
| ); |
| |
| const embed = new EmojiEmbed() |
| .setTitle(title) |
| .setEmoji(getEmojiByName("GUILD.SETTINGS.GREEN")) |
| .setDescription(`Select ${type}s:\n\nCurrent:\n` + (render.length > 0 ? render.join("\n") : "None")) |
| .setStatus("Success"); |
| const components: ActionRowBuilder< |
| | StringSelectMenuBuilder |
| | ButtonBuilder |
| | ChannelSelectMenuBuilder |
| | UserSelectMenuBuilder |
| | RoleSelectMenuBuilder |
| >[] = [new ActionRowBuilder<typeof menu>().addComponents(menu)]; |
| if (ids.length > 0) components.push(removeOptions); |
| components.push(back); |
| |
| await interaction.editReply({ embeds: [embed], components: components }); |
| |
| let i: AnySelectMenuInteraction | ButtonInteraction; |
| try { |
| i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 300000 }); |
| } catch (e) { |
| closed = true; |
| continue; |
| } |
| |
| if (i.isButton()) { |
| await i.deferUpdate(); |
| if (i.customId === "back") { |
| closed = true; |
| break; |
| } |
| } else if (i.isStringSelectMenu()) { |
| await i.deferUpdate(); |
| if (i.customId === "remove") { |
| ids = ids.filter((id) => id !== (i as StringSelectMenuInteraction).values[0]); |
| if (ids.length === 0) { |
| menu.data.disabled = true; |
| } |
| } |
| } else { |
| await i.deferUpdate(); |
| if (i.customId === "user") { |
| ids = ids.concat((i as UserSelectMenuInteraction).values); |
| } else if (i.customId === "role") { |
| ids = ids.concat((i as RoleSelectMenuInteraction).values); |
| } else if (i.customId === "channel") { |
| ids = ids.concat((i as ChannelSelectMenuInteraction).values); |
| } |
| } |
| } while (!closed); |
| return ids; |
| }; |
| |
| const imageMenu = async ( |
| interaction: StringSelectMenuInteraction, |
| m: Message, |
| unsavedChanges: boolean, |
| current: { |
| NSFW: boolean; |
| size: boolean; |
| } |
| ): Promise<{ NSFW: boolean; size: boolean }> => { |
| let closed = false; |
| do { |
| const options = new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId("back") |
| .setLabel("Back") |
| .setStyle(ButtonStyle.Secondary) |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), |
| new ButtonBuilder() |
| .setCustomId("nsfw") |
| .setLabel("NSFW") |
| .setStyle(current.NSFW ? ButtonStyle.Success : ButtonStyle.Danger) |
| .setEmoji(emojiFromBoolean(current.NSFW, "id") as APIMessageComponentEmoji), |
| new ButtonBuilder() |
| .setCustomId("size") |
| .setLabel("Size") |
| .setStyle(current.size ? ButtonStyle.Success : ButtonStyle.Danger) |
| .setEmoji(emojiFromBoolean(current.size, "id") as APIMessageComponentEmoji) |
| ); |
| |
| const embed = new EmojiEmbed() |
| .setTitle("Image Settings") |
| .setStatus("Success") |
| // .setEmoji("") // TODO |
| .setDescription( |
| `${emojiFromBoolean(current.NSFW)} **NSFW**\n` + `${emojiFromBoolean(current.size)} **Size**\n` |
| ) |
| .setFooter({ |
| text: unsavedChanges ? "No changes made" : "Changes not saved" |
| }); |
| |
| await interaction.editReply({ embeds: [embed], components: [options] }); |
| |
| let i: ButtonInteraction; |
| try { |
| i = (await m.awaitMessageComponent({ |
| filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, |
| time: 300000 |
| })) as ButtonInteraction; |
| } catch (e) { |
| return current; |
| } |
| await i.deferUpdate(); |
| switch (i.customId) { |
| case "back": { |
| closed = true; |
| break; |
| } |
| case "nsfw": { |
| current.NSFW = !current.NSFW; |
| unsavedChanges = true; |
| break; |
| } |
| case "size": { |
| current.size = !current.size; |
| unsavedChanges = true; |
| break; |
| } |
| } |
| } while (!closed); |
| return current; |
| }; |
| |
| const wordMenu = async ( |
| interaction: StringSelectMenuInteraction, |
| m: Message, |
| unsavedChanges: boolean, |
| current: { |
| enabled: boolean; |
| words: { strict: string[]; loose: string[] }; |
| allowed: { users: string[]; roles: string[]; channels: string[] }; |
| } |
| ): Promise<{ |
| enabled: boolean; |
| words: { strict: string[]; loose: string[] }; |
| allowed: { users: string[]; roles: string[]; channels: string[] }; |
| }> => { |
| let closed = false; |
| do { |
| const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId("back") |
| .setLabel("Back") |
| .setStyle(ButtonStyle.Secondary) |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), |
| new ButtonBuilder() |
| .setCustomId("enabled") |
| .setLabel("Enabled") |
| .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger) |
| .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji) |
| ); |
| |
| const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("edit") |
| .setPlaceholder("Edit... ") |
| .addOptions( |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Words") |
| .setDescription("Edit your list of words to filter") |
| .setValue("words"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Allowed Users") |
| .setDescription("Users who will be unaffected by the word filter") |
| .setValue("allowedUsers"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Allowed Roles") |
| .setDescription("Roles that will be unaffected by the word filter") |
| .setValue("allowedRoles"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Allowed Channels") |
| .setDescription("Channels where the word filter will not apply") |
| .setValue("allowedChannels") |
| ) |
| .setDisabled(!current.enabled) |
| ); |
| |
| const embed = new EmojiEmbed() |
| .setTitle("Word Filters") |
| .setDescription( |
| `${emojiFromBoolean(current.enabled)} **Enabled**\n` + |
| `**Strict Words:** ${listToAndMore(current.words.strict, 5)}\n` + |
| `**Loose Words:** ${listToAndMore(current.words.loose, 5)}\n\n` + |
| `**Users:** ` + |
| listToAndMore( |
| current.allowed.users.map((user) => `<@${user}>`), |
| 5 |
| ) + |
| `\n` + |
| `**Roles:** ` + |
| listToAndMore( |
| current.allowed.roles.map((role) => `<@&${role}>`), |
| 5 |
| ) + |
| `\n` + |
| `**Channels:** ` + |
| listToAndMore( |
| current.allowed.channels.map((channel) => `<#${channel}>`), |
| 5 |
| ) |
| ) |
| .setStatus("Success") |
| .setEmoji("GUILD.SETTINGS.GREEN") |
| .setFooter({ |
| text: unsavedChanges ? "No changes made" : "Changes not saved" |
| }); |
| |
| await interaction.editReply({ embeds: [embed], components: [selectMenu, buttons] }); |
| |
| let i: ButtonInteraction | StringSelectMenuInteraction; |
| try { |
| i = (await m.awaitMessageComponent({ |
| filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, |
| time: 300000 |
| })) as ButtonInteraction | StringSelectMenuInteraction; |
| } catch (e) { |
| closed = true; |
| break; |
| } |
| |
| if (i.isButton()) { |
| await i.deferUpdate(); |
| switch (i.customId) { |
| case "back": { |
| closed = true; |
| break; |
| } |
| case "enabled": { |
| current.enabled = !current.enabled; |
| unsavedChanges = true; |
| break; |
| } |
| } |
| } else { |
| switch (i.values[0]) { |
| case "words": { |
| await interaction.editReply({ |
| embeds: [ |
| new EmojiEmbed() |
| .setTitle("Word Filter") |
| .setDescription("Modal opened. If you can't see it, click back and try again.") |
| .setStatus("Success") |
| .setEmoji("GUILD.SETTINGS.GREEN") |
| ], |
| components: [ |
| new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setLabel("Back") |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) |
| .setStyle(ButtonStyle.Primary) |
| .setCustomId("back") |
| ) |
| ] |
| }); |
| const modal = new ModalBuilder() |
| .setTitle("Word Filter") |
| .setCustomId("wordFilter") |
| .addComponents( |
| new ActionRowBuilder<TextInputBuilder>().addComponents( |
| new TextInputBuilder() |
| .setCustomId("wordStrict") |
| .setLabel("Strict Words") |
| .setPlaceholder( |
| "Matches anywhere in the message, including surrounded by other characters" |
| ) |
| .setValue(current.words.strict.join(", ")) |
| .setStyle(TextInputStyle.Paragraph) |
| .setRequired(false) |
| ), |
| new ActionRowBuilder<TextInputBuilder>().addComponents( |
| new TextInputBuilder() |
| .setCustomId("wordLoose") |
| .setLabel("Loose Words") |
| .setPlaceholder( |
| "Matches only if the word is by itself, surrounded by spaces or punctuation" |
| ) |
| .setValue(current.words.loose.join(", ")) |
| .setStyle(TextInputStyle.Paragraph) |
| .setRequired(false) |
| ) |
| ); |
| |
| await i.showModal(modal); |
| let out; |
| try { |
| out = await modalInteractionCollector(m, interaction.user); |
| } catch (e) { |
| break; |
| } |
| if (!out) break; |
| if (out.isButton()) break; |
| current.words.strict = out.fields |
| .getTextInputValue("wordStrict") |
| .split(",") |
| .map((s) => s.trim()) |
| .filter((s) => s.length > 0); |
| current.words.loose = out.fields |
| .getTextInputValue("wordLoose") |
| .split(",") |
| .map((s) => s.trim()) |
| .filter((s) => s.length > 0); |
| unsavedChanges = true; |
| break; |
| } |
| case "allowedUsers": { |
| await i.deferUpdate(); |
| current.allowed.users = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| case "allowedRoles": { |
| await i.deferUpdate(); |
| current.allowed.roles = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| case "allowedChannels": { |
| await i.deferUpdate(); |
| current.allowed.channels = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| } |
| } |
| } while (!closed); |
| return current; |
| }; |
| |
| const inviteMenu = async ( |
| interaction: StringSelectMenuInteraction, |
| m: Message, |
| unsavedChanges: boolean, |
| current: { |
| enabled: boolean; |
| allowed: { users: string[]; roles: string[]; channels: string[] }; |
| } |
| ): Promise<{ |
| enabled: boolean; |
| allowed: { users: string[]; roles: string[]; channels: string[] }; |
| }> => { |
| let closed = false; |
| do { |
| const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId("back") |
| .setLabel("Back") |
| .setStyle(ButtonStyle.Secondary) |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), |
| new ButtonBuilder() |
| .setCustomId("enabled") |
| .setLabel(current.enabled ? "Enabled" : "Disabled") |
| .setStyle(current.enabled ? ButtonStyle.Success : ButtonStyle.Danger) |
| .setEmoji(emojiFromBoolean(current.enabled, "id") as APIMessageComponentEmoji) |
| ); |
| const menu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("toEdit") |
| .setPlaceholder("Edit your allow list") |
| .addOptions( |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Users") |
| .setDescription("Users that are allowed to send invites") |
| .setValue("users"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Roles") |
| .setDescription("Roles that are allowed to send invites") |
| .setValue("roles"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Channels") |
| .setDescription("Channels that anyone is allowed to send invites in") |
| .setValue("channels") |
| ) |
| .setDisabled(!current.enabled) |
| ); |
| |
| const embed = new EmojiEmbed() |
| .setTitle("Invite Settings") |
| .setDescription( |
| "Automatically deletes invites sent by users (outside of staff members and self promotion channels)" + |
| `\n\n` + |
| `${emojiFromBoolean(current.enabled)} **${current.enabled ? "Enabled" : "Disabled"}**\n\n` + |
| `**Users:** ` + |
| listToAndMore( |
| current.allowed.users.map((user) => `<@${user}>`), |
| 5 |
| ) + |
| `\n` + |
| `**Roles:** ` + |
| listToAndMore( |
| current.allowed.roles.map((role) => `<@&${role}>`), |
| 5 |
| ) + |
| `\n` + |
| `**Channels:** ` + |
| listToAndMore( |
| current.allowed.channels.map((channel) => `<#${channel}>`), |
| 5 |
| ) |
| ) |
| .setStatus("Success") |
| .setEmoji("GUILD.SETTINGS.GREEN") |
| .setFooter({ |
| text: unsavedChanges ? "No changes made" : "Changes not saved" |
| }); |
| |
| await interaction.editReply({ embeds: [embed], components: [menu, buttons] }); |
| |
| let i: ButtonInteraction | StringSelectMenuInteraction; |
| try { |
| i = (await m.awaitMessageComponent({ |
| filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, |
| time: 300000 |
| })) as ButtonInteraction | StringSelectMenuInteraction; |
| } catch (e) { |
| return current; |
| } |
| |
| if (i.isButton()) { |
| await i.deferUpdate(); |
| switch (i.customId) { |
| case "back": { |
| closed = true; |
| break; |
| } |
| case "enabled": { |
| current.enabled = !current.enabled; |
| unsavedChanges = true; |
| break; |
| } |
| } |
| } else { |
| await i.deferUpdate(); |
| switch (i.values[0]) { |
| case "users": { |
| current.allowed.users = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.users, "member", "Invite Settings") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| case "roles": { |
| current.allowed.roles = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.roles, "role", "Invite Settings") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| case "channels": { |
| current.allowed.channels = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Invite Settings") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| } |
| } |
| } while (!closed); |
| return current; |
| }; |
| |
| const mentionMenu = async ( |
| interaction: StringSelectMenuInteraction, |
| m: Message, |
| unsavedChanges: boolean, |
| current: { |
| mass: number; |
| everyone: boolean; |
| roles: boolean; |
| allowed: { |
| roles: string[]; |
| rolesToMention: string[]; |
| users: string[]; |
| channels: string[]; |
| }; |
| } |
| ): Promise<{ |
| mass: number; |
| everyone: boolean; |
| roles: boolean; |
| allowed: { |
| roles: string[]; |
| rolesToMention: string[]; |
| users: string[]; |
| channels: string[]; |
| }; |
| }> => { |
| let closed = false; |
| |
| do { |
| const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId("back") |
| .setLabel("Back") |
| .setStyle(ButtonStyle.Secondary) |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), |
| new ButtonBuilder() |
| .setCustomId("everyone") |
| .setLabel(current.everyone ? "Everyone" : "No one") |
| .setStyle(current.everyone ? ButtonStyle.Success : ButtonStyle.Danger) |
| .setEmoji(emojiFromBoolean(current.everyone, "id") as APIMessageComponentEmoji), |
| new ButtonBuilder() |
| .setCustomId("roles") |
| .setLabel(current.roles ? "Roles" : "No roles") |
| .setStyle(current.roles ? ButtonStyle.Success : ButtonStyle.Danger) |
| .setEmoji(emojiFromBoolean(current.roles, "id") as APIMessageComponentEmoji) |
| ); |
| const menu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("toEdit") |
| .setPlaceholder("Edit mention settings") |
| .addOptions( |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Mass Mention Amount") |
| .setDescription("The amount of mentions before the bot will delete the message") |
| .setValue("mass"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Roles") |
| .setDescription("Roles that are able to be mentioned") |
| .setValue("roles") |
| ) |
| ); |
| |
| const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("allowed") |
| .setPlaceholder("Edit allowed list") |
| .addOptions( |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Users") |
| .setDescription("Users that are unaffected by the mention filter") |
| .setValue("users"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Roles") |
| .setDescription("Roles that are unaffected by the mention filter") |
| .setValue("roles"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Channels") |
| .setDescription("Channels where anyone is unaffected by the mention filter") |
| .setValue("channels") |
| ) |
| ); |
| |
| const embed = new EmojiEmbed() |
| .setTitle("Mention Settings") |
| .setDescription( |
| `Log when members mention:\n` + |
| `${emojiFromBoolean(true)} **${current.mass}+ members** in one message\n` + |
| `${emojiFromBoolean(current.everyone)} **Everyone**\n` + |
| `${emojiFromBoolean(current.roles)} **Roles**\n` + |
| (current.allowed.rolesToMention.length > 0 |
| ? `> *Except for ${listToAndMore( |
| current.allowed.rolesToMention.map((r) => `<@&${r}>`), |
| 3 |
| )}*\n` |
| : "") + |
| "\n" + |
| `Except if...\n` + |
| (current.allowed.users.length > 0 |
| ? `> Member is: ${listToAndMore( |
| current.allowed.users.map((u) => `<@${u}>`), |
| 3 |
| )}\n` |
| : "") + |
| (current.allowed.roles.length > 0 |
| ? `> Member has role: ${listToAndMore( |
| current.allowed.roles.map((r) => `<@&${r}>`), |
| 3 |
| )}\n` |
| : "") + |
| (current.allowed.channels.length > 0 |
| ? `> In channel: ${listToAndMore( |
| current.allowed.channels.map((c) => `<#${c}>`), |
| 3 |
| )}\n` |
| : "") + |
| (current.allowed.users.length == 0 || |
| current.allowed.roles.length == 0 || |
| current.allowed.channels.length == 0 |
| ? "> *Nobody on allowed lists*\n" |
| : "") |
| ) |
| .setStatus("Success") |
| .setEmoji("GUILD.SETTINGS.GREEN") |
| .setFooter({ |
| text: unsavedChanges ? "No changes made" : "Changes not saved" |
| }); |
| |
| await interaction.editReply({ embeds: [embed], components: [menu, allowedMenu, buttons] }); |
| |
| let i: ButtonInteraction | StringSelectMenuInteraction; |
| try { |
| i = (await m.awaitMessageComponent({ |
| filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, |
| time: 300000 |
| })) as ButtonInteraction | StringSelectMenuInteraction; |
| } catch (e) { |
| closed = true; |
| break; |
| } |
| |
| if (i.isButton()) { |
| await i.deferUpdate(); |
| switch (i.customId) { |
| case "back": { |
| closed = true; |
| break; |
| } |
| case "everyone": { |
| current.everyone = !current.everyone; |
| unsavedChanges = true; |
| break; |
| } |
| case "roles": { |
| current.roles = !current.roles; |
| unsavedChanges = true; |
| break; |
| } |
| } |
| } else { |
| switch (i.customId) { |
| case "toEdit": { |
| switch (i.values[0]) { |
| case "mass": { |
| await interaction.editReply({ |
| embeds: [ |
| new EmojiEmbed() |
| .setTitle("Word Filter") |
| .setDescription("Modal opened. If you can't see it, click back and try again.") |
| .setStatus("Success") |
| .setEmoji("GUILD.SETTINGS.GREEN") |
| ], |
| components: [ |
| new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setLabel("Back") |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) |
| .setStyle(ButtonStyle.Primary) |
| .setCustomId("back") |
| ) |
| ] |
| }); |
| const modal = new ModalBuilder() |
| .setTitle("Mass Mention Amount") |
| .setCustomId("mass") |
| .addComponents( |
| new ActionRowBuilder<TextInputBuilder>().addComponents( |
| new TextInputBuilder() |
| .setCustomId("mass") |
| .setPlaceholder("Amount") |
| .setMinLength(1) |
| .setMaxLength(3) |
| .setStyle(TextInputStyle.Short) |
| ) |
| ); |
| await i.showModal(modal); |
| let out; |
| try { |
| out = await modalInteractionCollector(m, interaction.user); |
| } catch (e) { |
| break; |
| } |
| if (!out) break; |
| if (out.isButton()) break; |
| current.mass = parseInt(out.fields.getTextInputValue("mass")); |
| unsavedChanges = true; |
| break; |
| } |
| case "roles": { |
| await i.deferUpdate(); |
| current.allowed.rolesToMention = _.cloneDeep( |
| await toSelectMenu( |
| interaction, |
| m, |
| current.allowed.rolesToMention, |
| "role", |
| "Mention Settings" |
| ) |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| } |
| break; |
| } |
| case "allowed": { |
| await i.deferUpdate(); |
| switch (i.values[0]) { |
| case "users": { |
| current.allowed.users = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| case "roles": { |
| current.allowed.roles = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| case "channels": { |
| current.allowed.channels = _.cloneDeep( |
| await toSelectMenu( |
| interaction, |
| m, |
| current.allowed.channels, |
| "channel", |
| "Mention Settings" |
| ) |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| } |
| } while (!closed); |
| return current; |
| }; |
| |
| const cleanMenu = async ( |
| interaction: StringSelectMenuInteraction, |
| m: Message, |
| unsavedChanges: boolean, |
| current?: { |
| channels?: string[]; |
| allowed?: { |
| roles: string[]; |
| users: string[]; |
| }; |
| } |
| ): Promise<{ |
| channels: string[]; |
| allowed: { |
| roles: string[]; |
| users: string[]; |
| }; |
| }> => { |
| let closed = false; |
| if (!current) current = { channels: [], allowed: { roles: [], users: [] } }; |
| if (!current.channels) current.channels = []; |
| if (!current.allowed) current.allowed = { roles: [], users: [] }; |
| |
| const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents( |
| new ChannelSelectMenuBuilder().setCustomId("toAdd").setPlaceholder("Select a channel") |
| ); |
| |
| const allowedMenu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("allowed") |
| .setPlaceholder("Edit exceptions") |
| .addOptions( |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Users") |
| .setDescription("Users that are unaffected by the mention filter") |
| .setValue("users"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Roles") |
| .setDescription("Roles that are unaffected by the mention filter") |
| .setValue("roles") |
| ) |
| ); |
| |
| do { |
| const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId("back") |
| .setLabel("Back") |
| .setStyle(ButtonStyle.Primary) |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) |
| ); |
| |
| const embed = new EmojiEmbed() |
| .setTitle("Clean Settings") |
| .setEmoji("GUILD.SETTINGS.GREEN") |
| .setDescription( |
| `Current clean channels:\n\n` + |
| `${ |
| current.channels.length > 0 |
| ? listToAndMore( |
| current.channels.map((c) => `<#${c}>`), |
| 10 |
| ) |
| : "None" |
| }\n\n` |
| ) |
| .setStatus("Success") |
| .setFooter({ |
| text: unsavedChanges ? "No changes made" : "Changes not saved" |
| }); |
| |
| await interaction.editReply({ embeds: [embed], components: [channelMenu, allowedMenu, buttons] }); |
| |
| let i: ButtonInteraction | ChannelSelectMenuInteraction; |
| try { |
| i = (await m.awaitMessageComponent({ |
| filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, |
| time: 300000 |
| })) as ButtonInteraction | ChannelSelectMenuInteraction; |
| } catch (e) { |
| closed = true; |
| break; |
| } |
| await i.deferUpdate(); |
| if (i.isButton()) { |
| switch (i.customId) { |
| case "back": { |
| closed = true; |
| break; |
| } |
| } |
| } else { |
| switch (i.customId) { |
| case "toAdd": { |
| const channelEmbed = new EmojiEmbed() |
| .setTitle("Clean Settings") |
| .setDescription(`Editing <#${i.values[0]}>`) |
| .setEmoji("GUILD.SETTINGS.GREEN") |
| .setStatus("Success"); |
| const channelButtons = new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId("back") |
| .setLabel("Back") |
| .setStyle(ButtonStyle.Primary) |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id")), |
| new ButtonBuilder() |
| .setCustomId("switch") |
| .setLabel(current.channels.includes(i.values[0]!) ? "Remove" : "Add") |
| .setStyle( |
| current.channels.includes(i.values[0]!) ? ButtonStyle.Danger : ButtonStyle.Success |
| ) |
| ); |
| |
| await i.editReply({ embeds: [channelEmbed], components: [channelButtons] }); |
| let j: ButtonInteraction; |
| try { |
| j = (await m.awaitMessageComponent({ |
| filter: (i) => interaction.user.id === i.user.id && i.message.id === m.id, |
| time: 300000 |
| })) as ButtonInteraction; |
| } catch (e) { |
| closed = true; |
| break; |
| } |
| await j.deferUpdate(); |
| switch (j.customId) { |
| case "back": { |
| break; |
| } |
| case "switch": { |
| if (current.channels.includes(i.values[0]!)) { |
| current.channels.splice(current.channels.indexOf(i.values[0]!), 1); |
| } else { |
| current.channels.push(i.values[0]!); |
| } |
| } |
| } |
| unsavedChanges = true; |
| break; |
| } |
| case "allowed": { |
| switch (i.values[0]) { |
| case "users": { |
| current.allowed.users = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| case "roles": { |
| current.allowed.roles = _.cloneDeep( |
| await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings") |
| ); |
| unsavedChanges = true; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| } |
| } while (!closed); |
| |
| return current as { |
| channels: string[]; |
| allowed: { |
| roles: string[]; |
| users: string[]; |
| }; |
| }; |
| }; |
| |
| const callback = async (interaction: CommandInteraction): Promise<void> => { |
| if (!interaction.guild) return; |
| const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true }); |
| let config = (await client.database.guilds.read(interaction.guild.id)).filters; |
| |
| let closed = false; |
| |
| let current = _.cloneDeep(config); |
| do { |
| const button = new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId("save") |
| .setLabel("Save") |
| .setStyle(ButtonStyle.Success) |
| .setDisabled(_.isEqual(config, current)) |
| ); |
| const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("filter") |
| .setPlaceholder("Select a filter to edit") |
| .addOptions( |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Invites") |
| .setDescription("Automatically delete messages containing server invites") |
| .setValue("invites"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Mentions") |
| .setDescription("Deletes messages with excessive mentions") |
| .setValue("mentions"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Words") |
| .setDescription("Delete messages containing filtered words") |
| .setValue("words"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Malware") |
| .setDescription("Automatically delete files and links containing malware") |
| .setValue("malware"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Images") |
| .setDescription("Checks performed on images (NSFW, size checking, etc.)") |
| .setValue("images"), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Clean") |
| .setDescription("Automatically delete new messages in specific channels") |
| .setValue("clean") |
| ) |
| ); |
| |
| const embed = new EmojiEmbed() |
| .setTitle("Automod Settings") |
| .setDescription( |
| `${emojiFromBoolean(config.invite.enabled)} **Invites**\n` + |
| `${emojiFromBoolean( |
| config.pings.everyone || config.pings.mass > 0 || config.pings.roles |
| )} **Mentions**\n` + |
| `${emojiFromBoolean(config.wordFilter.enabled)} **Words**\n` + |
| `${emojiFromBoolean(config.malware)} **Malware**\n` + |
| `${emojiFromBoolean(config.images.NSFW || config.images.size)} **Images**\n` + |
| `${emojiFromBoolean(config.clean.channels.length > 0)} **Clean**\n` |
| ) |
| .setStatus("Success") |
| .setEmoji("GUILD.SETTINGS.GREEN") |
| .setFooter({ |
| text: _.isEqual(config, current) ? "No changes made" : "Changes not saved" |
| }); |
| |
| await interaction.editReply({ embeds: [embed], components: [selectMenu, button] }); |
| |
| let i: StringSelectMenuInteraction | ButtonInteraction; |
| try { |
| i = (await m.awaitMessageComponent({ |
| filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, |
| time: 300000 |
| })) as StringSelectMenuInteraction | ButtonInteraction; |
| } catch (e) { |
| closed = true; |
| continue; |
| } |
| await i.deferUpdate(); |
| if (i.isButton()) { |
| await client.database.guilds.write(interaction.guild.id, { filters: current }); |
| await client.memory.forceUpdate(interaction.guild.id); |
| config = current; |
| current = _.cloneDeep(config); |
| } else { |
| switch (i.values[0]) { |
| case "invites": { |
| current.invite = await inviteMenu(i, m, _.isEqual(config, current), current.invite); |
| break; |
| } |
| case "mentions": { |
| current.pings = await mentionMenu(i, m, _.isEqual(config, current), current.pings); |
| break; |
| } |
| case "words": { |
| current.wordFilter = await wordMenu(i, m, _.isEqual(config, current), current.wordFilter); |
| break; |
| } |
| case "malware": { |
| current.malware = !current.malware; |
| break; |
| } |
| case "images": { |
| const next = await imageMenu(i, m, _.isEqual(config, current), current.images); |
| current.images = next; |
| break; |
| } |
| case "clean": { |
| const next = await cleanMenu(i, m, _.isEqual(config, current), config.clean); |
| current.clean = next; |
| break; |
| } |
| } |
| } |
| } while (!closed); |
| await interaction.deleteReply(); |
| }; |
| |
| const check = (interaction: CommandInteraction, _partial: boolean = false) => { |
| const member = interaction.member as Discord.GuildMember; |
| if (!member.permissions.has("ManageMessages")) |
| return "You must have the *Manage Messages* permission to use this command"; |
| return true; |
| }; |
| |
| export { command }; |
| export { callback }; |
| export { check }; |