finished automod
diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts
index a7b6dbc..8e006b0 100644
--- a/src/commands/settings/automod.ts
+++ b/src/commands/settings/automod.ts
@@ -1,10 +1,34 @@
import type Discord from "discord.js";
-import { ActionRowBuilder, AnyComponent, AnyComponentBuilder, AnySelectMenuInteraction, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, CommandInteraction, Guild, GuildMember, GuildTextBasedChannel, MentionableSelectMenuBuilder, Message, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SelectMenuBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, UserSelectMenuBuilder, UserSelectMenuInteraction } from "discord.js";
+import { ActionRowBuilder,
+ AnySelectMenuInteraction,
+ APIMessageComponentEmoji,
+ ButtonBuilder,
+ ButtonInteraction,
+ ButtonStyle,
+ ChannelSelectMenuBuilder,
+ ChannelSelectMenuInteraction,
+ CommandInteraction,
+ Interaction,
+ Message,
+ MessageComponentInteraction,
+ ModalBuilder,
+ ModalSubmitInteraction,
+ 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";
const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -75,7 +99,7 @@
let i: AnySelectMenuInteraction | ButtonInteraction;
try {
- i = await interaction.channel!.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000});
+ i = await m.awaitMessageComponent({filter: i => i.user.id === interaction.user.id, time: 300000});
} catch(e) {
closed = true;
break;
@@ -177,7 +201,152 @@
}> => {
let closed = false;
do {
- closed = true;
+ 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")
+
+ 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;
+ 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,
+ (m: Interaction) =>
+ (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
+ (m) => m.customId === "back"
+ );
+ } 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);
+ break;
+ case "allowedUsers":
+ await i.deferUpdate();
+ current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Word Filter");
+ break;
+ case "allowedRoles":
+ await i.deferUpdate();
+ current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Word Filter");
+ break;
+ case "allowedChannels":
+ await i.deferUpdate();
+ current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Word Filter");
+ break;
+ }
+ }
} while(!closed);
return current;
}
@@ -231,15 +400,15 @@
.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)
+ `**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")
- await interaction.editReply({embeds: [embed], components: [buttons, menu]});
+ await interaction.editReply({embeds: [embed], components: [menu, buttons]});
let i: ButtonInteraction | StringSelectMenuInteraction;
try {
@@ -301,7 +470,172 @@
let closed = false;
do {
- closed = true;
+
+ 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 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"),
+ 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` : ""}`
+ )
+ .setStatus("Success")
+ .setEmoji("GUILD.SETTINGS.GREEN")
+
+ 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;
+ break;
+ case "roles":
+ current.roles = !current.roles;
+ 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,
+ (m: Interaction) =>
+ (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId,
+ (m) => m.customId === "back"
+ );
+ } catch (e) {
+ break;
+ }
+ if (!out) break;
+ if(out.isButton()) break;
+ current.mass = parseInt(out.fields.getTextInputValue("mass"));
+ break;
+ case "roles":
+ await i.deferUpdate();
+ current.allowed.rolesToMention = await toSelectMenu(interaction, m, current.allowed.rolesToMention, "role", "Mention Settings");
+ break;
+ }
+ break;
+ case "allowed":
+ await i.deferUpdate();
+ switch (i.values[0]) {
+ case "users":
+ current.allowed.users = await toSelectMenu(interaction, m, current.allowed.users, "member", "Mention Settings");
+ break;
+ case "roles":
+ current.allowed.roles = await toSelectMenu(interaction, m, current.allowed.roles, "role", "Mention Settings");
+ break;
+ case "channels":
+ current.allowed.channels = await toSelectMenu(interaction, m, current.allowed.channels, "channel", "Mention Settings");
+ break;
+ }
+ break;
+ }
+ }
+
} while(!closed);
return current
}
diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts
index 9563a33..65e1422 100644
--- a/src/events/messageDelete.ts
+++ b/src/events/messageDelete.ts
@@ -6,7 +6,7 @@
export async function callback(client: NucleusClient, message: Message) {
if (message.author.id === client.user!.id) return;
if (message.author.bot) return;
- if (client.noLog.includes(`${message.id}/${message.channel.id}/${message.id}`)) return;
+ if (client.noLog.includes(`${message.guild!.id}/${message.channel.id}/${message.id}`)) return;
const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd))
.filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0];
diff --git a/src/reflex/scanners.ts b/src/reflex/scanners.ts
index c8d59f0..c55e463 100644
--- a/src/reflex/scanners.ts
+++ b/src/reflex/scanners.ts
@@ -9,19 +9,18 @@
interface NSFWSchema {
nsfw: boolean;
+ errored?: boolean;
}
interface MalwareSchema {
safe: boolean;
+ errored?: boolean;
}
export async function testNSFW(link: string): Promise<NSFWSchema> {
const [p, hash] = await saveAttachment(link);
- console.log("Checking an image")
let alreadyHaveCheck = await client.database.scanCache.read(hash)
if(alreadyHaveCheck) return { nsfw: alreadyHaveCheck.data };
- console.log("Was not in db")
const data = new FormData();
- console.log(link);
data.append("file", createReadStream(p));
const result = await fetch("https://unscan.p.rapidapi.com/", {
method: "POST",
@@ -31,13 +30,14 @@
},
body: data
})
- .then((response) => response.json() as Promise<NSFWSchema>)
+ .then((response) => response.status === 200 ? response.json() as Promise<NSFWSchema> : { nsfw: false, errored: true })
.catch((err) => {
console.error(err);
- return { nsfw: false };
+ return { nsfw: false, errored: true };
});
- console.log(result);
- client.database.scanCache.write(hash, result.nsfw);
+ if(!result.errored) {
+ client.database.scanCache.write(hash, result.nsfw);
+ }
return { nsfw: result.nsfw };
}
@@ -48,7 +48,6 @@
const data = new URLSearchParams();
let f = createReadStream(p);
data.append("file", f.read(fs.statSync(p).size));
- console.log(link);
const result = await fetch("https://unscan.p.rapidapi.com/malware", {
method: "POST",
headers: {
@@ -57,18 +56,18 @@
},
body: data
})
- .then((response) => response.json() as Promise<MalwareSchema>)
+ .then((response) => response.status === 200 ? response.json() as Promise<MalwareSchema> : { safe: true, errored: true })
.catch((err) => {
console.error(err);
- return { safe: true };
+ return { safe: true, errored: true };
});
- console.log(result);
- client.database.scanCache.write(hash, result.safe);
+ if (!result.errored) {
+ client.database.scanCache.write(hash, result.safe);
+ }
return { safe: result.safe };
}
export async function testLink(link: string): Promise<{ safe: boolean; tags: string[] }> {
- console.log(link);
let alreadyHaveCheck = await client.database.scanCache.read(link)
if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data, tags: [] };
const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/link", {
@@ -84,7 +83,6 @@
console.error(err);
return { safe: true, tags: [] };
});
- console.log(scanned);
client.database.scanCache.write(link, scanned.safe ?? true, []);
return {
safe: scanned.safe ?? true,
@@ -93,10 +91,11 @@
}
export async function saveAttachment(link: string): Promise<[string, string]> {
- const image = (await fetch(link)).arrayBuffer().toString();
+ const image = await (await fetch(link)).arrayBuffer()
const fileName = generateFileName(link.split("/").pop()!.split(".").pop()!);
- writeFileSync(fileName, image, "base64");
- return [fileName, createHash('sha512').update(image, 'base64').digest('base64')];
+ const enc = new TextDecoder("utf-8");
+ writeFileSync(fileName, new DataView(image), "base64");
+ return [fileName, createHash('sha512').update(enc.decode(image), 'base64').digest('base64')];
}
const linkTypes = {
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 4eff4e2..c5c7e06 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -10,7 +10,7 @@
export const Logger = {
renderUser(user: Discord.User | string) {
- if (typeof user === "string") return `${user} [<@${user}>]`;
+ if (typeof user === "string") user = client.users.cache.get(user) as Discord.User;
return `${user.username} [<@${user.id}>]`;
},
renderTime(t: number) {
@@ -55,7 +55,6 @@
const config = await client.database.guilds.read(log.hidden.guild);
if (!config.logging.logs.enabled) return;
if (!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) {
- console.log("Not logging this type of event");
return;
}
if (config.logging.logs.channel) {