started fix on NSFW & Malware checking (using tfjs-node + NSFWJS & cl… (#39)

…amscan)
pull/67/head
Skyler 3 years ago committed by GitHub
commit 0c9ccb578f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,13 +1,16 @@
{
"dependencies": {
"@discordjs/rest": "^0.2.0-canary.0",
"@hokify/agenda": "^6.2.12",
"@tensorflow/tfjs": "^4.0.0",
"@tensorflow/tfjs-node": "^4.2.0",
"@total-typescript/ts-reset": "^0.3.7",
"@tsconfig/node18-strictest-esm": "^1.0.0",
"@types/node": "^18.14.6",
"@ungap/structured-clone": "^1.0.1",
"agenda": "^4.3.0",
"body-parser": "^1.20.0",
"canvas": "^2.11.0",
"clamscan": "^2.1.2",
"discord.js": "^14.7.1",
"eslint": "^8.21.0",
"express": "^4.18.1",
@ -18,12 +21,11 @@
"mongodb": "^4.7.0",
"node-fetch": "^3.3.0",
"node-tesseract-ocr": "^2.2.1",
"nsfwjs": "2.4.2",
"seedrandom": "^3.0.5",
"structured-clone": "^0.2.2",
"systeminformation": "^5.17.3"
},
"resolutions": {
"discord-api-types": "0.37.23"
},
"name": "nucleus",
"version": "0.0.1",
"description": "Nucleus: The core of your server",
@ -38,7 +40,8 @@
"lint-list": "echo 'Style checking...'; prettier --check .; echo 'Linting...'; eslint src; echo 'To view errors in more detail, please run `yarn lint`'; true",
"lint-ci": "echo 'Style checking...' && prettier --check . && echo 'Linting...' && eslint src",
"setup": "node Installer.js",
"win-force-build": "clear | rm -r dist | tsc-suppress"
"win-force-build": "clear | rm -r dist | tsc-suppress",
"audit-fix": "yarn-audit-fix"
},
"repository": {
"type": "git",
@ -57,6 +60,7 @@
"private": false,
"type": "module",
"devDependencies": {
"@types/clamscan": "^2.0.4",
"@types/lodash": "^4.14.191",
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
@ -64,6 +68,7 @@
"prettier": "^2.7.1",
"prettier-eslint": "^15.0.1",
"tsc-suppress": "^1.0.7",
"typescript": "^4.9.4"
"typescript": "^4.9.4",
"yarn-audit-fix": "^9.3.9"
}
}

@ -3,9 +3,9 @@ import Discord, {
GuildMember,
ActionRowBuilder,
ButtonBuilder,
User,
ButtonStyle,
SlashCommandSubcommandBuilder
SlashCommandSubcommandBuilder,
ButtonInteraction
} from "discord.js";
import confirmationMessage from "../../utils/confirmationMessage.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@ -29,8 +29,16 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
.setRequired(false)
);
const callback = async (interaction: CommandInteraction): Promise<void> => {
const callback = async (interaction: CommandInteraction | ButtonInteraction, member?: GuildMember): Promise<void> => {
if (!interaction.guild) return;
let deleteDays;
if (!interaction.isButton()) {
member = interaction.options.getMember("user") as GuildMember;
deleteDays = (interaction.options.get("delete")?.value as number | null) ?? 0;
} else {
deleteDays = 0;
}
if (!member) return;
const { renderUser } = client.logger;
// TODO:[Modals] Replace the command arguments with a modal
let reason = null;
@ -44,15 +52,12 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
.setTitle("Ban")
.setDescription(
keyValueList({
user: renderUser(interaction.options.getUser("user")!),
user: renderUser(member.user),
reason: reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*"
}) +
`The user **will${notify ? "" : " not"}** be notified\n` +
`${addPlurals(
(interaction.options.get("delete")?.value as number | null) ?? 0,
"day"
)} of messages will be deleted\n\n` +
`Are you sure you want to ban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`
`${addPlurals(deleteDays, "day")} of messages will be deleted\n\n` +
`Are you sure you want to ban <@!${member.id}>?`
)
.addCustomBoolean(
"notify",
@ -110,26 +115,19 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel(config.moderation.ban.text)
.setURL(
config.moderation.ban.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).id
)
)
.setURL(config.moderation.ban.link.replaceAll("{id}", member.id))
)
);
}
dmMessage = await (interaction.options.getMember("user") as GuildMember).send(messageData);
dmMessage = await member.send(messageData);
dmSent = true;
}
} catch {
dmSent = false;
}
try {
const member = interaction.options.getMember("user") as GuildMember;
const days: number = (interaction.options.get("delete")?.value as number | null) ?? 0;
member.ban({
deleteMessageSeconds: days * 24 * 60 * 60,
deleteMessageSeconds: deleteDays * 24 * 60 * 60,
reason: reason ?? "*No reason provided*"
});
await client.database.history.create("ban", interaction.guild.id, member.user, interaction.user, reason);
@ -189,23 +187,22 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
});
};
const check = async (interaction: CommandInteraction, partial: boolean = false) => {
const check = (interaction: CommandInteraction | ButtonInteraction, partial: boolean = false, target?: GuildMember) => {
if (!interaction.guild) return;
const member = interaction.member as GuildMember;
// Check if the user has ban_members permission
if (!member.permissions.has("BanMembers")) return "You do not have the *Ban Members* permission";
if (partial) return true;
const me = interaction.guild.members.me!;
let apply = interaction.options.getUser("user") as User | GuildMember;
let apply: GuildMember;
if (interaction.isButton()) {
apply = target!;
} else {
apply = interaction.options.getMember("user") as GuildMember;
}
const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0;
let applyPos = 0;
try {
apply = (await interaction.guild.members.fetch(apply.id)) as GuildMember;
applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0;
} catch {
apply = apply as User;
}
const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0;
// Do not allow banning the owner
if (member.id === interaction.guild.ownerId) return "You cannot ban the owner of the server";
// Check if Nucleus can ban the member

@ -5,7 +5,8 @@ import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
SlashCommandSubcommandBuilder
SlashCommandSubcommandBuilder,
ButtonInteraction
} from "discord.js";
// @ts-expect-error
import humanizeDuration from "humanize-duration";
@ -22,8 +23,12 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
.setDescription("Kicks a user from the server")
.addUserOption((option) => option.setName("user").setDescription("The user to kick").setRequired(true));
const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const callback = async (interaction: CommandInteraction | ButtonInteraction, member?: GuildMember): Promise<void> => {
if (!interaction.guild) return;
if (!interaction.isButton()) {
member = interaction.options.getMember("user") as GuildMember;
}
if (!member) return;
const { renderUser } = client.logger;
// TODO:[Modals] Replace this with a modal
let reason: string | null = null;
@ -37,9 +42,9 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setTitle("Kick")
.setDescription(
keyValueList({
user: renderUser(interaction.options.getUser("user")!),
user: renderUser(member.user),
reason: reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*"
}) + `Are you sure you want to kick <@!${(interaction.options.getMember("user") as GuildMember).id}>?`
}) + `Are you sure you want to kick <@!${member.id}>?`
)
.setColor("Danger")
.addCustomBoolean(
@ -98,24 +103,18 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel(config.moderation.kick.text)
.setURL(
config.moderation.kick.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).id
)
)
.setURL(config.moderation.kick.link.replaceAll("{id}", member.id))
)
);
}
dmMessage = await (interaction.options.getMember("user") as GuildMember).send(messageData);
dmMessage = await member.send(messageData);
dmSent = true;
}
} catch {
dmSent = false;
}
try {
(interaction.options.getMember("user") as GuildMember).kick(reason || "No reason provided");
const member = interaction.options.getMember("user") as GuildMember;
member.kick(reason || "No reason provided");
await client.database.history.create("kick", interaction.guild.id, member.user, interaction.user, reason);
const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
const timeInServer = member.joinedTimestamp
@ -186,7 +185,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
});
};
const check = (interaction: CommandInteraction, partial: boolean = false) => {
const check = (interaction: CommandInteraction | ButtonInteraction, partial: boolean = false, target?: GuildMember) => {
if (!interaction.guild) return;
const member = interaction.member as GuildMember;
@ -195,7 +194,12 @@ const check = (interaction: CommandInteraction, partial: boolean = false) => {
if (partial) return true;
const me = interaction.guild.members.me!;
const apply = interaction.options.getMember("user") as GuildMember;
let apply: GuildMember;
if (interaction.isButton()) {
apply = target!;
} else {
apply = interaction.options.getMember("user") as GuildMember;
}
const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0;
const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0;

@ -1,5 +1,12 @@
import { LinkWarningFooter, LoadingEmbed } from "../../utils/defaults.js";
import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
import Discord, {
CommandInteraction,
GuildMember,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ButtonInteraction
} from "discord.js";
import type { SlashCommandSubcommandBuilder } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
@ -49,16 +56,25 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
.setRequired(false)
);
const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const callback = async (
interaction: CommandInteraction | ButtonInteraction,
member?: GuildMember
): Promise<unknown> => {
if (!interaction.guild) return;
const { log, NucleusColors, renderUser, entry, renderDelta } = client.logger;
const member = interaction.options.getMember("user") as GuildMember;
const time: { days: number; hours: number; minutes: number; seconds: number } = {
days: (interaction.options.get("days")?.value as number | null) ?? 0,
hours: (interaction.options.get("hours")?.value as number | null) ?? 0,
minutes: (interaction.options.get("minutes")?.value as number | null) ?? 0,
seconds: (interaction.options.get("seconds")?.value as number | null) ?? 0
};
let time: { days: number; hours: number; minutes: number; seconds: number } | null = null;
if (!interaction.isButton()) {
member = interaction.options.getMember("user") as GuildMember;
time = {
days: (interaction.options.get("days")?.value as number | null) ?? 0,
hours: (interaction.options.get("hours")?.value as number | null) ?? 0,
minutes: (interaction.options.get("minutes")?.value as number | null) ?? 0,
seconds: (interaction.options.get("seconds")?.value as number | null) ?? 0
};
} else {
time = { days: 0, hours: 0, minutes: 0, seconds: 0 };
}
if (!member) return;
const config = await client.database.guilds.read(interaction.guild.id);
let serverSettingsDescription = config.moderation.mute.timeout ? "given a timeout" : "";
if (config.moderation.mute.role)
@ -197,8 +213,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
"appeal",
"Create appeal ticket",
!(await areTicketsEnabled(interaction.guild.id)),
async () =>
await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
async () => await create(interaction.guild!, member!.user, interaction.user, reason),
"An appeal ticket will be created when Confirm is clicked",
null,
"CONTROL.TICKET",
@ -275,12 +290,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel(config.moderation.mute.text)
.setURL(
config.moderation.mute.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).id
)
)
.setURL(config.moderation.mute.link.replaceAll("{id}", member.id))
)
);
}
@ -399,14 +409,19 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
});
};
const check = async (interaction: CommandInteraction, partial: boolean = false) => {
const check = (interaction: CommandInteraction | ButtonInteraction, partial: boolean = false, target?: GuildMember) => {
if (!interaction.guild) return;
const member = interaction.member as GuildMember;
// Check if the user has moderate_members permission
if (!member.permissions.has("ModerateMembers")) return "You do not have the *Moderate Members* permission";
if (partial) return true;
const me = interaction.guild.members.me!;
const apply = interaction.options.getMember("user") as GuildMember;
let apply;
if (interaction.isButton()) {
apply = target!;
} else {
apply = interaction.options.getMember("user") as GuildMember;
}
const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0;
const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0;
const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0;

@ -1,5 +1,16 @@
import { LinkWarningFooter } from "./../../utils/defaults.js";
import { ActionRowBuilder, ButtonBuilder, CommandInteraction, GuildMember, ButtonStyle, Message } from "discord.js";
import {
ActionRowBuilder,
ButtonBuilder,
CommandInteraction,
GuildMember,
ButtonStyle,
Message,
ButtonInteraction,
ModalBuilder,
TextInputBuilder,
TextInputStyle
} from "discord.js";
import type { SlashCommandSubcommandBuilder } from "discord.js";
import confirmationMessage from "../../utils/confirmationMessage.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@ -17,44 +28,40 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
option.setName("name").setDescription("The name to set | Leave blank to clear").setRequired(false)
);
const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const callback = async (
interaction: CommandInteraction | ButtonInteraction,
member?: GuildMember
): Promise<unknown> => {
const { log, NucleusColors, entry, renderDelta, renderUser } = client.logger;
let newNickname;
if (!interaction.isButton()) {
member = interaction.options.getMember("user") as GuildMember;
newNickname = interaction.options.get("name")?.value as string | undefined;
}
if (!member) return;
// TODO:[Modals] Replace this with a modal
let notify = true;
let notify = false;
let confirmation;
let timedOut = false;
let success = false;
let createAppealTicket = false;
let firstRun = true;
let firstRun = !interaction.isButton();
do {
confirmation = await new confirmationMessage(interaction)
.setEmoji("PUNISH.NICKNAME.RED")
.setTitle("Nickname")
.setDescription(
keyValueList({
user: renderUser(interaction.options.getUser("user")!),
"new nickname": `${
(interaction.options.get("name")?.value as string)
? (interaction.options.get("name")?.value as string)
: "*No nickname*"
}`
}) +
`Are you sure you want to ${
(interaction.options.get("name")?.value as string) ? "change" : "clear"
} <@!${(interaction.options.getMember("user") as GuildMember).id}>'s nickname?`
user: renderUser(member.user),
"new nickname": `${newNickname ? newNickname : "*No nickname*"}`
}) + `Are you sure you want to ${newNickname ? "change" : "clear"} <@!${member.id}>'s nickname?`
)
.setColor("Danger")
.addCustomBoolean(
"appeal",
"Create appeal ticket",
!(await areTicketsEnabled(interaction.guild!.id)),
async () =>
await create(
interaction.guild!,
interaction.options.getUser("user")!,
interaction.user,
"Nickname changed"
),
async () => await create(interaction.guild!, member!.user, interaction.user, "Nickname changed"),
"An appeal ticket will be created",
null,
"CONTROL.TICKET",
@ -70,6 +77,23 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify
)
.addModal(
"Change nickname",
"ICONS.EDIT",
"modal",
newNickname ?? "",
new ModalBuilder().setTitle("Editing nickname").addComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(
new TextInputBuilder()
.setCustomId("default")
.setLabel("Nickname")
.setMaxLength(32)
.setRequired(false)
.setStyle(TextInputStyle.Short)
.setValue(newNickname ? newNickname : " ")
)
)
)
.setFailedMessage("No changes were made", "Success", "PUNISH.NICKNAME.GREEN")
.send(!firstRun);
firstRun = false;
@ -79,6 +103,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
notify = confirmation.components["notify"]!.active;
createAppealTicket = confirmation.components["appeal"]!.active;
}
if (confirmation.modals) newNickname = confirmation.modals![0]!.value;
} while (!timedOut && !success);
if (timedOut || !success) return;
let dmSent = false;
@ -95,12 +120,8 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setEmoji("PUNISH.NICKNAME.RED")
.setTitle("Nickname changed")
.setDescription(
`Your nickname was ${
(interaction.options.get("name")?.value as string) ? "changed" : "cleared"
} in ${interaction.guild!.name}.` +
((interaction.options.get("name")?.value as string)
? `\nIt is now: ${interaction.options.get("name")?.value as string}`
: "") +
`Your nickname was ${newNickname ? "changed" : "cleared"} in ${interaction.guild!.name}.` +
(newNickname ? `\nIt is now: ${newNickname}` : "") +
"\n\n" +
(createAppealTicket
? `You can appeal this in the ticket created in <#${
@ -119,29 +140,20 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel(config.moderation.nick.text)
.setURL(
config.moderation.nick.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).id
)
)
.setURL(config.moderation.nick.link.replaceAll("{id}", member.id))
)
);
}
dmMessage = await (interaction.options.getMember("user") as GuildMember).send(messageData);
dmMessage = await member.send(messageData);
dmSent = true;
}
} catch {
dmSent = false;
}
let member: GuildMember;
let before: string | null;
let nickname: string | undefined;
try {
member = interaction.options.getMember("user") as GuildMember;
before = member.nickname;
nickname = interaction.options.get("name")?.value as string | undefined;
member.setNickname(nickname ?? null, "Nucleus Nickname command");
member.setNickname(newNickname ?? null, "Nucleus Nickname command");
await client.database.history.create(
"nickname",
interaction.guild!.id,
@ -149,7 +161,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
interaction.user,
null,
before,
nickname
newNickname
);
} catch {
await interaction.editReply({
@ -175,9 +187,9 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
timestamp: Date.now()
},
list: {
memberId: entry(member.id, `\`${member.id}\``),
member: entry(member.id, renderUser(member.user)),
before: entry(before, before ?? "*No nickname set*"),
after: entry(nickname ?? null, nickname ?? "*No nickname set*"),
after: entry(newNickname ?? null, newNickname ?? "*No nickname set*"),
updated: entry(Date.now(), renderDelta(Date.now())),
updatedBy: entry(interaction.user.id, renderUser(interaction.user))
},
@ -210,13 +222,18 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
});
};
const check = async (interaction: CommandInteraction, partial: boolean = false) => {
const check = async (interaction: CommandInteraction | ButtonInteraction, partial: boolean, target?: GuildMember) => {
const member = interaction.member as GuildMember;
// Check if the user has manage_nicknames permission
if (!member.permissions.has("ManageNicknames")) return "You do not have the *Manage Nicknames* permission";
if (partial) return true;
const me = interaction.guild!.members.me!;
const apply = interaction.options.getMember("user") as GuildMember;
let apply: GuildMember;
if (interaction.isButton()) {
apply = target!;
} else {
apply = interaction.options.getMember("user") as GuildMember;
}
const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
const mePos = me.roles.cache.size ? me.roles.highest.position : 0;
const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;

@ -101,8 +101,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
let component;
try {
component = m.awaitMessageComponent({
filter: (i) =>
i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id,
filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id,
time: 300000
});
} catch (e) {

@ -1,4 +1,11 @@
import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
import Discord, {
CommandInteraction,
GuildMember,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ButtonInteraction
} from "discord.js";
import type { SlashCommandSubcommandBuilder } from "discord.js";
import confirmationMessage from "../../utils/confirmationMessage.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@ -14,9 +21,14 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
.setDescription("Warns a user")
.addUserOption((option) => option.setName("user").setDescription("The user to warn").setRequired(true));
const callback = async (interaction: CommandInteraction): Promise<unknown> => {
if (interaction.guild === null) return;
const callback = async (
interaction: CommandInteraction | ButtonInteraction,
member?: GuildMember
): Promise<unknown> => {
if (!interaction.guild) return;
const { log, NucleusColors, renderUser, entry } = client.logger;
if (!interaction.isButton()) member = interaction.options.getMember("user") as GuildMember;
if (!member) return;
// TODO:[Modals] Replace this with a modal
let reason: string | null = null;
let notify = true;
@ -30,17 +42,16 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setTitle("Warn")
.setDescription(
keyValueList({
user: renderUser(interaction.options.getUser("user")!),
user: renderUser(member.user),
reason: reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*"
}) + `Are you sure you want to warn <@!${(interaction.options.getMember("user") as GuildMember).id}>?`
}) + `Are you sure you want to warn <@!${member.id}>?`
)
.setColor("Danger")
.addCustomBoolean(
"appeal",
"Create appeal ticket",
!(await areTicketsEnabled(interaction.guild.id)),
async () =>
await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
async () => await create(interaction.guild!, member!.user, interaction.user, reason),
"An appeal ticket will be created",
null,
"CONTROL.TICKET",
@ -108,16 +119,11 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel(config.moderation.warn.text)
.setURL(
config.moderation.warn.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).id
)
)
.setURL(config.moderation.warn.link.replaceAll("{id}", member.id))
)
);
}
await (interaction.options.getMember("user") as GuildMember).send(messageData);
await member.send(messageData);
dmSent = true;
}
} catch (e) {
@ -133,10 +139,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
timestamp: Date.now()
},
list: {
user: entry(
(interaction.options.getMember("user") as GuildMember).user.id,
renderUser((interaction.options.getMember("user") as GuildMember).user)
),
user: entry(member.user.id, renderUser(member.user)),
warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
reason: reason ? reason : "*No reason provided*"
},
@ -149,13 +152,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
guild: interaction.guild.id
}
};
await client.database.history.create(
"warn",
interaction.guild.id,
(interaction.options.getMember("user") as GuildMember).user,
interaction.user,
reason
);
await client.database.history.create("warn", interaction.guild.id, member.user, interaction.user, reason);
log(data);
const failed = !dmSent && notify;
if (!failed) {
@ -177,9 +174,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
components: []
});
} else {
const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
.permissionsIn(interaction.channel as Discord.TextChannel)
.has("ViewChannel");
const canSeeChannel = member.permissionsIn(interaction.channel as Discord.TextChannel).has("ViewChannel");
const m = (await interaction.editReply({
embeds: [
new EmojiEmbed()
@ -235,9 +230,9 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
.setStatus("Danger")
],
content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
content: `<@!${member.id}>`,
allowedMentions: {
users: [(interaction.options.getMember("user") as GuildMember).id]
users: [member.id]
}
});
return await interaction.editReply({
@ -271,7 +266,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
} else if (component.customId === "ticket") {
const ticketChannel = await create(
interaction.guild,
interaction.options.getUser("user")!,
member.user,
interaction.user,
reason,
"Warn Notification"
@ -302,13 +297,17 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
}
};
const check = (interaction: CommandInteraction, partial: boolean = false) => {
const check = (interaction: CommandInteraction | ButtonInteraction, partial: boolean = false, target?: GuildMember) => {
if (!interaction.guild) return;
const member = interaction.member as GuildMember;
if (!member.permissions.has("ModerateMembers")) return "You do not have the *Moderate Members* permission";
if (partial) return true;
const apply = interaction.options.getMember("user") as GuildMember | null;
if (apply === null) return "That member is not in the server";
let apply: GuildMember;
if (interaction.isButton()) {
apply = target!;
} else {
apply = interaction.options.getMember("user") as GuildMember;
}
const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
// Do not allow warning bots

@ -1,4 +1,18 @@
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, ChannelType, CommandInteraction, ComponentType, Guild, ModalBuilder, ModalSubmitInteraction, TextInputBuilder, TextInputStyle } from "discord.js";
import {
ActionRowBuilder,
AttachmentBuilder,
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
ChannelType,
CommandInteraction,
ComponentType,
Guild,
ModalBuilder,
ModalSubmitInteraction,
TextInputBuilder,
TextInputStyle
} from "discord.js";
import type { SlashCommandSubcommandBuilder } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import client from "../../utils/client.js";
@ -8,7 +22,7 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
builder.setName("stats").setDescription("Gets the bot's stats");
const callback = async (interaction: CommandInteraction): Promise<void> => {
const description = `**Servers:** ${client.guilds.cache.size}\n` + `**Ping:** \`${client.ws.ping * 2}ms\``
const description = `**Servers:** ${client.guilds.cache.size}\n` + `**Ping:** \`${client.ws.ping * 2}ms\``;
const m = await interaction.reply({
embeds: [
new EmojiEmbed()
@ -28,27 +42,39 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
.setDescription(description)
.setStatus("Success")
.setEmoji("SETTINGS.STATS.GREEN")
], components: [new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder().setCustomId("admin").setLabel("Admin Panel").setStyle(ButtonStyle.Primary))]
],
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().setCustomId("admin").setLabel("Admin Panel").setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId("mod:nickname:599498449733550102")
.setLabel("Testing")
.setStyle(ButtonStyle.Primary)
)
]
});
const modal = new ModalBuilder()
.addComponents(
new ActionRowBuilder<TextInputBuilder>()
.addComponents(
new TextInputBuilder()
.setStyle(TextInputStyle.Short)
.setLabel("Guild ID")
.setCustomId("guildID")
.setPlaceholder("Guild ID")
.setMinLength(16)
.setMaxLength(25)
)
new ActionRowBuilder<TextInputBuilder>().addComponents(
new TextInputBuilder()
.setStyle(TextInputStyle.Short)
.setLabel("Guild ID")
.setCustomId("guildID")
.setPlaceholder("Guild ID")
.setMinLength(16)
.setMaxLength(25)
)
)
.setTitle("Admin Panel")
.setCustomId("adminPanel")
.setCustomId("adminPanel");
let i1: ButtonInteraction;
const channel = await client.channels.fetch(interaction.channelId)
if(!channel || [ChannelType.GuildCategory, ChannelType.GroupDM, ChannelType.GuildStageVoice].includes(channel.type)) return;
const channel = await client.channels.fetch(interaction.channelId);
if (
!channel ||
[ChannelType.GuildCategory, ChannelType.GroupDM, ChannelType.GuildStageVoice].includes(channel.type)
)
return;
// console.log(interaction)
if (!("awaitMessageComponent" in channel)) return;
try {
@ -56,27 +82,28 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
filter: (i) => i.customId === "admin" && i.user.id === interaction.user.id,
time: 300000
});
} catch (e) { console.log(e); return }
await i1.showModal(modal)
} catch (e) {
console.log(e);
return;
}
await i1.showModal(modal);
let out: ModalSubmitInteraction;
try {
out = await i1.awaitModalSubmit({
filter: (i) => i.customId === "adminPanel" && i.user.id === interaction.user.id,
time: 300000
})
} catch { return }
});
} catch {
return;
}
out.deferUpdate();
const GuildID = out.fields.getTextInputValue("guildID");
if (!client.guilds.cache.has(GuildID)) {
await interaction.editReply({
embeds: [
new EmojiEmbed()
.setTitle("Admin")
.setDescription("Not in server")
.setStatus("Danger")
], components: []
embeds: [new EmojiEmbed().setTitle("Admin").setDescription("Not in server").setStatus("Danger")],
components: []
});
};
}
await interaction.editReply({
embeds: [],
@ -88,24 +115,23 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
new ButtonBuilder().setCustomId("purge").setLabel("Delete data").setStyle(ButtonStyle.Danger),
new ButtonBuilder().setCustomId("cache").setLabel("Reset cache").setStyle(ButtonStyle.Success)
)
]});
]
});
let i;
try {
i = await m.awaitMessageComponent<ComponentType.Button>({
filter: (i) => i.user.id === interaction.user.id,
time: 300000
})
} catch { return }
});
} catch {
return;
}
i.deferUpdate();
const guild = await client.guilds.fetch(GuildID) as Guild | null;
const guild = (await client.guilds.fetch(GuildID)) as Guild | null;
if (!guild) {
await interaction.editReply({
embeds: [
new EmojiEmbed()
.setTitle("Admin")
.setDescription("Not in server")
.setStatus("Danger")
], components: []
embeds: [new EmojiEmbed().setTitle("Admin").setDescription("Not in server").setStatus("Danger")],
components: []
});
return;
}
@ -116,17 +142,17 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
.setTitle("Stats")
.setDescription(
`**Name:** ${guild.name}\n` +
`**ID:** \`${guild.id}\`\n` +
`**Owner:** ${client.users.cache.get(guild.ownerId)!.tag}\n` +
`**Member Count:** ${guild.memberCount}\n` +
`**Created:** <t:${guild.createdTimestamp}:F>\n` +
`**Added Nucleus:** <t:${guild.members.me!.joinedTimestamp}:R>\n` +
`**Nucleus' Perms:** https://discordapi.com/permissions.html#${guild.members.me!.permissions.valueOf()}\n`
`**ID:** \`${guild.id}\`\n` +
`**Owner:** ${client.users.cache.get(guild.ownerId)!.tag}\n` +
`**Member Count:** ${guild.memberCount}\n` +
`**Created:** <t:${guild.createdTimestamp}:F>\n` +
`**Added Nucleus:** <t:${guild.members.me!.joinedTimestamp}:R>\n` +
`**Nucleus' Perms:** https://discordapi.com/permissions.html#${guild.members.me!.permissions.valueOf()}\n`
)
.setStatus("Success")
.setEmoji("SETTINGS.STATS.GREEN")
]
})
});
} else if (i.customId === "leave") {
await guild.leave();
await interaction.editReply({
@ -136,8 +162,9 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
.setDescription(`Left ${guild.name}`)
.setStatus("Success")
.setEmoji("SETTINGS.STATS.GREEN")
], components: []
})
],
components: []
});
} else if (i.customId === "data") {
// Get all the data and convert to a string
const data = await client.database.guilds.read(guild.id);
@ -147,9 +174,10 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
await interaction.editReply({
embeds: [
new EmojiEmbed().setTitle("Data").setDescription(`Data for ${guild.name}`).setStatus("Success")
], components: [],
],
components: [],
files: [attachment]
})
});
} else if (i.customId === "purge") {
await client.database.guilds.delete(GuildID);
await client.database.history.delete(GuildID);
@ -162,8 +190,9 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
.setDescription(`Deleted data for ${guild.name}`)
.setStatus("Success")
.setEmoji("SETTINGS.STATS.GREEN")
], components: []
})
],
components: []
});
} else if (i.customId === "cache") {
await client.memory.forceUpdate(guild.id);
await interaction.editReply({
@ -173,8 +202,9 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
.setDescription(`Reset cache for ${guild.name}`)
.setStatus("Success")
.setEmoji("SETTINGS.STATS.GREEN")
], components: []
})
],
components: []
});
}
}
};

