worked on scanners, database, tracks, some moving around and cleaning up files.

pull/11/head
TheCodedProf 3 years ago
parent 14d11f6c24
commit b5e9d55b0b

@ -1,4 +1,3 @@
? Role all
Server rules
verificationRequired on welcome
// TODO !IMPORTANT! URL + image hash + file hash database

@ -21,6 +21,5 @@
"everyone": true,
"roles": true
}
},
"tracks": []
}
}

@ -8,6 +8,7 @@ import getEmojiByName from "../../utils/getEmojiByName.js";
import createPageIndicator from "../../utils/createPageIndicator.js";
import { configToDropdown } from "../../actions/roleMenu.js";
import { modalInteractionCollector } from "../../utils/dualCollector.js";
import ellipsis from "../../utils/ellipsis.js";
import lodash from 'lodash';
const isEqual = lodash.isEqual;
@ -44,8 +45,9 @@ const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message,
.addComponents(
new StringSelectMenuBuilder()
.setCustomId("reorder")
.setPlaceholder("Select a page to move...")
.setMinValues(1)
.setPlaceholder("Select all pages in the order you want them to appear.")
.setMinValues(currentObj.length)
.setMaxValues(currentObj.length)
.addOptions(
currentObj.map((o, i) => new StringSelectMenuOptionBuilder()
.setLabel(o.name)
@ -81,6 +83,7 @@ const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message,
out = null;
}
if(!out) return;
out.deferUpdate();
if (out.isButton()) return;
if(!out.values) return;
const values = out.values;
@ -160,12 +163,7 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele
}
const ellipsis = (str: string, max: number): string => {
if (str.length <= max) return str;
return str.slice(0, max - 3) + "...";
}
const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise<ObjectSchema | null> => {
const editRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise<ObjectSchema | null> => {
if (!data) data = {
name: "Role Menu Page",
description: "A new role menu page",
@ -321,7 +319,7 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
let modified = false;
do {
const embed = new EmojiEmbed()
.setTitle("Role Menu Settings")
.setTitle("Role Menu")
.setEmoji("GUILD.GREEN")
.setStatus("Success");
const noRoleMenus = currentObject.length === 0;
@ -377,7 +375,7 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
.setDisabled(!modified),
);
if(noRoleMenus) {
embed.setDescription("No role menu page have been set up yet. Use the button below to add one.\n\n" +
embed.setDescription("No role menu pages have been set up yet. Use the button below to add one.\n\n" +
createPageIndicator(1, 1, undefined, true)
);
pageSelect.setDisabled(true);
@ -390,7 +388,7 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
page = Math.min(page, Object.keys(currentObject).length - 1);
current = currentObject[page]!;
embed.setDescription(`**Currently Editing:** ${current.name}\n\n` +
`**Description:** \`${current.description}\`\n` +
`**Description:**\n> ${current.description}\n` +
`\n\n${createPageIndicator(Object.keys(config.roleMenu.options).length, page)}`
);
@ -424,7 +422,7 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
page++;
break;
case "add":
let newPage = await createRoleMenuPage(i, m)
let newPage = await editRoleMenuPage(i, m)
if(!newPage) break;
currentObject.push();
page = currentObject.length - 1;
@ -444,7 +442,7 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
case "action":
switch(i.values[0]) {
case "edit":
let edited = await createRoleMenuPage(i, m, current!);
let edited = await editRoleMenuPage(i, m, current!);
if(!edited) break;
currentObject[page] = edited;
modified = true;

@ -1,16 +1,405 @@
import type { CommandInteraction, GuildMember, SlashCommandSubcommandBuilder } from "discord.js";
import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, CommandInteraction, GuildMember, Message, ModalBuilder, ModalSubmitInteraction, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
import client from "../../utils/client.js";
import createPageIndicator, { createVerticalTrack } from "../../utils/createPageIndicator.js";
import { LoadingEmbed } from "../../utils/defaults.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
import ellipsis from "../../utils/ellipsis.js";
import { modalInteractionCollector } from "../../utils/dualCollector.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
.setName("tracks")
.setDescription("Manage the tracks for the server")
interface ObjectSchema {
name: string;
retainPrevious: boolean;
nullable: boolean;
track: string[];
manageableBy: string[];
}
const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, current?: string) => {
let name = current ?? "";
const modal = new ModalBuilder()
.setTitle("Edit Name and Description")
.setCustomId("editNameDescription")
.addComponents(
new ActionRowBuilder<TextInputBuilder>()
.addComponents(
new TextInputBuilder()
.setLabel("Name")
.setCustomId("name")
.setPlaceholder("Name here...") // TODO: Make better placeholder
.setStyle(TextInputStyle.Short)
.setValue(name ?? "")
.setRequired(true)
)
)
const button = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("back")
.setLabel("Back")
.setStyle(ButtonStyle.Secondary)
.setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
)
await i.showModal(modal)
await interaction.editReply({
embeds: [
new EmojiEmbed()
.setTitle("Tracks")
.setDescription("Modal opened. If you can't see it, click back and try again.")
.setStatus("Success")
],
components: [button]
});
let out: ModalSubmitInteraction | null;
try {
out = await modalInteractionCollector(
m,
(m) => m.channel!.id === interaction.channel!.id,
(_) => true
) as ModalSubmitInteraction | null;
} catch (e) {
console.error(e);
out = null;
}
if(!out) return name;
if (out.isButton()) return name;
if(!out.fields) return name;
name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
return name
}
const reorderTracks = async (interaction: ButtonInteraction, m: Message, roles: Collection<string, Role>, currentObj: string[]) => {
let reorderRow = new ActionRowBuilder<StringSelectMenuBuilder>()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId("reorder")
.setPlaceholder("Select all roles in the order you want users to gain them (Lowest to highest rank).")
.setMinValues(currentObj.length)
.setMaxValues(currentObj.length)
.addOptions(
currentObj.map((o, i) => new StringSelectMenuOptionBuilder()
.setLabel(roles.get(o)!.name)
.setValue(i.toString())
)
)
);
let buttonRow = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("back")
.setLabel("Back")
.setStyle(ButtonStyle.Secondary)
.setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
)
await interaction.editReply({
embeds: [
new EmojiEmbed()
.setTitle("Tracks")
.setDescription("Select all roles in the order you want users to gain them (Lowest to highest rank).")
.setStatus("Success")
],
components: [reorderRow, buttonRow]
});
let out: StringSelectMenuInteraction | ButtonInteraction | null;
try {
out = await m.awaitMessageComponent({
filter: (i) => i.channel!.id === interaction.channel!.id,
time: 300000
}) as StringSelectMenuInteraction | ButtonInteraction | null;
} catch (e) {
console.error(e);
out = null;
}
if(!out) return;
out.deferUpdate();
if (out.isButton()) return;
if(!out.values) return;
const values = out.values;
const newOrder: string[] = currentObj.map((_, i) => {
const index = values.findIndex(v => v === i.toString());
return currentObj[index];
}) as string[];
return newOrder;
}
const editTrack = async (interaction: ButtonInteraction | StringSelectMenuInteraction, message: Message, roles: Collection<string, Role>, current?: ObjectSchema) => {
if(!current) {
current = {
name: "",
retainPrevious: false,
nullable: false,
track: [],
manageableBy: []
}
}
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("edit")
.setLabel("Edit Name")
.setStyle(ButtonStyle.Primary)
.setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
new ButtonBuilder()
.setCustomId("reorder")
.setLabel("Reorder")
.setStyle(ButtonStyle.Primary)
.setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji),
);
const roleSelect = new ActionRowBuilder<RoleSelectMenuBuilder>()
.addComponents(
new RoleSelectMenuBuilder()
.setCustomId("addRole")
.setPlaceholder("Select a role to add")
);
let closed = false;
do {
const editableRoles: string[] = current.track.map((r) => {
if(!(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position)) return r;
}).filter(v => v !== undefined) as string[];
const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId("removeRole")
.setPlaceholder("Select a role to remove")
.addOptions(
editableRoles.map((r, i) => {
return new StringSelectMenuOptionBuilder()
.setLabel(r)
.setValue(i.toString())}
)
)
);
let allowed: boolean[] = [];
for (const role of current.track) {
const disabled: boolean =
roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position;
allowed.push(disabled)
}
const embed = new EmojiEmbed()
.setTitle("Tracks")
.setDescription(
`**Currently Editing:** ${current.name}\n\n` +
`${getEmojiByName} Members ${current.nullable ? "don't " : ""}need a role in this track` +
`${getEmojiByName} Members ${current.retainPrevious ? "don't " : ""}keep all roles below their current highest` +
createVerticalTrack(current.track, new Array(current.track.length).fill(false), allowed)
)
interaction.editReply({embeds: [embed], components: [buttons, roleSelect, selectMenu]});
let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
try {
out = await message.awaitMessageComponent({
filter: (i) => i.channel!.id === interaction.channel!.id,
time: 300000
}) as ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
} catch (e) {
console.error(e);
out = null;
}
if(!out) return;
if (out.isButton()) {
out.deferUpdate();
switch(out.customId) {
case "back":
closed = true;
break;
case "edit":
current.name = (await editName(out, interaction, message, current.name))!;
break;
case "reorder":
current.track = (await reorderTracks(out, message, roles, current.track))!;
}
} else if (out.isStringSelectMenu()) {
out.deferUpdate();
switch(out.customId) {
case "removeRole":
const index = current.track.findIndex(v => v === editableRoles[parseInt((out! as StringSelectMenuInteraction).values![0]!)]);
current.track.splice(index, 1);
break;
}
} else {
switch(out.customId) {
case "addRole":
const role = out.values![0]!;
if(!current.track.includes(role)) {
current.track.push(role);
}
out.reply({content: "That role is already on this track", ephemeral: true})
break;
}
}
} while(!closed);
return current;
}
const callback = async (interaction: CommandInteraction) => {
const m = await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true})
const config = await client.database.guilds.read(interaction.guild!.id);
const tracks: ObjectSchema[] = config.tracks;
const roles = await interaction.guild!.roles.fetch();
const memberRoles = interaction.member!.roles;
const member = interaction.member as GuildMember;
let page = 0;
let closed = false;
let modified = false;
do {
const embed = new EmojiEmbed()
.setTitle("Track Settings")
.setEmoji("TRACKS.ICON")
.setStatus("Success");
const noTracks = config.tracks.length === 0;
let current: ObjectSchema;
const pageSelect = new StringSelectMenuBuilder()
.setCustomId("page")
.setPlaceholder("Select a track to manage");
const actionSelect = new StringSelectMenuBuilder()
.setCustomId("action")
.setPlaceholder("Perform an action")
.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel("Edit")
.setDescription("Edit this track")
.setValue("edit")
.setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
new StringSelectMenuOptionBuilder()
.setLabel("Delete")
.setDescription("Delete this track")
.setValue("delete")
.setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
);
const buttonRow = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("back")
.setStyle(ButtonStyle.Primary)
.setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
.setDisabled(page === 0),
new ButtonBuilder()
.setCustomId("next")
.setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
.setStyle(ButtonStyle.Primary)
.setDisabled(page === Object.keys(tracks).length - 1),
new ButtonBuilder()
.setCustomId("add")
.setLabel("New Track")
.setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
.setStyle(ButtonStyle.Secondary)
.setDisabled(Object.keys(tracks).length >= 24),
new ButtonBuilder()
.setCustomId("save")
.setLabel("Save")
.setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
.setStyle(ButtonStyle.Success)
.setDisabled(!modified),
);
if(noTracks) {
embed.setDescription("No tracks have been set up yet. Use the button below to add one.\n\n" +
createPageIndicator(1, 1, undefined, true)
);
pageSelect.setDisabled(true);
actionSelect.setDisabled(true);
pageSelect.addOptions(new StringSelectMenuOptionBuilder()
.setLabel("No tracks")
.setValue("none")
);
} else {
page = Math.min(page, Object.keys(tracks).length - 1);
current = tracks[page]!;
embed.setDescription(`**Currently Editing:** ${current.name}\n\n` +
`${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${current.nullable ? "don't " : ""}need a role in this track\n` +
`${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${current.retainPrevious ? "" : "don't "}keep all roles below their current highest\n\n` +
createVerticalTrack(current.track, new Array(current.track.length).fill(false)) +
`\n${createPageIndicator(config.tracks.length, page)}`
);
pageSelect.addOptions(
tracks.map((key: ObjectSchema, index) => {
return new StringSelectMenuOptionBuilder()
.setLabel(ellipsis(key.name, 50))
.setDescription(ellipsis(roles.get(key.track[0]!)?.name!, 50))
.setValue(index.toString());
})
);
}
await interaction.editReply({embeds: [embed], components: [new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect), new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect), buttonRow]});
let i: StringSelectMenuInteraction | ButtonInteraction;
try {
i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction;
} catch (e) {
closed = true;
break;
}
await i.deferUpdate();
if (i.isButton()) {
switch (i.customId) {
case "back":
page--;
break;
case "next":
page++;
break;
case "add":
let newPage = await editTrack(i, m, roles)
if(!newPage) break;
tracks.push();
page = tracks.length - 1;
break;
case "save":
// client.database.guilds.write(interaction.guild!.id, {"roleMenu.options": tracks}); // TODO
modified = false;
break;
}
} else if (i.isStringSelectMenu()) {
switch (i.customId) {
case "action":
switch(i.values[0]) {
case "edit":
let edited = await editTrack(i, m, roles, current!);
if(!edited) break;
tracks[page] = edited;
modified = true;
break;
case "delete":
if(page === 0 && tracks.keys.length - 1 > 0) page++;
else page--;
tracks.splice(page, 1);
break;
}
break;
case "page":
page = parseInt(i.values[0]!);
break;
}
}
} while (!closed)
}

