blob: 98ffcffbdba3ab09a5148f03202aca501d8c4033 [file] [log] [blame]
import type { NucleusClient } from "../utils/client.js";
import { LinkCheck, MalwareCheck, NSFWCheck, SizeCheck, TestString, TestImage } from "../reflex/scanners.js";
import logAttachment from "../premium/attachmentLogs.js";
import { messageException } from "../utils/createTemporaryStorage.js";
import getEmojiByName from "../utils/getEmojiByName.js";
import client from "../utils/client.js";
import { callback as statsChannelUpdate } from "../reflex/statsChannelUpdate.js";
import { ChannelType, GuildMember, Message, ThreadChannel } from "discord.js";
import singleNotify from "../utils/singleNotify.js";
export const event = "messageCreate";
function checkUserForExceptions(user: GuildMember, exceptions: { roles: string[]; users: string[] }) {
if (exceptions.users.includes(user.id)) return true;
for (const role of user.roles.cache.values()) {
if (exceptions.roles.includes(role.id)) return true;
}
return false;
}
export async function callback(_client: NucleusClient, message: Message) {
if (!message.guild) return;
const config = await client.memory.readGuildInfo(message.guild.id);
if (
config.autoPublish.enabled &&
config.autoPublish.channels.includes(message.channel.id) &&
message.channel.type === ChannelType.GuildAnnouncement &&
message.reference === null
) {
if (message.channel.permissionsFor(message.guild.members.me!)!.has("ManageMessages")) {
await message.crosspost();
} else {
await singleNotify(
`Nucleus does not have Manage Messages in <#${message.channel.id}>`,
message.guild.id,
true
);
}
}
if (message.author.bot) return;
if (message.channel.isDMBased()) return;
try {
await statsChannelUpdate((await message.guild.members.fetch(message.author.id)).user, message.guild);
} catch (e) {
console.log(e);
}
const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
const fileNames = await logAttachment(message);
const lowerCaseContent = message.content.toLowerCase() || "";
if (config.filters.clean.channels.includes(message.channel.id)) {
if (!checkUserForExceptions(message.member!, config.filters.clean.allowed)) return await message.delete();
}
const filter = getEmojiByName("ICONS.FILTER");
let attachmentJump = "";
// console.log(config.logging.attachments.saved);
if (config.logging.attachments.saved[message.channel.id + message.id]) {
attachmentJump = ` [[View attachments]](${config.logging.attachments.saved[message.channel.id + message.id]})`;
}
const list = {
messageId: entry(message.id, `\`${message.id}\``),
sentBy: entry(message.author.id, renderUser(message.author)),
sentIn: entry(message.channel.id, renderChannel(message.channel)),
deleted: entry(Date.now(), renderDelta(Date.now())),
mentions: message.mentions.users.size,
attachments: entry(message.attachments.size, message.attachments.size + attachmentJump),
repliedTo: entry(
(message.reference ? message.reference.messageId : null) ?? null,
message.reference
? `[[Jump to message]](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.reference.messageId})`
: "None"
)
};
if (config.filters.invite.enabled) {
if (
!(
config.filters.invite.allowed.channels.includes(message.channel.id) ||
checkUserForExceptions(message.member!, config.filters.invite.allowed)
) &&
/(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/.test(lowerCaseContent)
) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
const data = {
meta: {
type: "messageDelete",
displayName: "Message Deleted",
calculateType: "autoModeratorDeleted",
color: NucleusColors.red,
emoji: "MESSAGE.DELETE",
timestamp: Date.now()
},
separate: {
start:
filter +
" Contained invite\n\n" +
(lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*")
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
}
if (fileNames.files.length > 0) {
for (const element of fileNames.files) {
const url = element.url ? element.url : element.local;
if (
/\.(jpg|jpeg|png|apng|gif|gifv|webm|webp|mp4|wav|mp3|ogg|jfif|mpeg-\d|avi|h\.264|h\.265)$/.test(
url.toLowerCase()
)
) {
// j(pe?g|fif)|a?png|gifv?|w(eb[mp]|av)|mp([34]|eg-\d)|ogg|avi|h\.26(4|5)
// ^no
if (
config.filters.images.NSFW &&
!(message.channel instanceof ThreadChannel ? message.channel.parent?.nsfw : message.channel.nsfw) &&
(await NSFWCheck(element.url))
) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
const data = {
meta: {
type: "messageDelete",
displayName: "Message Deleted",
calculateType: "autoModeratorDeleted",
color: NucleusColors.red,
emoji: "MESSAGE.DELETE",
timestamp: Date.now()
},
separate: {
start:
filter +
" Image detected as NSFW\n\n" +
(lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*")
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
if (config.filters.wordFilter.enabled) {
const text = await TestImage(url);
const check = TestString(
text ?? "",
config.filters.wordFilter.words.loose,
config.filters.wordFilter.words.strict
);
if (
check !== null &&
(!checkUserForExceptions(message.member!, config.filters.wordFilter.allowed) ||
!config.filters.wordFilter.allowed.channels.includes(message.channel.id))
) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
const data = {
meta: {
type: "messageDelete",
displayName: "Message Deleted",
calculateType: "autoModeratorDeleted",
color: NucleusColors.red,
emoji: "MESSAGE.DELETE",
timestamp: Date.now()
},
separate: {
start:
filter +
" Image contained filtered word\n\n" +
(lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*")
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
}
if (config.filters.images.size) {
if (url.match(/\.+(webp|png|jpg)$/gi)) {
if (!(await SizeCheck(element))) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
const data = {
meta: {
type: "messageDelete",
displayName: "Message Deleted",
calculateType: "autoModeratorDeleted",
color: NucleusColors.red,
emoji: "MESSAGE.DELETE",
timestamp: Date.now()
},
separate: {
start:
filter +
" Image was too small\n\n" +
(lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*")
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
}
}
}
if (config.filters.malware && (await MalwareCheck(url))) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
const data = {
meta: {
type: "messageDelete",
displayName: "Message Deleted",
calculateType: "autoModeratorDeleted",
color: NucleusColors.red,
emoji: "MESSAGE.DELETE",
timestamp: Date.now()
},
separate: {
start:
filter +
" File detected as malware\n\n" +
(lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*")
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
}
}
const linkDetectionTypes = await LinkCheck(message);
if (linkDetectionTypes.length > 0) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
const data = {
meta: {
type: "messageDelete",
displayName: "Message Deleted",
calculateType: "autoModeratorDeleted",
color: NucleusColors.red,
emoji: "MESSAGE.DELETE",
timestamp: Date.now()
},
separate: {
start:
filter +
` Link filtered as ${linkDetectionTypes[0]?.toLowerCase()}\n\n` +
(lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*")
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
if (
config.filters.wordFilter.enabled &&
!(
checkUserForExceptions(message.member!, config.filters.wordFilter.allowed) ||
config.filters.wordFilter.allowed.channels.includes(message.channel.id)
)
) {
const check = TestString(
lowerCaseContent,
config.filters.wordFilter.words.loose,
config.filters.wordFilter.words.strict
);
if (check !== null) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
const data = {
meta: {
type: "messageDelete",
displayName: "Message Deleted",
calculateType: "autoModeratorDeleted",
color: NucleusColors.red,
emoji: "MESSAGE.DELETE",
timestamp: Date.now()
},
separate: {
start:
filter +
" Message contained filtered word\n\n" +
(lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*")
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
}
if (
config.filters.pings.everyone &&
message.mentions.everyone &&
!checkUserForExceptions(message.member!, config.filters.pings.allowed)
) {
if (!(await isLogging(message.guild.id, "messageMassPing"))) return;
const data = {
meta: {
type: "everyonePing",
displayName: "Everyone Pinged",
calculateType: "messageMassPing",
color: NucleusColors.yellow,
emoji: "MESSAGE.PING.EVERYONE",
timestamp: Date.now()
},
separate: {
start: lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*"
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
if (config.filters.pings.roles && !checkUserForExceptions(message.member!, config.filters.pings.allowed)) {
for (const roleId in message.mentions.roles) {
if (!config.filters.pings.allowed.roles.includes(roleId)) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
if (!(await isLogging(message.guild.id, "messageMassPing"))) return;
const data = {
meta: {
type: "rolePing",
displayName: "Role Pinged",
calculateType: "messageMassPing",
color: NucleusColors.yellow,
emoji: "MESSAGE.PING.ROLE",
timestamp: Date.now()
},
separate: {
start: lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*"
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
}
}
if (
message.mentions.users.size >= config.filters.pings.mass &&
config.filters.pings.mass &&
!checkUserForExceptions(message.member!, config.filters.pings.allowed)
) {
messageException(message.guild.id, message.channel.id, message.id);
await message.delete();
if (!(await isLogging(message.guild.id, "messageMassPing"))) return;
const data = {
meta: {
type: "massPing",
displayName: "Mass Ping",
calculateType: "messageMassPing",
color: NucleusColors.yellow,
emoji: "MESSAGE.PING.MASS",
timestamp: Date.now()
},
separate: {
start: lowerCaseContent
? `**Message:**\n\`\`\`${message.content}\`\`\``
: "**Message:** *Message had no content*"
},
list: list,
hidden: {
guild: message.channel.guild.id
}
};
return log(data);
}
}