@ -1069,17 +1069,19 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
closed = true;
continue;
}
await i.deferUpdate();
if (i.isButton()) {
await i.deferUpdate();
await client.database.guilds.write(interaction.guild.id, { filters: config });
await client.memory.forceUpdate(interaction.guild.id);
} else {
switch (i.values[0]) {
case "invites": {
i.deferUpdate();
config.invite = await inviteMenu(i, m, config.invite);
break;
}
case "mentions": {
i.deferUpdate();
config.pings = await mentionMenu(i, m, config.pings);
break;
}
@ -1088,15 +1090,18 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
break;
}
case "malware": {
i.deferUpdate();
config.malware = !config.malware;
break;
}
case "images": {
i.deferUpdate();
const next = await imageMenu(i, m, config.images);
config.images = next;
break;
}
case "clean": {
i.deferUpdate();
const next = await cleanMenu(i, m, config.clean);
config.clean = next;
break;

@ -176,7 +176,7 @@ const editRoleMenuPage = async (
m: Message,
data?: ObjectSchema
): Promise<ObjectSchema | null> => {
if (!data) data = _.cloneDeep(defaultRoleMenuData)
if (!data) data = _.cloneDeep(defaultRoleMenuData);
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("back")

@ -49,6 +49,14 @@
"RULES": "990213153080115250",
"FORUM": "1061706437526552716",
"CATEGORY": "1064943289708597348"
},
"FLAGS": {
"RED": "1082719687219101800",
"YELLOW": "1082719684060794890",
"GREEN": "1082719681326108763",
"BLUE": "1082719679161843734",
"PURPLE": "1082719686292156628",
"GRAY": "1082719682492125337"
}
},
"CONTROL": {

@ -2,6 +2,7 @@ import { AuditLogEvent, GuildAuditLogsEntry, GuildMember } from "discord.js";
import type { NucleusClient } from "../utils/client.js";
import type { LoggerOptions } from "../utils/log.js";
import { generalException } from "../utils/createTemporaryStorage.js";
import { doMemberChecks } from "../reflex/scanners.js";
export const event = "guildMemberUpdate";
@ -92,6 +93,7 @@ export async function callback(client: NucleusClient, before: GuildMember, after
if (!auditLog) return;
if (auditLog.executor!.id === client.user!.id) return;
if (before.nickname !== after.nickname) {
doMemberChecks(after, after.guild);
await client.database.history.create(
"nickname",
after.guild.id,
@ -111,7 +113,6 @@ export async function callback(client: NucleusClient, before: GuildMember, after
timestamp: Date.now()
},
list: {
memberId: entry(after.id, `\`${after.id}\``),
name: entry(after.user.id, renderUser(after.user)),
before: entry(before.nickname, before.nickname ? before.nickname : "*None*"),
after: entry(after.nickname, after.nickname ? after.nickname : "*None*"),

@ -5,7 +5,7 @@ import { callback as statsChannelUpdate } from "../reflex/statsChannelUpdate.js"
export const event = "guildUpdate";
export async function callback(client: NucleusClient, before: Guild, after: Guild) {
await statsChannelUpdate(client, after.members.me!);
await statsChannelUpdate(after.members.me!.user, after);
const { getAuditLog, isLogging, log, NucleusColors, entry, renderUser, renderDelta } = client.logger;
if (!(await isLogging(after.id, "guildUpdate"))) return;
const auditLog = (await getAuditLog(after, AuditLogEvent.GuildUpdate)).filter(

@ -4,13 +4,27 @@ import create from "../actions/tickets/create.js";
import close from "../actions/tickets/delete.js";
import createTranscript from "../premium/createTranscript.js";
import type { Interaction } from "discord.js";
import type { ButtonInteraction, Interaction } from "discord.js";
import type Discord from "discord.js";
import type { NucleusClient } from "../utils/client.js";
import EmojiEmbed from "../utils/generateEmojiEmbed.js";
import { callback as banCallback, check as banCheck } from "../commands/mod/ban.js";
import { callback as kickCallback, check as kickCheck } from "../commands/mod/kick.js";
import { callback as muteCallback, check as muteCheck } from "../commands/mod/mute.js";
import { callback as nicknameCallback, check as nicknameCheck } from "../commands/mod/nick.js";
import { callback as warnCallback, check as warnCheck } from "../commands/mod/warn.js";
export const event = "interactionCreate";
async function errorMessage(interaction: ButtonInteraction, message: string) {
await interaction.reply({
embeds: [new EmojiEmbed().setDescription(message).setStatus("Danger")],
ephemeral: true,
components: []
});
}
async function interactionCreate(interaction: Interaction) {
if (interaction.isButton()) {
switch (interaction.customId) {
@ -36,6 +50,39 @@ async function interactionCreate(interaction: Interaction) {
return await modifySuggestion(interaction, false);
}
}
// Mod actions
if (interaction.customId.startsWith("mod:")) {
const action = interaction.customId.split(":")[1];
const memberId = interaction.customId.split(":")[2];
const member = await interaction.guild?.members.fetch(memberId!);
switch (action) {
case "kick": {
const check = await kickCheck(interaction, false, member);
if (check !== true) return await errorMessage(interaction, check!);
return await kickCallback(interaction, member);
}
case "ban": {
const check = await banCheck(interaction, false, member);
if (check !== true) return await errorMessage(interaction, check!);
return await banCallback(interaction, member);
}
case "mute": {
const check = await muteCheck(interaction, false, member);
if (check !== true) return await errorMessage(interaction, check!);
return await muteCallback(interaction, member);
}
case "nickname": {
const check = await nicknameCheck(interaction, false, member);
if (check !== true) return await errorMessage(interaction, check || "Something went wrong");
return await nicknameCallback(interaction, member);
}
case "warn": {
const check = await warnCheck(interaction, false, member);
if (check !== true) return await errorMessage(interaction, check!);
return await warnCallback(interaction, member);
}
}
}
}
}

@ -2,12 +2,14 @@ import type { GuildMember } from "discord.js";
import { callback as statsChannelAdd } from "../reflex/statsChannelUpdate.js";
import { callback as welcome } from "../reflex/welcome.js";
import type { NucleusClient } from "../utils/client.js";
import { doMemberChecks } from "../reflex/scanners.js";
export const event = "guildMemberAdd";
export async function callback(client: NucleusClient, member: GuildMember) {
welcome(client, member);
statsChannelAdd(client, member);
welcome(member);
statsChannelAdd(member.user, member.guild);
doMemberChecks(member, member.guild);
const { log, isLogging, NucleusColors, entry, renderUser, renderDelta } = client.logger;
if (!(await isLogging(member.guild.id, "guildMemberUpdate"))) return;
await client.database.history.create("join", member.guild.id, member.user, null, null);

@ -30,7 +30,7 @@ export async function callback(_client: NucleusClient, message: Message) {
if (message.author.bot) return;
if (message.channel.isDMBased()) return;
try {
await statsChannelUpdate(client, await message.guild.members.fetch(message.author.id));
await statsChannelUpdate((await message.guild.members.fetch(message.author.id)).user, message.guild);
} catch (e) {
console.log(e);
}

@ -5,6 +5,12 @@ import Tesseract from "node-tesseract-ocr";
import type Discord from "discord.js";
import client from "../utils/client.js";
import { createHash } from "crypto";
// import * as nsfwjs from "nsfwjs";
// import * as clamscan from "clamscan";
// import * as tf from "@tensorflow/tfjs-node";
import EmojiEmbed from "../utils/generateEmojiEmbed.js";
import getEmojiByName from "../utils/getEmojiByName.js";
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
interface NSFWSchema {
nsfw: boolean;
@ -15,32 +21,18 @@ interface MalwareSchema {
errored?: boolean;
}
// const model = await nsfwjs.load();
export async function testNSFW(link: string): Promise<NSFWSchema> {
const [p, hash] = await saveAttachment(link);
const [_fileName, hash] = await saveAttachment(link);
const alreadyHaveCheck = await client.database.scanCache.read(hash);
if (alreadyHaveCheck) return { nsfw: alreadyHaveCheck.data };
const data = new URLSearchParams();
const r = createReadStream(p);
data.append("file", r.read(fs.statSync(p).size));
const result = await fetch("https://unscan.p.rapidapi.com/", {
method: "POST",
headers: {
"X-RapidAPI-Key": client.config.rapidApiKey,
"X-RapidAPI-Host": "unscan.p.rapidapi.com"
},
body: data
})
.then((response) =>
response.status === 200 ? (response.json() as Promise<NSFWSchema>) : { nsfw: false, errored: true }
)
.catch((err) => {
console.error(err);
return { nsfw: false, errored: true };
});
if (!result.errored) {
client.database.scanCache.write(hash, result.nsfw);
}
return { nsfw: result.nsfw };
// const image = tf.node.decodePng()
// const result = await model.classify(image)
return { nsfw: false };
}
export async function testMalware(link: string): Promise<MalwareSchema> {
@ -175,7 +167,13 @@ export async function MalwareCheck(element: string): Promise<boolean> {
}
}
export function TestString(string: string, soft: string[], strict: string[]): object | null {
export function TestString(
string: string,
soft: string[],
strict: string[],
enabled?: boolean
): { word: string; type: string } | null {
if (!enabled) return null;
for (const word of strict) {
if (string.toLowerCase().includes(word)) {
return { word: word, type: "strict" };
@ -184,7 +182,7 @@ export function TestString(string: string, soft: string[], strict: string[]): ob
for (const word of soft) {
for (const word2 of string.match(/[a-z]+/gi) ?? []) {
if (word2 === word) {
return { word: word, type: "strict" };
return { word: word, type: "soft" };
}
}
}
@ -199,3 +197,107 @@ export async function TestImage(url: string): Promise<string | null> {
});
return text;
}
export async function doMemberChecks(member: Discord.GuildMember, guild: Discord.Guild): Promise<void> {
if (member.user.bot) return;
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];
// Does the username contain filtered words
const usernameCheck = TestString(member.user.username, loose, strict, guildData.filters.wordFilter.enabled);
// Does the nickname contain filtered words
const nicknameCheck = TestString(member.nickname ?? "", loose, strict, guildData.filters.wordFilter.enabled);
// Does the profile picture contain filtered words
const avatarTextCheck = TestString(
(await TestImage(member.user.displayAvatarURL({ forceStatic: true }))) ?? "",
loose,
strict,
guildData.filters.wordFilter.enabled
);
// Is the profile picture NSFW
const avatarCheck =
guildData.filters.images.NSFW && (await NSFWCheck(member.user.displayAvatarURL({ forceStatic: true })));
// Does the username contain an invite
const inviteCheck =
guildData.filters.invite.enabled && member.user.username.match(/discord\.gg\/[a-zA-Z0-9]+/gi) !== null;
// Does the nickname contain an invite
const nicknameInviteCheck =
guildData.filters.invite.enabled && member.nickname?.match(/discord\.gg\/[a-zA-Z0-9]+/gi) !== null;
if (
usernameCheck !== null ||
nicknameCheck !== null ||
avatarCheck ||
inviteCheck ||
nicknameInviteCheck ||
avatarTextCheck !== null
) {
const infractions = [];
if (usernameCheck !== null) {
infractions.push(`Username contains a ${usernameCheck.type}ly filtered word (${usernameCheck.word})`);
}
if (nicknameCheck !== null) {
infractions.push(`Nickname contains a ${nicknameCheck.type}ly filtered word (${nicknameCheck.word})`);
}
if (avatarCheck) {
infractions.push("Profile picture is NSFW");
}
if (inviteCheck) {
infractions.push("Username contains an invite");
}
if (nicknameInviteCheck) {
infractions.push("Nickname contains an invite");
}
if (avatarTextCheck !== null) {
infractions.push(
`Profile picture contains a ${avatarTextCheck.type}ly filtered word: ${avatarTextCheck.word}`
);
}
if (infractions.length === 0) return;
// This is bad - Warn in the staff notifications channel
const filter = getEmojiByName("ICONS.FILTER");
const channel = guild.channels.cache.get(guildData.logging.staff.channel) as Discord.TextChannel;
const embed = new EmojiEmbed()
.setTitle("Member Flagged")
.setEmoji("ICONS.FLAGS.RED")
.setStatus("Danger")
.setDescription(
`**Member:** ${member.user.username} (<@${member.user.id}>)\n\n` +
infractions.map((element) => `${filter} ${element}`).join("\n")
);
await channel.send({
embeds: [embed],
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
...[
new ButtonBuilder()
.setCustomId(`mod:warn:${member.user.id}`)
.setLabel("Warn")
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId(`mod:mute:${member.user.id}`)
.setLabel("Mute")
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId(`mod:kick:${member.user.id}`)
.setLabel("Kick")
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setCustomId(`mod:ban:${member.user.id}`)
.setLabel("Ban")
.setStyle(ButtonStyle.Danger)
].concat(
usernameCheck !== null || nicknameCheck !== null || avatarTextCheck !== null
? [
new ButtonBuilder()
.setCustomId(`mod:nickname:${member.user.id}`)
.setLabel("Change Name")
.setStyle(ButtonStyle.Primary)
]
: []
)
)
]
});
}
}

@ -1,7 +1,6 @@
import { getCommandMentionByName } from "../utils/getCommandDataByName.js";
import type { Guild, User } from "discord.js";
import type { NucleusClient } from "../utils/client.js";
import type { GuildMember } from "discord.js";
import client from "../utils/client.js";
import convertCurlyBracketString from "../utils/convertCurlyBracketString.js";
import singleNotify from "../utils/singleNotify.js";
@ -10,10 +9,8 @@ interface PropSchema {
name: string;
}
export async function callback(client: NucleusClient, member?: GuildMember, guild?: Guild, user?: User) {
if (!member && !guild) return;
guild = await client.guilds.fetch(member ? member.guild.id : guild!.id);
user = user ?? member!.user;
export async function callback(user: User, guild: Guild) {
guild = await client.guilds.fetch(guild.id);
const config = await client.database.guilds.read(guild.id);
Object.entries(config.stats).forEach(async ([channel, props]) => {
if ((props as PropSchema).enabled) {
@ -22,16 +19,16 @@ export async function callback(client: NucleusClient, member?: GuildMember, guil
string = await convertCurlyBracketString(string, user!.id, user!.username, guild!.name, guild!.members);
let fetchedChannel;
try {
fetchedChannel = await guild!.channels.fetch(channel);
fetchedChannel = await guild.channels.fetch(channel);
} catch (e) {
fetchedChannel = null;
}
if (!fetchedChannel) {
const deleted = config.stats[channel];
await client.database.guilds.write(guild!.id, null, `stats.${channel}`);
await client.database.guilds.write(guild.id, null, `stats.${channel}`);
return singleNotify(
"statsChannelDeleted",
guild!.id,
guild.id,
`One or more of your stats channels have been deleted. You can use ${getCommandMentionByName(
"settings/stats"
)}.\n` + `The channels name was: ${deleted!.name}`,

@ -1,12 +1,11 @@
import { getCommandMentionByName } from "./../utils/getCommandDataByName.js";
import type { NucleusClient } from "../utils/client.js";
import convertCurlyBracketString from "../utils/convertCurlyBracketString.js";
import client from "../utils/client.js";
import EmojiEmbed from "../utils/generateEmojiEmbed.js";
import { GuildChannel, GuildMember, BaseGuildTextChannel } from "discord.js";
import singleNotify from "../utils/singleNotify.js";
export async function callback(_client: NucleusClient, member: GuildMember) {
export async function callback(member: GuildMember) {
if (member.user.bot) return;
const config = await client.database.guilds.read(member.guild.id);
if (!config.welcome.enabled) return;

@ -1,4 +1,4 @@
import { TextInputBuilder } from "discord.js";
import { ButtonInteraction, TextInputBuilder } from "discord.js";
import Discord, {
CommandInteraction,
Message,
@ -24,7 +24,7 @@ interface CustomBoolean<T> {
}
class confirmationMessage {
interaction: CommandInteraction;
interaction: CommandInteraction | ButtonInteraction;
title = "";
emoji = "";
redEmoji: string | null = null;
@ -37,7 +37,15 @@ class confirmationMessage {
inverted = false;
reason: string | null = null;
constructor(interaction: CommandInteraction) {
modals: {
buttonText: string;
emoji: string;
customId: string;
modal: Discord.ModalBuilder;
value: string | undefined;
}[] = [];
constructor(interaction: CommandInteraction | ButtonInteraction) {
this.interaction = interaction;
}
@ -98,11 +106,23 @@ class confirmationMessage {
this.reason = reason;
return this;
}
addModal(buttonText: string, emoji: string, customId: string, current: string, modal: Discord.ModalBuilder) {
modal.setCustomId(customId);
this.modals.push({ buttonText, emoji, customId, modal, value: current });
return this;
}
async send(editOnly?: boolean): Promise<{
success?: boolean;
cancelled?: boolean;
components?: Record<string, CustomBoolean<unknown>>;
newReason?: string;
modals?: {
buttonText: string;
emoji: string;
customId: string;
modal: Discord.ModalBuilder;
value: string | undefined;
}[];
}> {
let cancelled = false;
let success: boolean | undefined = undefined;
@ -131,6 +151,16 @@ class confirmationMessage {
if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
fullComponents.push(button);
});
for (const modal of this.modals) {
fullComponents.push(
new Discord.ButtonBuilder()
.setCustomId(modal.customId)
.setLabel(modal.buttonText)
.setStyle(ButtonStyle.Primary)
.setEmoji(getEmojiByName(modal.emoji, "id"))
.setDisabled(false)
);
}
if (this.reason !== null)
fullComponents.push(
new Discord.ButtonBuilder()
@ -183,7 +213,6 @@ class confirmationMessage {
m = (await this.interaction.reply(object)) as unknown as Message;
}
} catch (e) {
console.log(e);
cancelled = true;
continue;
}
@ -273,6 +302,51 @@ class confirmationMessage {
returnComponents = true;
continue;
}
} else if (this.modals.map((m) => m.customId).includes(component.customId)) {
const chosenModal = this.modals.find(
(
(component) => (m) =>
m.customId === component.customId
)(component)
);
await component.showModal(chosenModal!.modal);
await this.interaction.editReply({
embeds: [
new EmojiEmbed()
.setTitle(this.title)
.setDescription("Modal opened. If you can't see it, click back and try again.")
.setStatus(this.color)
.setEmoji(this.emoji)
],
components: [
new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel("Back")
.setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
.setStyle(ButtonStyle.Primary)
.setCustomId("back")
)
]
});
let out;
try {
out = (await modalInteractionCollector(
m,
this.interaction.user
)) as Discord.ModalSubmitInteraction | null;
} catch (e) {
console.log(e);
cancelled = true;
continue;
}
if (out === null || out.isButton()) {
continue;
}
if (out instanceof ModalSubmitInteraction) {
chosenModal!.value = out.fields.getTextInputValue("default");
}
returnComponents = true;
continue;
} else {
component.deferUpdate();
this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
@ -297,17 +371,24 @@ class confirmationMessage {
],
components: []
});
return { success: false };
return { success: false, cancelled: returnValue.cancelled ?? false };
}
if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
if (success !== undefined) returnValue.success = success;
if (newReason) returnValue.newReason = newReason;
returnValue.modals = this.modals;
const modals = this.modals;
const typedReturnValue = returnValue as
| { cancelled: true }
| { success: boolean; components: Record<string, CustomBoolean<unknown>>; newReason?: string }
| { newReason: string; components: Record<string, CustomBoolean<unknown>> }
| { components: Record<string, CustomBoolean<unknown>> };
| {
success: boolean;
components: Record<string, CustomBoolean<unknown>>;
modals: typeof modals;
newReason?: string;
}
| { newReason: string; components: Record<string, CustomBoolean<unknown>>; modals: typeof modals }
| { components: Record<string, CustomBoolean<unknown>>; modals: typeof modals };
return typedReturnValue;
}

@ -14,14 +14,17 @@ import * as crypto from "crypto";
import _ from "lodash";
import defaultData from "../config/default.js";
const username = encodeURIComponent(config.mongoOptions.username);
const password = encodeURIComponent(config.mongoOptions.password);
let username, password;
// @ts-expect-error
if (Object.keys(config.mongoOptions).includes("username")) username = encodeURIComponent(config.mongoOptions.username);
// @ts-expect-error
if (Object.keys(config.mongoOptions).includes("password")) password = encodeURIComponent(config.mongoOptions.password);
const mongoClient = new MongoClient(
username
? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT`
: `mongodb://${config.mongoOptions.host}`,
{ authSource: config.mongoOptions.authSource }
? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT&authSource=${config.mongoOptions.authSource}`
: `mongodb://${config.mongoOptions.host}`
);
await mongoClient.connect();
const database = mongoClient.db();
@ -221,7 +224,7 @@ interface TranscriptSchema {
interface findDocSchema {
channelID: string;
messageID: string;
code: string;
transcript: string;
}
export class Transcript {
@ -284,20 +287,16 @@ export class Transcript {
async deleteAll(guild: string) {
// console.log("Transcript delete")
const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
const filteredDocs1 = await this.messageToTranscript.find({ guild: guild }).toArray();
for (const doc of filteredDocs) {
await this.transcripts.deleteOne({ code: doc.code });
}
for (const doc of filteredDocs1) {
await this.messageToTranscript.deleteOne({ code: doc.code });
}
}
async readEncrypted(code: string) {
// console.log("Transcript read")
let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
let findDoc: findDocSchema | null = null;
if (!doc) findDoc = await this.messageToTranscript.findOne({ code: code });
if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
if (findDoc) {
const message = await (
client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
@ -334,7 +333,7 @@ export class Transcript {
let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
let findDoc: findDocSchema | null = null;
console.log(doc);
if (!doc) findDoc = await this.messageToTranscript.findOne({ code: code });
if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
if (findDoc) {
const message = await (
client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
@ -837,6 +836,8 @@ export class Premium {
}
}
// export class Plugins {}
export interface GuildConfig {
id: string;
version: number;

Loading…
Cancel
Save