|
|
|
|
@ -12,13 +12,16 @@ import EmojiEmbed from "../utils/generateEmojiEmbed.js";
|
|
|
|
|
import getEmojiByName from "../utils/getEmojiByName.js";
|
|
|
|
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
|
|
|
|
|
import config from "../config/main.js";
|
|
|
|
|
import GIFEncoder from "gifencoder";
|
|
|
|
|
import gm_var from 'gm';
|
|
|
|
|
const gm = gm_var.subClass({ imageMagick: '7+' });
|
|
|
|
|
|
|
|
|
|
interface NSFWSchema {
|
|
|
|
|
nsfw: boolean;
|
|
|
|
|
errored?: boolean;
|
|
|
|
|
}
|
|
|
|
|
interface MalwareSchema {
|
|
|
|
|
safe: boolean;
|
|
|
|
|
malware: boolean;
|
|
|
|
|
errored?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -29,15 +32,28 @@ const clamscanner = await new ClamScan().init({
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export async function testNSFW(link: string): Promise<NSFWSchema> {
|
|
|
|
|
const [fileStream, hash] = await streamAttachment(link);
|
|
|
|
|
export async function testNSFW(attachment: {
|
|
|
|
|
url: string;
|
|
|
|
|
local: string;
|
|
|
|
|
height: number | null;
|
|
|
|
|
width: number | null;
|
|
|
|
|
}): Promise<NSFWSchema> {
|
|
|
|
|
const [fileStream, hash] = await streamAttachment(attachment.url);
|
|
|
|
|
const alreadyHaveCheck = await client.database.scanCache.read(hash);
|
|
|
|
|
if (alreadyHaveCheck?.nsfw !== undefined) return { nsfw: alreadyHaveCheck.nsfw };
|
|
|
|
|
if (alreadyHaveCheck && ("nsfw" in alreadyHaveCheck!)) {
|
|
|
|
|
return { nsfw: alreadyHaveCheck.nsfw }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const image = gm(fileStream).command('convert').in('-')
|
|
|
|
|
|
|
|
|
|
const encoder = new GIFEncoder(attachment.width ?? 1024, attachment.height ?? 1024);
|
|
|
|
|
|
|
|
|
|
const image = tf.tensor3d(new Uint8Array(fileStream));
|
|
|
|
|
|
|
|
|
|
const predictions = (await nsfw_model.classify(image, 1))[0]!;
|
|
|
|
|
image.dispose();
|
|
|
|
|
// const array = new Uint8Array(fileStream);
|
|
|
|
|
const img = tf.node.decodeImage(array) as tf.Tensor3D;
|
|
|
|
|
|
|
|
|
|
const predictions = (await nsfw_model.classify(img, 1))[0]!;
|
|
|
|
|
console.log(2, predictions);
|
|
|
|
|
|
|
|
|
|
const nsfw = predictions.className === "Hentai" || predictions.className === "Porn";
|
|
|
|
|
await client.database.scanCache.write(hash, "nsfw", nsfw);
|
|
|
|
|
@ -48,51 +64,42 @@ export async function testNSFW(link: string): Promise<NSFWSchema> {
|
|
|
|
|
export async function testMalware(link: string): Promise<MalwareSchema> {
|
|
|
|
|
const [fileName, hash] = await saveAttachment(link);
|
|
|
|
|
const alreadyHaveCheck = await client.database.scanCache.read(hash);
|
|
|
|
|
if (alreadyHaveCheck?.malware !== undefined) return { safe: alreadyHaveCheck.malware };
|
|
|
|
|
if (alreadyHaveCheck?.malware !== undefined) return { malware: alreadyHaveCheck.malware };
|
|
|
|
|
let malware;
|
|
|
|
|
try {
|
|
|
|
|
malware = (await clamscanner.scanFile(fileName)).isInfected;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return { safe: true };
|
|
|
|
|
return { malware: true };
|
|
|
|
|
}
|
|
|
|
|
await client.database.scanCache.write(hash, "malware", malware);
|
|
|
|
|
return { safe: !malware };
|
|
|
|
|
return { malware };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function testLink(link: string): Promise<{ safe: boolean; tags: string[] }> {
|
|
|
|
|
const alreadyHaveCheck = await client.database.scanCache.read(link);
|
|
|
|
|
if (alreadyHaveCheck?.bad_link !== undefined) return { safe: alreadyHaveCheck.bad_link, tags: alreadyHaveCheck.tags ?? [] };
|
|
|
|
|
const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/link", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"X-RapidAPI-Key": client.config.rapidApiKey,
|
|
|
|
|
"X-RapidAPI-Host": "unscan.p.rapidapi.com"
|
|
|
|
|
},
|
|
|
|
|
body: `{"link":"${link}"}`
|
|
|
|
|
})
|
|
|
|
|
.then((response) => response.json() as Promise<MalwareSchema>)
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
console.error(err);
|
|
|
|
|
return { safe: true, tags: [] };
|
|
|
|
|
});
|
|
|
|
|
await client.database.scanCache.write(link, "bad_link", scanned.safe ?? true, scanned.tags ?? []);
|
|
|
|
|
return {
|
|
|
|
|
safe: scanned.safe ?? true,
|
|
|
|
|
tags: scanned.tags ?? []
|
|
|
|
|
};
|
|
|
|
|
if (alreadyHaveCheck?.bad_link !== undefined)
|
|
|
|
|
return { safe: alreadyHaveCheck.bad_link, tags: alreadyHaveCheck.tags ?? [] };
|
|
|
|
|
return { safe: true, tags: [] };
|
|
|
|
|
// const scanned: { safe?: boolean; tags?: string[] } = {}
|
|
|
|
|
// await client.database.scanCache.write(link, "bad_link", scanned.safe ?? true, scanned.tags ?? []);
|
|
|
|
|
// return {
|
|
|
|
|
// safe: scanned.safe ?? true,
|
|
|
|
|
// tags: scanned.tags ?? []
|
|
|
|
|
// };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function streamAttachment(link: string): Promise<[ArrayBuffer, string]> {
|
|
|
|
|
export async function streamAttachment(link: string): Promise<[Buffer, string]> {
|
|
|
|
|
const image = await (await fetch(link)).arrayBuffer();
|
|
|
|
|
const enc = new TextDecoder("utf-8");
|
|
|
|
|
return [image, createHash("sha512").update(enc.decode(image), "base64").digest("base64")];
|
|
|
|
|
const buf = Buffer.from(image);
|
|
|
|
|
return [buf, createHash("sha512").update(enc.decode(image), "base64").digest("base64")];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function saveAttachment(link: string): Promise<[string, string]> {
|
|
|
|
|
const image = await (await fetch(link)).arrayBuffer();
|
|
|
|
|
const fileName = await generateFileName(link.split("/").pop()!.split(".").pop()!);
|
|
|
|
|
const enc = new TextDecoder("utf-8");
|
|
|
|
|
writeFileSync(fileName, new DataView(image), "base64");
|
|
|
|
|
writeFileSync(fileName, new DataView(image));
|
|
|
|
|
return [fileName, createHash("sha512").update(enc.decode(image), "base64").digest("base64")];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -147,10 +154,16 @@ export async function LinkCheck(message: Discord.Message): Promise<string[]> {
|
|
|
|
|
return detectionsTypes as string[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function NSFWCheck(element: string): Promise<boolean> {
|
|
|
|
|
export async function NSFWCheck(element: {
|
|
|
|
|
url: string;
|
|
|
|
|
local: string;
|
|
|
|
|
height: number | null;
|
|
|
|
|
width: number | null;
|
|
|
|
|
}): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
return (await testNSFW(element)).nsfw;
|
|
|
|
|
} catch {
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log(e)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -163,7 +176,7 @@ export async function SizeCheck(element: { height: number | null; width: number
|
|
|
|
|
|
|
|
|
|
export async function MalwareCheck(element: string): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
return (await testMalware(element)).safe;
|
|
|
|
|
return (await testMalware(element)).malware;
|
|
|
|
|
} catch {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
@ -200,15 +213,20 @@ export async function TestImage(url: string): Promise<string | null> {
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function doMemberChecks(member: Discord.GuildMember, guild: Discord.Guild): Promise<void> {
|
|
|
|
|
export async function doMemberChecks(member: Discord.GuildMember): Promise<void> {
|
|
|
|
|
if (member.user.bot) return;
|
|
|
|
|
console.log("Checking member " + member.user.tag)
|
|
|
|
|
const guild = member.guild;
|
|
|
|
|
const guildData = await client.database.guilds.read(guild.id);
|
|
|
|
|
if (!guildData.logging.staff.channel) return;
|
|
|
|
|
const [loose, strict] = [guildData.filters.wordFilter.words.loose, guildData.filters.wordFilter.words.strict];
|
|
|
|
|
console.log(1, loose, strict)
|
|
|
|
|
// Does the username contain filtered words
|
|
|
|
|
const usernameCheck = TestString(member.user.username, loose, strict, guildData.filters.wordFilter.enabled);
|
|
|
|
|
console.log(2, usernameCheck)
|
|
|
|
|
// Does the nickname contain filtered words
|
|
|
|
|
const nicknameCheck = TestString(member.nickname ?? "", loose, strict, guildData.filters.wordFilter.enabled);
|
|
|
|
|
console.log(3, nicknameCheck)
|
|
|
|
|
// Does the profile picture contain filtered words
|
|
|
|
|
const avatarTextCheck = TestString(
|
|
|
|
|
(await TestImage(member.user.displayAvatarURL({ forceStatic: true }))) ?? "",
|
|
|
|
|
@ -216,15 +234,19 @@ export async function doMemberChecks(member: Discord.GuildMember, guild: Discord
|
|
|
|
|
strict,
|
|
|
|
|
guildData.filters.wordFilter.enabled
|
|
|
|
|
);
|
|
|
|
|
console.log(4, avatarTextCheck)
|
|
|
|
|
// Is the profile picture NSFW
|
|
|
|
|
const avatar = member.displayAvatarURL({ extension: "png", size: 1024, forceStatic: true });
|
|
|
|
|
const avatarCheck =
|
|
|
|
|
guildData.filters.images.NSFW && (await NSFWCheck(member.user.displayAvatarURL({ forceStatic: true })));
|
|
|
|
|
guildData.filters.images.NSFW && (await NSFWCheck({url: avatar, local: "", height: 1024, width: 1024}));
|
|
|
|
|
console.log(5, avatarCheck)
|
|
|
|
|
// Does the username contain an invite
|
|
|
|
|
const inviteCheck = guildData.filters.invite.enabled && /discord\.gg\/[a-zA-Z0-9]+/gi.test(member.user.username);
|
|
|
|
|
console.log(6, inviteCheck)
|
|
|
|
|
// Does the nickname contain an invite
|
|
|
|
|
const nicknameInviteCheck =
|
|
|
|
|
guildData.filters.invite.enabled && /discord\.gg\/[a-zA-Z0-9]+/gi.test(member.nickname ?? "");
|
|
|
|
|
|
|
|
|
|
console.log(7, nicknameInviteCheck)
|
|
|
|
|
if (
|
|
|
|
|
usernameCheck !== null ||
|
|
|
|
|
nicknameCheck !== null ||
|
|
|
|
|
|