blob: de8e7404bbd100bcada599cc274c6a98b1658562 [file] [log] [blame]
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()
.setLabel("Amount")
.setCustomId("mass")
.setPlaceholder(current.mass === 5 ? "Amount" : current.mass.toString())
.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 };