@ -1,10 +1,11 @@
import { LoadingEmbed } from "../../utils/defaults.js";
import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, SelectMenuOptionBuilder, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction } from "discord.js";
import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, APIMessageComponentEmoji, StringSelectMenuBuilder, MessageComponentInteraction, StringSelectMenuInteraction, StringSelectMenuOptionBuilder } from "discord.js";
import type { SlashCommandSubcommandBuilder } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
import addPlural from "../../utils/plurals.js";
import client from "../../utils/client.js";
import { createVerticalTrack } from "../../utils/createPageIndicator.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
@ -12,17 +13,8 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
.setDescription("Moves a user along a role track")
.addUserOption((option) => option.setName("user").setDescription("The user to manage").setRequired(true));
const generateFromTrack = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
active = active ? "ACTIVE" : "INACTIVE";
disabled = disabled ? "GREY." : "";
if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
};
const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const { renderUser } = client.logger;
const { renderUser, renderRole} = client.logger;
const member = interaction.options.getMember("user") as GuildMember;
const guild = interaction.guild;
if (!guild) return;
@ -44,10 +36,10 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const dropdown = new Discord.StringSelectMenuBuilder()
.addOptions(
config.tracks.map((option, index) => {
const hasRoleInTrack = option.track.some((element: string) => {
const hasRoleInTrack: boolean = option.track.some((element: string) => {
return memberRoles.cache.has(element);
});
return new SelectMenuOptionBuilder({
return new StringSelectMenuOptionBuilder({
default: index === track,
label: option.name,
value: index.toString(),
@ -68,33 +60,23 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
(data.retainPrevious
? "When promoted, the user keeps previous roles"
: "Members will lose their current role when promoted") + "\n";
generated +=
"\n" +
data.track
.map((role, index) => {
const allow: boolean =
roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position &&
!managed;
allowed.push(!allow);
return (
getEmojiByName(
generateFromTrack(index, memberRoles.cache.has(role), data.track.length, allow)
) +
" " +
roles.get(role)!.name +
" [<@&" +
roles.get(role)!.id +
">]"
for (const role of data.track) {
const disabled: boolean =
roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position && !managed;
allowed.push(!disabled)
}
generated += "\n" + createVerticalTrack(
data.track.map((role) => renderRole(roles.get(role)!)),
data.track.map((role) => memberRoles.cache.has(role)),
allowed.map((allow) => !allow)
);
})
.join("\n");
const selected = [];
for (const position of data.track) {
if (memberRoles.cache.has(position)) selected.push(position);
}
const conflict = data.retainPrevious ? false : selected.length > 1;
let conflictDropdown: StringSelectMenuBuilder[] = [];
const conflictDropdownOptions: SelectMenuOptionBuilder[] = [];
const conflictDropdownOptions: StringSelectMenuOptionBuilder[] = [];
let currentRoleIndex: number = -1;
if (conflict) {
generated += `\n\n${getEmojiByName(`PUNISH.WARN.${managed ? "YELLOW" : "RED"}`)} This user has ${
@ -106,10 +88,9 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
"In order to promote or demote this user, you must select which role the member should keep.";
selected.forEach((role) => {
conflictDropdownOptions.push(
new SelectMenuOptionBuilder({
label: roles.get(role)!.name,
value: roles.get(role)!.id
})
new StringSelectMenuOptionBuilder()
.setLabel(roles.get(role)!.name)
.setValue(roles.get(role)!.id)
);
});
conflictDropdown = [

@ -25,7 +25,7 @@
"ATTACHMENT": "997570687193587812",
"LOGGING": "999613304446144562",
"SAVE": "1065722246322200586",
"SHUFFLE": "1067913930304921690",
"SHUFFLE": "1069323453909454890",
"NOTIFY": {
"ON": "1000726394579464232",
"OFF": "1000726363495477368"

@ -1,10 +1,11 @@
import fetch from "node-fetch";
import FormData from "form-data";
import { writeFileSync, createReadStream } from "fs";
import fs, { writeFileSync, createReadStream } from "fs";
import generateFileName from "../utils/temp/generateFileName.js";
import Tesseract from "node-tesseract-ocr";
import type Discord from "discord.js";
import client from "../utils/client.js";
import { createHash } from "crypto";
interface NSFWSchema {
nsfw: boolean;
@ -14,7 +15,11 @@ interface MalwareSchema {
}
export async function testNSFW(link: string): Promise<NSFWSchema> {
const p = await saveAttachment(link);
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));
@ -32,13 +37,17 @@ export async function testNSFW(link: string): Promise<NSFWSchema> {
return { nsfw: false };
});
console.log(result);
client.database.scanCache.write(hash, result.nsfw);
return { nsfw: result.nsfw };
}
export async function testMalware(link: string): Promise<MalwareSchema> {
const p = await saveAttachment(link);
const data = new FormData();
data.append("file", createReadStream(p));
const [p, hash] = await saveAttachment(link);
let alreadyHaveCheck = await client.database.scanCache.read(hash)
if(alreadyHaveCheck) return { safe: alreadyHaveCheck.data };
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",
@ -54,12 +63,15 @@ export async function testMalware(link: string): Promise<MalwareSchema> {
return { safe: true };
});
console.log(result);
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);
const scanned: { safe?: boolean; tags?: string[] } = await fetch("https://unscan.p.rapidapi.com/malware", {
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", {
method: "POST",
headers: {
"X-RapidAPI-Key": client.config.rapidApiKey,
@ -73,17 +85,18 @@ export async function testLink(link: string): Promise<{ safe: boolean; tags: str
return { safe: true, tags: [] };
});
console.log(scanned);
client.database.scanCache.write(link, scanned.safe ?? true, []);
return {
safe: scanned.safe ?? true,
tags: scanned.tags ?? []
};
}
export async function saveAttachment(link: string): Promise<string> {
export async function saveAttachment(link: string): Promise<[string, string]> {
const image = (await fetch(link)).arrayBuffer().toString();
const fileName = generateFileName(link.split("/").pop()!.split(".").pop()!);
writeFileSync(fileName, image, "base64");
return fileName;
return [fileName, createHash('sha512').update(image, 'base64').digest('base64')];
}
const linkTypes = {
@ -139,8 +152,7 @@ export async function LinkCheck(message: Discord.Message): Promise<string[]> {
export async function NSFWCheck(element: string): Promise<boolean> {
try {
const test = await testNSFW(element);
return test.nsfw;
return (await testNSFW(element)).nsfw;
} catch {
return false;
}

@ -2,7 +2,7 @@ import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBit
import { Logger } from "../utils/log.js";
import Memory from "../utils/memory.js";
import type { VerifySchema } from "../reflex/verify.js";
import { Guilds, History, ModNotes, Premium, PerformanceTest } from "../utils/database.js";
import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache } from "../utils/database.js";
import EventScheduler from "../utils/eventScheduler.js";
import type { RoleMenuSchema } from "../actions/roleMenu.js";
import config from "../config/main.json" assert { type: "json" };
@ -22,6 +22,7 @@ class NucleusClient extends Client {
premium: Premium;
eventScheduler: EventScheduler;
performanceTest: PerformanceTest;
scanCache: ScanCache;
};
preloadPage: Record<string, {command: string, argument: string}> = {}; // e.g. { channelID: { command: privacy, page: 3}}
commands: Record<string, [{
@ -51,7 +52,8 @@ const client = new NucleusClient({
notes: new ModNotes(),
premium: new Premium(),
eventScheduler: new EventScheduler(),
performanceTest: new PerformanceTest()
performanceTest: new PerformanceTest(),
scanCache: new ScanCache()
});
export default client;

@ -21,4 +21,23 @@ function pageIndicator(amount: number, selected: number, showDetails?: boolean,
return out;
}
export const verticalTrackIndicator = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
active = active ? "ACTIVE" : "INACTIVE";
disabled = disabled ? "GREY." : "";
if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
};
export const createVerticalTrack = (items: string[], active: boolean[], disabled?: boolean[]) => {
let out = "";
if (!disabled) disabled = new Array(items.length).fill(false);
for (let i = 0; i < items.length; i++) {
out += getEmojiByName(verticalTrackIndicator(i, active[i] ?? false, items.length, disabled[i] ?? false));
out += items[i] + "\n";
}
return out;
}
export default pageIndicator;

@ -148,6 +148,33 @@ export class History {
}
}
interface ScanCacheSchema {
addedAt: Date;
hash: string;
data: boolean;
tags: string[];
}
export class ScanCache {
scanCache: Collection<ScanCacheSchema>;
constructor() {
this.scanCache = database.collection<ScanCacheSchema>("scanCache");
}
async read(hash: string) {
return await this.scanCache.findOne({ hash: hash });
}
async write(hash: string, data: boolean, tags?: string[]) {
await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }); // TODO: cleanup function maybe
}
async cleanup() {
await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
}
}
export class PerformanceTest {
performanceData: Collection<PerformanceDataSchema>;

@ -0,0 +1,4 @@
export default (str: string, max: number): string => {
if (str.length <= max) return str;
return str.slice(0, max - 3) + "...";
}

@ -39,7 +39,7 @@ const record = async () => {
singleNotify(
"performanceTest",
config.developmentGuildID,
`Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${results.resources.memory}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``,
`Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${Math.round(results.resources.memory)}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``,
"Critical",
config.owners
)

@ -13,6 +13,6 @@
"skipLibCheck": true,
"noImplicitReturns": false
},
"include": ["src/**/*"],
"include": ["src/**/*", "src/index.d.ts"],
"exclude": ["src/Unfinished/**/*"]
}

Loading…
Cancel
Save