Please look over - not for production

pull/39/head
pineafan 3 years ago
parent f42285d930
commit 6de4da59ed
Signed by: Pinea
GPG Key ID: E5E1C2D43B0E4AB3

@ -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",
@ -113,23 +118,21 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
.setURL(
config.moderation.ban.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).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 +192,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(
@ -101,21 +106,20 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setURL(
config.moderation.kick.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).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 +190,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 +199,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,5 @@
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 +49,22 @@ 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)
@ -198,7 +204,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
"Create appeal ticket",
!(await areTicketsEnabled(interaction.guild.id)),
async () =>
await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
await create(interaction.guild!, member!.user, interaction.user, reason),
"An appeal ticket will be created when Confirm is clicked",
null,
"CONTROL.TICKET",
@ -275,12 +281,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 +400,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,5 @@
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,31 +17,37 @@ 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")!),
user: renderUser(member.user),
"new nickname": `${
(interaction.options.get("name")?.value as string)
? (interaction.options.get("name")?.value as string)
newNickname
? newNickname
: "*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?`
newNickname ? "change" : "clear"
} <@!${member.id}>'s nickname?`
)
.setColor("Danger")
.addCustomBoolean(
@ -51,7 +57,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
async () =>
await create(
interaction.guild!,
interaction.options.getUser("user")!,
member!.user,
interaction.user,
"Nickname changed"
),
@ -70,6 +76,25 @@ 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 +104,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;
@ -96,10 +122,10 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setTitle("Nickname changed")
.setDescription(
`Your nickname was ${
(interaction.options.get("name")?.value as string) ? "changed" : "cleared"
newNickname ? "changed" : "cleared"
} in ${interaction.guild!.name}.` +
((interaction.options.get("name")?.value as string)
? `\nIt is now: ${interaction.options.get("name")?.value as string}`
(newNickname
? `\nIt is now: ${newNickname}`
: "") +
"\n\n" +
(createAppealTicket
@ -122,26 +148,22 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setURL(
config.moderation.nick.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).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 +171,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
interaction.user,
null,
before,
nickname
newNickname
);
} catch {
await interaction.editReply({
@ -175,9 +197,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 +232,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;

@ -1,4 +1,4 @@
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 +14,11 @@ 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,9 +32,9 @@ 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(
@ -40,7 +42,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
"Create appeal ticket",
!(await areTicketsEnabled(interaction.guild.id)),
async () =>
await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
await create(interaction.guild!, member!.user, interaction.user, reason),
"An appeal ticket will be created",
null,
"CONTROL.TICKET",
@ -111,13 +113,13 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setURL(
config.moderation.warn.link.replaceAll(
"{id}",
(interaction.options.getMember("user") as GuildMember).id
member.id
)
)
)
);
}
await (interaction.options.getMember("user") as GuildMember).send(messageData);
await member.send(messageData);
dmSent = true;
}
} catch (e) {
@ -134,8 +136,8 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
},
list: {
user: entry(
(interaction.options.getMember("user") as GuildMember).user.id,
renderUser((interaction.options.getMember("user") as GuildMember).user)
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*"
@ -152,7 +154,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
await client.database.history.create(
"warn",
interaction.guild.id,
(interaction.options.getMember("user") as GuildMember).user,
member.user,
interaction.user,
reason
);
@ -177,7 +179,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
components: []
});
} else {
const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
const canSeeChannel = member
.permissionsIn(interaction.channel as Discord.TextChannel)
.has("ViewChannel");
const m = (await interaction.editReply({
@ -235,9 +237,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 +273,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 +304,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

@ -28,7 +28,10 @@ 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()

@ -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;

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

@ -111,7 +111,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,30 @@ 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 +53,35 @@ 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);
}

@ -8,6 +8,9 @@ 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;
@ -164,7 +167,8 @@ 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" };
@ -173,7 +177,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" };
}
}
}
@ -188,3 +192,76 @@ 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,9 @@ 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 +100,17 @@ 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 +139,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 +201,6 @@ class confirmationMessage {
m = (await this.interaction.reply(object)) as unknown as Message;
}
} catch (e) {
console.log(e);
cancelled = true;
continue;
}
@ -195,7 +212,7 @@ class confirmationMessage {
time: 300000
});
} catch (e) {
success = false;
success = false
break;
}
if (component.customId === "yes") {
@ -273,6 +290,46 @@ 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 +354,19 @@ 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,10 @@ export class Premium {
}
}
export class Plugins {
}
export interface GuildConfig {
id: string;
version: number;

Loading…
Cancel
Save