More commands fixed and purge to here

pull/17/head
PineaFan 3 years ago
parent c729e76ee0
commit a34d04b272
No known key found for this signature in database
GPG Key ID: 0AEF25BAA0FB1C74

@ -53,6 +53,7 @@ const callback = async (interaction: CommandInteraction): Promise<void> => {
false, false,
undefined, undefined,
"The user will be sent a DM", "The user will be sent a DM",
null,
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"), "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify notify
) )

@ -1,3 +1,4 @@
import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
import type { HistorySchema } from "../../utils/database.js"; import type { HistorySchema } from "../../utils/database.js";
import Discord, { import Discord, {
CommandInteraction, CommandInteraction,
@ -8,11 +9,12 @@ import Discord, {
ButtonBuilder, ButtonBuilder,
MessageComponentInteraction, MessageComponentInteraction,
ModalSubmitInteraction, ModalSubmitInteraction,
TextInputComponent,
ButtonStyle, ButtonStyle,
StringSelectMenuInteraction StringSelectMenuInteraction,
TextInputStyle,
APIMessageComponentEmoji
} from "discord.js"; } from "discord.js";
import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import getEmojiByName from "../../utils/getEmojiByName.js"; import getEmojiByName from "../../utils/getEmojiByName.js";
import client from "../../utils/client.js"; import client from "../../utils/client.js";
@ -153,34 +155,33 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte
} }
} }
if (pageIndex === null) pageIndex = 0; if (pageIndex === null) pageIndex = 0;
const components = ( let components: (ActionRowBuilder<Discord.StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[] = []
openFilterPane if (openFilterPane) components = components.concat([
? [
new ActionRowBuilder<Discord.StringSelectMenuBuilder>().addComponents( new ActionRowBuilder<Discord.StringSelectMenuBuilder>().addComponents(
new Discord.SelectMenuBuilder() new Discord.StringSelectMenuBuilder().setOptions(
.setOptions( ...Object.entries(types).map(([key, value]) => new StringSelectMenuOptionBuilder()
Object.entries(types).map(([key, value]) => ({ .setLabel(value.text)
label: value.text, .setValue(key)
value: key, .setDefault(filteredTypes.includes(key))
default: filteredTypes.includes(key), .setEmoji(client.emojis.resolve(getEmojiByName(value.emoji, "id"))! as APIMessageComponentEmoji)
emoji: client.emojis.resolve(getEmojiByName(value.emoji, "id")) )
}))
) )
.setMinValues(1) .setMinValues(1)
.setMaxValues(Object.keys(types).length) .setMaxValues(Object.keys(types).length)
.setCustomId("filter") .setCustomId("filter")
.setPlaceholder("Select at least one event") .setPlaceholder("Select events to show")
) )
] ])
: [] components = components.concat([new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
).concat([
new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
new ButtonBuilder() new ButtonBuilder()
.setCustomId("prevYear") .setCustomId("prevYear")
.setLabel((currentYear - 1).toString()) .setLabel((currentYear - 1).toString())
.setEmoji(getEmojiByName("CONTROL.LEFT", "id")) .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
.setStyle(ButtonStyle.Secondary), .setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId("prevPage").setLabel("Previous page").setStyle(ButtonStyle.Primary), new ButtonBuilder()
.setCustomId("prevPage")
.setLabel("Previous page")
.setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId("today").setLabel("Today").setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId("today").setLabel("Today").setStyle(ButtonStyle.Primary),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("nextPage") .setCustomId("nextPage")
@ -193,8 +194,8 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte
.setEmoji(getEmojiByName("CONTROL.RIGHT", "id")) .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
.setDisabled(currentYear === new Date().getFullYear()) .setDisabled(currentYear === new Date().getFullYear())
]), ])])
new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([ components = components.concat([new ActionRowBuilder<Discord.ButtonBuilder>().addComponents([
new ButtonBuilder() new ButtonBuilder()
.setLabel("Mod notes") .setLabel("Mod notes")
.setCustomId("modNotes") .setCustomId("modNotes")
@ -205,8 +206,8 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte
.setCustomId("openFilter") .setCustomId("openFilter")
.setStyle(openFilterPane ? ButtonStyle.Success : ButtonStyle.Primary) .setStyle(openFilterPane ? ButtonStyle.Success : ButtonStyle.Primary)
.setEmoji(getEmojiByName("ICONS.FILTER", "id")) .setEmoji(getEmojiByName("ICONS.FILTER", "id"))
]) ])])
]);
const end = const end =
"\n\nJanuary " + "\n\nJanuary " +
currentYear.toString() + currentYear.toString() +
@ -216,7 +217,7 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte
currentYear.toString(); currentYear.toString();
if (groups.length > 0) { if (groups.length > 0) {
const toRender = groups[Math.min(pageIndex, groups.length - 1)]!; const toRender = groups[Math.min(pageIndex, groups.length - 1)]!;
m = (await interaction.editReply({ m = await interaction.editReply({
embeds: [ embeds: [
new EmojiEmbed() new EmojiEmbed()
.setEmoji("MEMBER.JOIN") .setEmoji("MEMBER.JOIN")
@ -226,25 +227,25 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte
) )
.setStatus("Success") .setStatus("Success")
.setFooter({ .setFooter({
text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : "" text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : "No filters selected"
}) })
], ],
components: components components: components
})) as Message; });
} else { } else {
m = (await interaction.editReply({ m = await interaction.editReply({
embeds: [ embeds: [
new EmojiEmbed() new EmojiEmbed()
.setEmoji("MEMBER.JOIN") .setEmoji("MEMBER.JOIN")
.setTitle("Moderation history for " + member.user.username) .setTitle("Moderation history for " + member.user.username)
.setDescription(`**${currentYear}**\n\n*No events*` + "\n\n" + end) .setDescription(`**${currentYear}**\n\n*No events*`)
.setStatus("Success") .setStatus("Success")
.setFooter({ .setFooter({
text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : "" text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : "No filters selected"
}) })
], ],
components: components components: components
})) as Message; })
} }
let i: MessageComponentInteraction; let i: MessageComponentInteraction;
try { try {
@ -315,7 +316,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
let m: Message; let m: Message;
const member = interaction.options.getMember("user") as Discord.GuildMember; const member = interaction.options.getMember("user") as Discord.GuildMember;
await interaction.reply({ await interaction.reply({
embeds: [new EmojiEmbed().setEmoji("NUCLEUS.LOADING").setTitle("Downloading Data").setStatus("Danger")], embeds: LoadingEmbed,
ephemeral: true, ephemeral: true,
fetchReply: true fetchReply: true
}); });
@ -324,9 +325,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
let timedOut = false; let timedOut = false;
while (!timedOut) { while (!timedOut) {
note = await client.database.notes.read(member.guild.id, member.id); note = await client.database.notes.read(member.guild.id, member.id);
if (firstLoad && !note) { if (firstLoad && !note) { await showHistory(member, interaction); }
await showHistory(member, interaction);
}
firstLoad = false; firstLoad = false;
m = (await interaction.editReply({ m = (await interaction.editReply({
embeds: [ embeds: [
@ -344,7 +343,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setCustomId("modify") .setCustomId("modify")
.setEmoji(getEmojiByName("ICONS.EDIT", "id")), .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
new ButtonBuilder() new ButtonBuilder()
.setLabel("View moderation history") .setLabel("Moderation history")
.setStyle(ButtonStyle.Primary) .setStyle(ButtonStyle.Primary)
.setCustomId("history") .setCustomId("history")
.setEmoji(getEmojiByName("ICONS.HISTORY", "id")) .setEmoji(getEmojiByName("ICONS.HISTORY", "id"))
@ -364,13 +363,13 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setCustomId("modal") .setCustomId("modal")
.setTitle("Editing moderator note") .setTitle("Editing moderator note")
.addComponents( .addComponents(
new ActionRowBuilder<Discord.TextInputComponent>().addComponents( new ActionRowBuilder<Discord.TextInputBuilder>().addComponents(
new TextInputComponent() new Discord.TextInputBuilder()
.setCustomId("note") .setCustomId("note")
.setLabel("Note") .setLabel("Note")
.setMaxLength(4000) .setMaxLength(4000)
.setRequired(false) .setRequired(false)
.setStyle("PARAGRAPH") .setStyle(TextInputStyle.Paragraph)
.setValue(note ? note : " ") .setValue(note ? note : " ")
) )
) )
@ -408,7 +407,9 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
if (out === null) { if (out === null) {
continue; continue;
} else if (out instanceof ModalSubmitInteraction) { } else if (out instanceof ModalSubmitInteraction) {
const toAdd = out.fields.getTextInputValue("note") || null; let toAdd = out.fields.getTextInputValue("note") || null;
if (toAdd === " ") toAdd = null;
if (toAdd) toAdd = toAdd.trim();
await client.database.notes.create(member.guild.id, member.id, toAdd); await client.database.notes.create(member.guild.id, member.id, toAdd);
} else { } else {
continue; continue;

@ -1,4 +1,4 @@
import { LinkWarningFooter } from './../../utils/defaultEmbeds'; import { LinkWarningFooter } from './../../utils/defaultEmbeds.js';
import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
// @ts-expect-error // @ts-expect-error
import humanizeDuration from "humanize-duration"; import humanizeDuration from "humanize-duration";
@ -42,6 +42,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
false, false,
null, null,
"The user will be sent a DM", "The user will be sent a DM",
null,
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"), "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify notify
) )

@ -192,6 +192,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
async () => async () =>
await create(interaction.guild, interaction.options.getUser("user")!, interaction.user, reason), await create(interaction.guild, interaction.options.getUser("user")!, interaction.user, reason),
"An appeal ticket will be created when Confirm is clicked", "An appeal ticket will be created when Confirm is clicked",
null,
"CONTROL.TICKET", "CONTROL.TICKET",
createAppealTicket createAppealTicket
) )
@ -201,6 +202,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
false, false,
undefined, undefined,
null, null,
null,
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"), "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify notify
) )

@ -45,6 +45,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
false, false,
null, null,
null, null,
null,
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"), "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify notify
) )

@ -1,4 +1,4 @@
import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle } from "discord.js"; import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder } from "discord.js";
import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import confirmationMessage from "../../utils/confirmationMessage.js"; import confirmationMessage from "../../utils/confirmationMessage.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@ -26,7 +26,8 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
); );
const callback = async (interaction: CommandInteraction): Promise<unknown> => { const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const user = (interaction.options.getMember("user") as GuildMember); if (!interaction.guild) return;
const user = (interaction.options.getMember("user") as GuildMember | null);
const channel = interaction.channel as GuildChannel; const channel = interaction.channel as GuildChannel;
if ( if (
!["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes( !["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(
@ -46,7 +47,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
}); });
} }
// TODO:[Modals] Replace this with a modal // TODO:[Modals] Replace this with a modal
if (!interaction.options.getInteger("amount")) { if (!interaction.options.get("amount")) {
await interaction.reply({ await interaction.reply({
embeds: [ embeds: [
new EmojiEmbed() new EmojiEmbed()
@ -74,17 +75,17 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setStatus("Danger") .setStatus("Danger")
], ],
components: [ components: [
new Discord.ActionRowBuilder().addComponents([ new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
new Discord.ButtonBuilder().setCustomId("1").setLabel("1").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("1").setLabel("1").setStyle(ButtonStyle.Secondary),
new Discord.ButtonBuilder().setCustomId("3").setLabel("3").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("3").setLabel("3").setStyle(ButtonStyle.Secondary),
new Discord.ButtonBuilder().setCustomId("5").setLabel("5").setStyle(ButtonStyle.Secondary) new Discord.ButtonBuilder().setCustomId("5").setLabel("5").setStyle(ButtonStyle.Secondary)
]), ]),
new Discord.ActionRowBuilder().addComponents([ new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
new Discord.ButtonBuilder().setCustomId("10").setLabel("10").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("10").setLabel("10").setStyle(ButtonStyle.Secondary),
new Discord.ButtonBuilder().setCustomId("25").setLabel("25").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("25").setLabel("25").setStyle(ButtonStyle.Secondary),
new Discord.ButtonBuilder().setCustomId("50").setLabel("50").setStyle(ButtonStyle.Secondary) new Discord.ButtonBuilder().setCustomId("50").setLabel("50").setStyle(ButtonStyle.Secondary)
]), ]),
new Discord.ActionRowBuilder().addComponents([ new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
new Discord.ButtonBuilder() new Discord.ButtonBuilder()
.setCustomId("done") .setCustomId("done")
.setLabel("Done") .setLabel("Done")
@ -110,16 +111,14 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
} }
const amount = parseInt((await component).customId); const amount = parseInt((await component).customId);
let messages; let messages: Discord.Message[] = [];
await (interaction.channel as TextChannel).messages.fetch({ limit: amount }).then(async (ms) => { await (interaction.channel as TextChannel).messages.fetch({ limit: amount }).then(async (ms) => {
if (user) { if (user) {
ms = ms.filter((m) => m.author.id === user.id); ms = ms.filter((m) => m.author.id === user.id);
} }
messages = await (channel as TextChannel).bulkDelete(ms, true); messages = (await (channel as TextChannel).bulkDelete(ms, true)).map(m => m as Discord.Message);
}); });
if (messages) { deleted = deleted.concat(messages);
deleted = deleted.concat(messages.map((m) => m));
}
} }
if (deleted.length === 0) if (deleted.length === 0)
return await interaction.editReply({ return await interaction.editReply({
@ -136,11 +135,12 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
await client.database.history.create( await client.database.history.create(
"purge", "purge",
interaction.guild.id, interaction.guild.id,
user, user.user,
interaction.options.getString("reason"), interaction.user,
(interaction.options.get("reason")?.value as (string | null)) ?? "*No reason provided*",
null, null,
null, null,
deleted.length deleted.length.toString()
); );
} }
const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
@ -156,8 +156,8 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
list: { list: {
memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
purgedBy: entry(interaction.user.id, renderUser(interaction.user)), purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
channel: entry(interaction.channel.id, renderChannel(interaction.channel)), channel: entry(interaction.channel!.id, renderChannel(interaction.channel! as GuildChannel)),
messagesCleared: entry(deleted.length, deleted.length) messagesCleared: entry(deleted.length.toString(), deleted.length.toString())
}, },
hidden: { hidden: {
guild: interaction.guild.id guild: interaction.guild.id
@ -189,7 +189,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setStatus("Success") .setStatus("Success")
], ],
components: [ components: [
new Discord.ActionRowBuilder().addComponents([ new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
new Discord.ButtonBuilder() new Discord.ButtonBuilder()
.setCustomId("download") .setCustomId("download")
.setLabel("Download transcript") .setLabel("Download transcript")
@ -207,7 +207,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
} catch { } catch {
return; return;
} }
if (component && component.customId === "download") { if (component.customId === "download") {
interaction.editReply({ interaction.editReply({
embeds: [ embeds: [
new EmojiEmbed() new EmojiEmbed()
@ -239,12 +239,8 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setDescription( .setDescription(
keyValueList({ keyValueList({
channel: `<#${channel.id}>`, channel: `<#${channel.id}>`,
amount: interaction.options.getInteger("amount").toString(), amount: (interaction.options.get("amount")?.value as number).toString(),
reason: `\n> ${ reason: `\n> ${interaction.options.get("reason")?.value ? interaction.options.get("reason")?.value : "*No reason provided*"}`
interaction.options.getString("reason")
? interaction.options.getString("reason")
: "*No reason provided*"
}`
}) })
) )
.setColor("Danger") .setColor("Danger")
@ -255,7 +251,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
try { try {
if (!user) { if (!user) {
const toDelete = await (interaction.channel as TextChannel).messages.fetch({ const toDelete = await (interaction.channel as TextChannel).messages.fetch({
limit: interaction.options.getInteger("amount") limit: interaction.options.get("amount")?.value as number
}); });
messages = await (channel as TextChannel).bulkDelete(toDelete, true); messages = await (channel as TextChannel).bulkDelete(toDelete, true);
} else { } else {
@ -265,7 +261,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
limit: 100 limit: 100
}) })
).filter((m) => m.author.id === user.id) ).filter((m) => m.author.id === user.id)
).first(interaction.options.getInteger("amount")); ).first(interaction.options.get("amount")?.value as number);
messages = await (channel as TextChannel).bulkDelete(toDelete, true); messages = await (channel as TextChannel).bulkDelete(toDelete, true);
} }
} catch (e) { } catch (e) {
@ -280,15 +276,29 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
components: [] components: []
}); });
} }
if (!messages) {
await interaction.editReply({
embeds: [
new EmojiEmbed()
.setEmoji("CHANNEL.PURGE.RED")
.setTitle("Purge")
.setDescription("No messages could be deleted")
.setStatus("Danger")
],
components: []
});
return;
}
if (user) { if (user) {
await client.database.history.create( await client.database.history.create(
"purge", "purge",
interaction.guild.id, interaction.guild.id,
user, user.user,
interaction.options.getString("reason"), interaction.user,
(interaction.options.get("reason")?.value as (string | null)) ?? "*No reason provided*",
null, null,
null, null,
messages.size messages.size.toString()
); );
} }
const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
@ -304,8 +314,8 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
list: { list: {
memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
purgedBy: entry(interaction.user.id, renderUser(interaction.user)), purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
channel: entry(interaction.channel.id, renderChannel(interaction.channel)), channel: entry(interaction.channel!.id, renderChannel(interaction.channel! as GuildChannel)),
messagesCleared: entry(messages.size, messages.size) messagesCleared: entry(messages.size.toString(), messages.size.toString())
}, },
hidden: { hidden: {
guild: interaction.guild.id guild: interaction.guild.id
@ -314,14 +324,26 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
log(data); log(data);
let out = ""; let out = "";
messages.reverse().forEach((message) => { messages.reverse().forEach((message) => {
out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date( if (!message) {
out += "Unknown message\n\n"
} else {
const author = message.author ?? { username: "Unknown", discriminator: "0000", id: "Unknown" };
out += `${author.username}#${author.discriminator} (${author.id}) [${new Date(
message.createdTimestamp message.createdTimestamp
).toISOString()}]\n`; ).toISOString()}]\n`;
if (message.content) {
const lines = message.content.split("\n"); const lines = message.content.split("\n");
lines.forEach((line) => { lines.forEach((line) => {
out += `> ${line}\n`; out += `> ${line}\n`;
}); });
}
if (message.attachments.size > 0) {
message.attachments.forEach((attachment) => {
out += `Attachment > ${attachment.url}\n`;
});
}
out += "\n\n"; out += "\n\n";
}
}); });
const attachmentObject = { const attachmentObject = {
attachment: Buffer.from(out), attachment: Buffer.from(out),
@ -337,7 +359,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
.setStatus("Success") .setStatus("Success")
], ],
components: [ components: [
new Discord.ActionRowBuilder().addComponents([ new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
new Discord.ButtonBuilder() new Discord.ButtonBuilder()
.setCustomId("download") .setCustomId("download")
.setLabel("Download transcript") .setLabel("Download transcript")
@ -355,7 +377,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
} catch { } catch {
return; return;
} }
if (component && component.customId === "download") { if (component.customId === "download") {
interaction.editReply({ interaction.editReply({
embeds: [ embeds: [
new EmojiEmbed() new EmojiEmbed()
@ -395,14 +417,15 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
}; };
const check = (interaction: CommandInteraction) => { const check = (interaction: CommandInteraction) => {
if (!interaction.guild) return false;
const member = interaction.member as GuildMember; const member = interaction.member as GuildMember;
const me = interaction.guild.me!; const me = interaction.guild.members.me!;
// Check if nucleus has the manage_messages permission // Check if nucleus has the manage_messages permission
if (!me.permissions.has("MANAGE_MESSAGES")) throw new Error("I do not have the *Manage Messages* permission"); if (!me.permissions.has("ManageMessages")) throw new Error("I do not have the *Manage Messages* permission");
// Allow the owner to purge // Allow the owner to purge
if (member.id === interaction.guild.ownerId) return true; if (member.id === interaction.guild.ownerId) return true;
// Check if the user has manage_messages permission // Check if the user has manage_messages permission
if (!member.permissions.has("MANAGE_MESSAGES")) throw new Error("You do not have the *Manage Messages* permission"); if (!member.permissions.has("ManageMessages")) throw new Error("You do not have the *Manage Messages* permission");
// Allow purge // Allow purge
return true; return true;
}; };

@ -51,6 +51,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
false, false,
null, null,
null, null,
null,
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"), "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify notify
) )

@ -38,6 +38,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
false, false,
null, null,
"The user will be sent a DM", "The user will be sent a DM",
null,
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"), "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify notify
) )

@ -41,6 +41,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
!(await areTicketsEnabled(interaction.guild.id)), !(await areTicketsEnabled(interaction.guild.id)),
async () => await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason), async () => await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
"An appeal ticket will be created", "An appeal ticket will be created",
null,
"CONTROL.TICKET", "CONTROL.TICKET",
createAppealTicket createAppealTicket
) )
@ -50,6 +51,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
false, false,
null, null,
"The user will be sent a DM", "The user will be sent a DM",
null,
"ICONS.NOTIFY." + (notify ? "ON" : "OFF"), "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
notify notify
) )

@ -2,6 +2,8 @@ import { AutocompleteInteraction, CommandInteraction, ActionRowBuilder, ButtonBu
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from "@discordjs/builders";
import client from "../utils/client.js"; import client from "../utils/client.js";
import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js";
import { capitalize } from "../utils/generateKeyValueList.js";
import { getResults } from "../utils/search.js";
const command = new SlashCommandBuilder() const command = new SlashCommandBuilder()
.setName("tag") .setName("tag")
@ -9,41 +11,44 @@ const command = new SlashCommandBuilder()
.addStringOption((o) => o.setName("tag").setDescription("The tag to get").setAutocomplete(true).setRequired(true)); .addStringOption((o) => o.setName("tag").setDescription("The tag to get").setAutocomplete(true).setRequired(true));
const callback = async (interaction: CommandInteraction): Promise<void> => { const callback = async (interaction: CommandInteraction): Promise<void> => {
const config = await client.database.guilds.read(interaction.guild.id); const config = await client.database.guilds.read(interaction.guild!.id);
const tags = config.getKey("tags"); const tags = config.tags;
const tag = tags[interaction.options.getString("tag")]; const search = interaction.options.get("tag")?.value as string;
const tag = tags[search];
if (!tag) { if (!tag) {
return await interaction.reply({ await interaction.reply({
embeds: [ embeds: [
new EmojiEmbed() new EmojiEmbed()
.setTitle("Tag") .setTitle("Tag")
.setDescription(`Tag \`${interaction.options.get("tag")}\` does not exist`) .setDescription(`Tag \`${search}\` does not exist`)
.setEmoji("PUNISH.NICKNAME.RED") .setEmoji("PUNISH.NICKNAME.RED")
.setStatus("Danger") .setStatus("Danger")
], ],
ephemeral: true ephemeral: true
}); });
return;
} }
let url = ""; let url = "";
let components: ActionRowBuilder[] = []; let components: ActionRowBuilder<ButtonBuilder>[] = [];
if (tag.match(/^(http|https):\/\/[^ "]+$/)) { if (tag.match(/^(http|https):\/\/[^ "]+$/)) {
url = tag; url = tag;
components = [ components = [
new ActionRowBuilder().addComponents([new ButtonBuilder().setLabel("Open").setURL(url).setStyle(ButtonStyle.Link)]) new ActionRowBuilder<ButtonBuilder>().addComponents([new ButtonBuilder().setLabel("Open").setURL(url).setStyle(ButtonStyle.Link)])
]; ];
} }
return await interaction.reply({ const em = new EmojiEmbed()
embeds: [ .setTitle(capitalize(search))
new EmojiEmbed()
.setTitle(interaction.options.get("tag").value)
.setDescription(tag)
.setEmoji("PUNISH.NICKNAME.GREEN") .setEmoji("PUNISH.NICKNAME.GREEN")
.setStatus("Success") .setStatus("Success")
.setImage(url) if (url) em.setImage(url)
], else em.setDescription(tag);
await interaction.reply({
embeds: [em],
components: components, components: components,
ephemeral: true ephemeral: true
}); });
return;
}; };
const check = () => { const check = () => {
@ -52,9 +57,12 @@ const check = () => {
const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => { const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
if (!interaction.guild) return []; if (!interaction.guild) return [];
const config = await client.database.guilds.read(interaction.guild.id); const prompt = interaction.options.getString("tag");
const tags = Object.keys(config.getKey("tags")); console.log(prompt)
return tags; const possible = Object.keys((await client.memory.readGuildInfo(interaction.guild.id)).tags);
const results = getResults(prompt ?? "", possible);
console.log(results)
return results;
}; };
export { command }; export { command };

@ -16,8 +16,8 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
); );
const callback = async (interaction: CommandInteraction): Promise<unknown> => { const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const name = interaction.options.getString("name"); const name = interaction.options.get("name")?.value as string;
const value = interaction.options.getString("value"); const value = interaction.options.get("value")?.value as string;
if (name!.length > 100) if (name!.length > 100)
return await interaction.reply({ return await interaction.reply({
embeds: [ embeds: [
@ -41,7 +41,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
ephemeral: true ephemeral: true
}); });
const data = await client.database.guilds.read(interaction.guild!.id); const data = await client.database.guilds.read(interaction.guild!.id);
if (data.tags.length >= 100) if (Object.keys(data.tags).length >= 100)
return await interaction.reply({ return await interaction.reply({
embeds: [ embeds: [
new EmojiEmbed() new EmojiEmbed()
@ -90,6 +90,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
await client.database.guilds.write(interaction.guild!.id, { await client.database.guilds.write(interaction.guild!.id, {
[`tags.${name}`]: value [`tags.${name}`]: value
}); });
await client.memory.forceUpdate(interaction.guild!.id);
} catch (e) { } catch (e) {
return await interaction.editReply({ return await interaction.editReply({
embeds: [ embeds: [
@ -116,7 +117,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const check = (interaction: CommandInteraction) => { const check = (interaction: CommandInteraction) => {
const member = interaction.member as Discord.GuildMember; const member = interaction.member as Discord.GuildMember;
if (!member.permissions.has("MANAGE_MESSAGES")) if (!member.permissions.has("ManageMessages"))
throw new Error("You must have the *Manage Messages* permission to use this command"); throw new Error("You must have the *Manage Messages* permission to use this command");
return true; return true;
}; };

@ -13,7 +13,7 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
.addStringOption((o) => o.setName("name").setRequired(true).setDescription("The name of the tag")); .addStringOption((o) => o.setName("name").setRequired(true).setDescription("The name of the tag"));
const callback = async (interaction: CommandInteraction): Promise<unknown> => { const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const name = interaction.options.getString("name"); const name = interaction.options.get("name")?.value as string;
const data = await client.database.guilds.read(interaction.guild!.id); const data = await client.database.guilds.read(interaction.guild!.id);
if (!data.tags[name]) if (!data.tags[name])
return await interaction.reply({ return await interaction.reply({
@ -51,6 +51,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
}); });
try { try {
await client.database.guilds.write(interaction.guild!.id, null, ["tags." + name]); await client.database.guilds.write(interaction.guild!.id, null, ["tags." + name]);
await client.memory.forceUpdate(interaction.guild!.id);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
return await interaction.editReply({ return await interaction.editReply({
@ -78,7 +79,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const check = (interaction: CommandInteraction) => { const check = (interaction: CommandInteraction) => {
const member = interaction.member as Discord.GuildMember; const member = interaction.member as Discord.GuildMember;
if (!member.permissions.has("MANAGE_MESSAGES")) if (!member.permissions.has("ManageMessages"))
throw new Error("You must have the *Manage Messages* permission to use this command"); throw new Error("You must have the *Manage Messages* permission to use this command");
return true; return true;
}; };

@ -110,6 +110,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
toSet[`tags.${newname}`] = data.tags[name]; toSet[`tags.${newname}`] = data.tags[name];
} }
await client.database.guilds.write(interaction.guild.id, toSet === {} ? null : toSet, toUnset); await client.database.guilds.write(interaction.guild.id, toSet === {} ? null : toSet, toUnset);
await client.memory.forceUpdate(interaction.guild!.id);
} catch (e) { } catch (e) {
return await interaction.editReply({ return await interaction.editReply({
embeds: [ embeds: [
@ -136,7 +137,7 @@ const callback = async (interaction: CommandInteraction): Promise<unknown> => {
const check = (interaction: CommandInteraction) => { const check = (interaction: CommandInteraction) => {
const member = interaction.member as GuildMember; const member = interaction.member as GuildMember;
if (!member.permissions.has("MANAGE_MESSAGES")) if (!member.permissions.has("ManageMessages"))
throw new Error("You must have the *Manage Messages* permission to use this command"); throw new Error("You must have the *Manage Messages* permission to use this command");
return true; return true;
}; };

@ -1,4 +1,4 @@
import { LoadingEmbed } from "./../../utils/defaultEmbeds.js"; import { LoadingEmbed, Embed } from "./../../utils/defaultEmbeds.js";
import Discord, { import Discord, {
CommandInteraction, CommandInteraction,
GuildMember, GuildMember,
@ -25,30 +25,6 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
option.setName("user").setDescription("The user to get info about | Default: Yourself") option.setName("user").setDescription("The user to get info about | Default: Yourself")
); );
class Embed {
embed: EmojiEmbed = new EmojiEmbed();
title: string = "";
description = "";
pageId = 0;
setEmbed(embed: EmojiEmbed) {
this.embed = embed;
return this;
}
setTitle(title: string) {
this.title = title;
return this;
}
setDescription(description: string) {
this.description = description;
return this;
}
setPageId(pageId: number) {
this.pageId = pageId;
return this;
}
}
const callback = async (interaction: CommandInteraction): Promise<void> => { const callback = async (interaction: CommandInteraction): Promise<void> => {
const guild = interaction.guild!; const guild = interaction.guild!;
const member = (interaction.options.getMember("user") ?? interaction.member) as Discord.GuildMember; const member = (interaction.options.getMember("user") ?? interaction.member) as Discord.GuildMember;
@ -101,11 +77,11 @@ async function userAbout(guild: Discord.Guild, member: Discord.GuildMember, inte
BugHunterLevel2: "Bug Hunter Level 2", BugHunterLevel2: "Bug Hunter Level 2",
Partner: "Partnered Server Owner", Partner: "Partnered Server Owner",
Staff: "Discord Staff", Staff: "Discord Staff",
VerifiedDeveloper: "Verified Bot Developer" VerifiedDeveloper: "Verified Bot Developer",
// ActiveDeveloper ActiveDeveloper: "Active Developer",
Quarantined: "Quarantined [What does this mean?](https://support.discord.com/hc/en-us/articles/6461420677527)",
Spammer: "Likely Spammer"
// CertifiedModerator // CertifiedModerator
// Quarantined https://discord-api-types.dev/api/discord-api-types-v10/enum/UserFlags#Quarantined
// Spammer https://discord-api-types.dev/api/discord-api-types-v10/enum/UserFlags#Spammer
// VerifiedBot // VerifiedBot
}; };
const members = await guild.members.fetch(); const members = await guild.members.fetch();
@ -121,8 +97,8 @@ async function userAbout(guild: Discord.Guild, member: Discord.GuildMember, inte
let s = ""; let s = "";
let count = 0; let count = 0;
let ended = false; let ended = false;
for (const roleId in roles) { for (const roleId of roles) {
const string = `<@&${roleId}>, `; const string = `<@&${roleId[1].id}>, `;
if (s.length + string.length > 1000) { if (s.length + string.length > 1000) {
ended = true; ended = true;
s += `and ${roles.size - count} more`; s += `and ${roles.size - count} more`;

@ -190,7 +190,10 @@
"BugHunterLevel2": "775783766130950234", "BugHunterLevel2": "775783766130950234",
"Partner": "775783766178005033", "Partner": "775783766178005033",
"Staff": "775783766383788082", "Staff": "775783766383788082",
"VerifiedDeveloper": "775783766425600060" "VerifiedDeveloper": "775783766425600060",
"Quarantined": "1059794708638994474",
"Spammer": "1059794708638994474",
"ActiveDeveloper": "1059795592966053918"
}, },
"VOICE": { "VOICE": {
"CONNECT": "784785219391193138", "CONNECT": "784785219391193138",

@ -0,0 +1,279 @@
import confirmationMessage from '../../utils/confirmationMessage.js';
import EmojiEmbed from '../../utils/generateEmojiEmbed.js';
import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, ContextMenuCommandBuilder, GuildTextBasedChannel, MessageContextMenuCommandInteraction } from "discord.js";
import client from "../../utils/client.js";
import getEmojiByName from '../../utils/getEmojiByName.js';
const command = new ContextMenuCommandBuilder()
.setName("Purge up to here")
async function waitForButton(m: Discord.Message, member: Discord.GuildMember): Promise<boolean> {
let component;
try {
component = m.awaitMessageComponent({ time: 200000, filter: (i) => i.user.id === member.id });
} catch (e) {
return false;
}
(await component).deferUpdate();
return true;
}
const callback = async (interaction: MessageContextMenuCommandInteraction) => {
await interaction.targetMessage.fetch();
const targetMessage = interaction.targetMessage;
const targetMember: Discord.User = targetMessage.author;
let allowedMessage: Discord.Message | undefined = undefined;
const channel = interaction.channel;
if (!channel) return;
await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
// Option for "include this message"?
// Option for "Only selected user"?
const history: Discord.Collection<string, Discord.Message> = await channel.messages.fetch({ limit: 100 });
if (Date.now() - targetMessage.createdTimestamp > 2 * 7 * 24 * 60 * 60 * 1000) {
const m = await interaction.editReply({ embeds: [new EmojiEmbed()
.setTitle("Purge")
.setDescription("The message you selected is older than 2 weeks. Discord only allows bots to delete messages that are 2 weeks old or younger.")
.setEmoji("CHANNEL.PURGE.RED")
.setStatus("Danger")
], components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("oldest")
.setLabel("Select first allowed message")
.setStyle(ButtonStyle.Primary),
)
]});
if (!await waitForButton(m, interaction.member as Discord.GuildMember)) return;
} else if (!history.has(targetMessage.id)) {
const m = await interaction.editReply({ embeds: [new EmojiEmbed()
.setTitle("Purge")
.setDescription("The message you selected is not in the last 100 messages in this channel. Discord only allows bots to delete 100 messages at a time.")
.setEmoji("CHANNEL.PURGE.YELLOW")
.setStatus("Warning")
], components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("oldest")
.setLabel("Select first allowed message")
.setStyle(ButtonStyle.Primary),
)
]});
if (!await waitForButton(m, interaction.member as Discord.GuildMember)) return;
} else {
allowedMessage = targetMessage;
}
if (!allowedMessage) {
// Find the oldest message thats younger than 2 weeks
const messages = history.filter(m => Date.now() - m.createdTimestamp < 2 * 7 * 24 * 60 * 60 * 1000);
allowedMessage = messages.sort((a, b) => a.createdTimestamp - b.createdTimestamp).first();
}
if (!allowedMessage) {
await interaction.editReply({ embeds: [new EmojiEmbed()
.setTitle("Purge")
.setDescription("There are no valid messages in the last 100 messages. (No messages younger than 2 weeks)")
.setEmoji("CHANNEL.PURGE.RED")
.setStatus("Danger")
], components: [] });
return;
}
let reason: string | null = null
let confirmation;
let chosen = false;
let timedOut = false;
let deleteSelected = true;
let deleteUser = false;
do {
confirmation = await new confirmationMessage(interaction)
.setEmoji("CHANNEL.PURGE.RED")
.setTitle("Purge")
.setDescription(
`[[Selected Message]](${allowedMessage.url})\n\n` +
(reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*") + "\n\n" +
`Are you sure you want to delete all messages from below the selected message?`
)
.addCustomBoolean(
"includeSelected",
"Include selected message",
false,
undefined,
"The selected message will be deleted as well.",
"The selected message will not be deleted.",
"CONTROL." + (deleteSelected ? "TICK" : "CROSS"),
deleteSelected
)
.addCustomBoolean(
"onlySelectedUser",
"Only selected user",
false,
undefined,
`Only messages from <@${targetMember.id}> will be deleted.`,
`All messages will be deleted.`,
"CONTROL." + (deleteUser ? "TICK" : "CROSS"),
deleteUser
)
.setColor("Danger")
.addReasonButton(reason ?? "")
.send(true)
reason = reason ?? ""
if (confirmation.cancelled) timedOut = true;
else if (confirmation.success !== undefined) chosen = true;
else if (confirmation.newReason) reason = confirmation.newReason;
else if (confirmation.components) {
deleteSelected = confirmation.components["includeSelected"]!.active;
deleteUser = confirmation.components["onlySelectedUser"]!.active;
}
} while (!chosen && !timedOut);
if (timedOut) return;
if (!confirmation.success) {
await interaction.editReply({ embeds: [new EmojiEmbed()
.setTitle("Purge")
.setDescription("No changes were made")
.setEmoji("CHANNEL.PURGE.GREEN")
.setStatus("Success")
], components: [] });
return;
}
const filteredMessages = history
.filter(m => m.createdTimestamp >= allowedMessage!.createdTimestamp) // older than selected
.filter(m => deleteUser ? m.author.id === targetMember.id : true) // only selected user
.filter(m => deleteSelected ? true : m.id !== allowedMessage!.id) // include selected
const deleted = await (channel as GuildTextBasedChannel).bulkDelete(filteredMessages, true);
if (deleted.size === 0) {
return await interaction.editReply({
embeds: [
new EmojiEmbed()
.setEmoji("CHANNEL.PURGE.RED")
.setTitle("Purge")
.setDescription("No messages were deleted")
.setStatus("Danger")
],
components: []
});
}
if (deleteUser) {
await client.database.history.create(
"purge",
interaction.guild!.id,
targetMember,
interaction.user,
reason === "" ? "*No reason provided*" : reason,
null,
null,
deleted.size.toString()
);
}
const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
const data = {
meta: {
type: "channelPurge",
displayName: "Channel Purged",
calculateType: "messageDelete",
color: NucleusColors.red,
emoji: "PUNISH.BAN.RED",
timestamp: new Date().getTime()
},
list: {
memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
purgedBy: entry(interaction.user.id, renderUser(interaction.user)),
channel: entry(interaction.channel!.id, renderChannel(interaction.channel! as Discord.GuildChannel)),
messagesCleared: entry(deleted.size.toString(), deleted.size.toString())
},
hidden: {
guild: interaction.guild!.id
}
};
log(data);
let out = "";
deleted.reverse().forEach((message) => {
if (!message) {
out += "Unknown message\n\n"
} else {
const author = message.author ?? { username: "Unknown", discriminator: "0000", id: "Unknown" };
out += `${author.username}#${author.discriminator} (${author.id}) [${new Date(
message.createdTimestamp
).toISOString()}]\n`;
if (message.content) {
const lines = message.content.split("\n");
lines.forEach((line) => {
out += `> ${line}\n`;
});
}
if (message.attachments.size > 0) {
message.attachments.forEach((attachment) => {
out += `Attachment > ${attachment.url}\n`;
});
}
out += "\n\n";
}
});
const attachmentObject = {
attachment: Buffer.from(out),
name: `purge-${channel.id}-${Date.now()}.txt`,
description: "Purge log"
};
const m = (await interaction.editReply({
embeds: [
new EmojiEmbed()
.setEmoji("CHANNEL.PURGE.GREEN")
.setTitle("Purge")
.setDescription("Messages cleared")
.setStatus("Success")
],
components: [
new Discord.ActionRowBuilder<ButtonBuilder>().addComponents([
new Discord.ButtonBuilder()
.setCustomId("download")
.setLabel("Download transcript")
.setStyle(ButtonStyle.Success)
.setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id"))
])
]
})) as Discord.Message;
let component;
try {
component = await m.awaitMessageComponent({
filter: (m) => m.user.id === interaction.user.id,
time: 300000
});
} catch {
return;
}
if (component.customId === "download") {
interaction.editReply({
embeds: [
new EmojiEmbed()
.setEmoji("CHANNEL.PURGE.GREEN")
.setTitle("Purge")
.setDescription("Transcript uploaded above")
.setStatus("Success")
],
components: [],
files: [attachmentObject]
});
} else {
interaction.editReply({
embeds: [
new EmojiEmbed()
.setEmoji("CHANNEL.PURGE.GREEN")
.setTitle("Purge")
.setDescription("Messages cleared")
.setStatus("Success")
],
components: []
});
}
}
const check = async (_interaction: MessageContextMenuCommandInteraction) => {
return true;
}
export { command, callback, check }

@ -0,0 +1,18 @@
import { ContextMenuCommandBuilder, GuildMember, UserContextMenuCommandInteraction } from "discord.js";
import { userAbout } from "../../commands/user/about.js";
const command = new ContextMenuCommandBuilder()
.setName("User info")
const callback = async (interaction: UserContextMenuCommandInteraction) => {
const guild = interaction.guild!
let member = interaction.targetMember
if (!member) member = await guild.members.fetch(interaction.targetId)
await userAbout(guild, member as GuildMember, interaction)
}
const check = async (_interaction: UserContextMenuCommandInteraction) => {
return true;
}
export { command, callback, check }

@ -3,71 +3,15 @@ import verify from "../reflex/verify.js";
import create from "../actions/tickets/create.js"; import create from "../actions/tickets/create.js";
import close from "../actions/tickets/delete.js"; import close from "../actions/tickets/delete.js";
import createTranscript from "../premium/createTranscript.js"; import createTranscript from "../premium/createTranscript.js";
import Fuse from "fuse.js";
import { autocomplete as tagAutocomplete } from "../commands/tag.js"; import type { Interaction, MessageComponentInteraction } from "discord.js";
import type { AutocompleteInteraction, Interaction, MessageComponentInteraction } from "discord.js";
import type { NucleusClient } from "../utils/client.js"; import type { NucleusClient } from "../utils/client.js";
export const event = "interactionCreate"; export const event = "interactionCreate";
function getAutocomplete(typed: string, options: string[]): { name: string; value: string }[] {
options = options.filter((option) => option.length <= 100); // thanks discord. 6000 character limit on slash command inputs but only 100 for autocomplete.
if (!typed)
return options
.slice(0, 25)
.sort()
.map((option) => ({ name: option, value: option }));
const fuse = new Fuse(options, {
useExtendedSearch: true,
findAllMatches: true,
minMatchCharLength: 0
}).search(typed);
return fuse.slice(0, 25).map((option) => ({ name: option.item, value: option.item }));
}
function generateStatsChannelAutocomplete(typed: string) {
const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"];
const autocompletions = [];
const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/);
if (beforeLastOpenBracket !== null) {
for (const replacement of validReplacements) {
autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`);
}
} else {
for (const replacement of validReplacements) {
autocompletions.push(`${typed} {${replacement}}`);
}
}
return getAutocomplete(typed, autocompletions);
}
function generateWelcomeMessageAutocomplete(typed: string) {
const validReplacements = [
"serverName",
"memberCount",
"memberCount:bots",
"memberCount:humans",
"member:mention",
"member:name"
];
const autocompletions = [];
const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/);
if (beforeLastOpenBracket !== null) {
for (const replacement of validReplacements) {
autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`);
}
} else {
for (const replacement of validReplacements) {
autocompletions.push(`${typed} {${replacement}}`);
}
}
return getAutocomplete(typed, autocompletions);
}
async function interactionCreate(interaction: Interaction) { async function interactionCreate(interaction: Interaction) {
if ( if (interaction.isButton()) {
interaction.type === "MESSAGE_COMPONENT" &&
(interaction as MessageComponentInteraction).componentType === "BUTTON"
) {
const int = interaction as MessageComponentInteraction; const int = interaction as MessageComponentInteraction;
switch (int.customId) { switch (int.customId) {
case "rolemenu": { case "rolemenu": {
@ -86,19 +30,19 @@ async function interactionCreate(interaction: Interaction) {
return createTranscript(int); return createTranscript(int);
} }
} }
} else if (interaction.type === "APPLICATION_COMMAND_AUTOCOMPLETE") { // } else if (interaction.type === "APPLICATION_COMMAND_AUTOCOMPLETE") {
const int = interaction as AutocompleteInteraction; // const int = interaction as AutocompleteInteraction;
switch (`${int.commandName} ${int.options.getSubcommandGroup(false)} ${int.options.getSubcommand(false)}`) { // switch (`${int.commandName} ${int.options.getSubcommandGroup(false)} ${int.options.getSubcommand(false)}`) {
case "tag null null": { // case "tag null null": {
return int.respond(getAutocomplete(int.options.getString("tag") ?? "", await tagAutocomplete(int))); // return int.respond(getAutocomplete(int.options.getString("tag") ?? "", await tagAutocomplete(int)));
} // }
case "settings null stats": { // case "settings null stats": {
return int.respond(generateStatsChannelAutocomplete(int.options.getString("name") ?? "")); // return int.respond(generateStatsChannelAutocomplete(int.options.getString("name") ?? ""));
} // }
case "settings null welcome": { // case "settings null welcome": {
return int.respond(generateWelcomeMessageAutocomplete(int.options.getString("message") ?? "")); // return int.respond(generateWelcomeMessageAutocomplete(int.options.getString("message") ?? ""));
} // }
} // }
} }
} }

@ -12,10 +12,9 @@ interface PropSchema {
export async function callback(client: NucleusClient, member?: GuildMember, guild?: Guild, user?: User) { export async function callback(client: NucleusClient, member?: GuildMember, guild?: Guild, user?: User) {
if (!member && !guild) return; if (!member && !guild) return;
guild = await client.guilds.fetch(member ? member.guild.id : guild!.id); guild = await client.guilds.fetch(member ? member.guild.id : guild!.id);
if (!guild) return;
user = user ?? member!.user; user = user ?? member!.user;
const config = await client.database.guilds.read(guild.id); const config = await client.database.guilds.read(guild.id);
Object.entries(config.getKey("stats")).forEach(async ([channel, props]) => { Object.entries(config.stats).forEach(async ([channel, props]) => {
if ((props as PropSchema).enabled) { if ((props as PropSchema).enabled) {
let string = (props as PropSchema).name; let string = (props as PropSchema).name;
if (!string) return; if (!string) return;
@ -27,13 +26,13 @@ export async function callback(client: NucleusClient, member?: GuildMember, guil
fetchedChannel = null; fetchedChannel = null;
} }
if (!fetchedChannel) { if (!fetchedChannel) {
const deleted = config.getKey("stats")[channel]; 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( return singleNotify(
"statsChannelDeleted", "statsChannelDeleted",
guild!.id, guild!.id,
"One or more of your stats channels have been deleted. Please use `/settings stats` if you wish to add the channel again.\n" + "One or more of your stats channels have been deleted. Please use `/settings stats` if you wish to add the channel again.\n" +
`The channels name was: ${deleted.name}`, `The channels name was: ${deleted!.name}`,
"Critical" "Critical"
); );
} }

@ -1,11 +1,10 @@
import Discord, { Client, Interaction } from 'discord.js'; import Discord, { Client, Interaction, AutocompleteInteraction } from 'discord.js';
import { Logger } from "../utils/log.js"; import { Logger } from "../utils/log.js";
import Memory from "../utils/memory.js"; import Memory from "../utils/memory.js";
import type { VerifySchema } from "../reflex/verify.js"; import type { VerifySchema } from "../reflex/verify.js";
import { Guilds, History, ModNotes, Premium } from "../utils/database.js"; import { Guilds, History, ModNotes, Premium } from "../utils/database.js";
import EventScheduler from "../utils/eventScheduler.js"; import EventScheduler from "../utils/eventScheduler.js";
import type { RoleMenuSchema } from "../actions/roleMenu.js"; import type { RoleMenuSchema } from "../actions/roleMenu.js";
// @ts-expect-error
import config from "../config/main.json" assert { type: "json" }; import config from "../config/main.json" assert { type: "json" };
@ -28,9 +27,9 @@ class NucleusClient extends Client {
((builder: Discord.SlashCommandBuilder) => Discord.SlashCommandBuilder) | ((builder: Discord.SlashCommandBuilder) => Discord.SlashCommandBuilder) |
Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder), Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder),
callback: (interaction: Interaction) => Promise<void>, callback: (interaction: Interaction) => Promise<void>,
check: (interaction: Interaction) => Promise<boolean> | boolean check: (interaction: Interaction) => Promise<boolean> | boolean,
autocomplete: (interaction: AutocompleteInteraction) => Promise<string[]>
}> = {}; }> = {};
// commands: Discord.Collection<string, [Function, Function]> = new Discord.Collection();
constructor(database: typeof NucleusClient.prototype.database) { constructor(database: typeof NucleusClient.prototype.database) {
super({ intents: 32767 }); super({ intents: 32767 });

@ -1,5 +1,4 @@
import Discord, { Interaction, SlashCommandBuilder, ApplicationCommandType } from 'discord.js'; import Discord, { Interaction, SlashCommandBuilder, ApplicationCommandType } from 'discord.js';
// @ts-expect-error
import config from "../../config/main.json" assert { type: "json" }; import config from "../../config/main.json" assert { type: "json" };
import client from "../client.js"; import client from "../client.js";
import fs from "fs"; import fs from "fs";
@ -140,9 +139,20 @@ async function registerCommandHandler() {
const commandName = "contextCommands/message/" + interaction.commandName; const commandName = "contextCommands/message/" + interaction.commandName;
execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction) execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction)
return; return;
} } else if (interaction.isAutocomplete()) {
if (!interaction.isChatInputCommand()) return; const commandName = interaction.commandName;
const subcommandGroupName = interaction.options.getSubcommandGroup(false);
const subcommandName = interaction.options.getSubcommand(false);
const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : "");
const choices = await client.commands[fullCommandName]?.autocomplete(interaction);
const formatted = (choices ?? []).map(choice => {
return { name: choice, value: choice }
})
interaction.respond(formatted)
} else if (interaction.isChatInputCommand()) {
const commandName = interaction.commandName; const commandName = interaction.commandName;
const subcommandGroupName = interaction.options.getSubcommandGroup(false); const subcommandGroupName = interaction.options.getSubcommandGroup(false);
const subcommandName = interaction.options.getSubcommand(false); const subcommandName = interaction.options.getSubcommand(false);
@ -153,6 +163,7 @@ async function registerCommandHandler() {
const callback = command?.callback; const callback = command?.callback;
const check = command?.check; const check = command?.check;
execute(check, callback, interaction); execute(check, callback, interaction);
}
}); });
} }

@ -1,6 +1,5 @@
import type { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; import type { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
import type { SlashCommandBuilder } from "discord.js"; import type { SlashCommandBuilder } from "discord.js";
// @ts-expect-error
import config from "../../config/main.json" assert { type: "json" }; import config from "../../config/main.json" assert { type: "json" };
import getSubcommandsInFolder from "./getFilesInFolder.js"; import getSubcommandsInFolder from "./getFilesInFolder.js";
import client from "../client.js"; import client from "../client.js";

@ -18,6 +18,7 @@ interface CustomBoolean<T> {
title: string; title: string;
disabled: boolean; disabled: boolean;
value: string | null; value: string | null;
notValue: string | null;
emoji: string | undefined; emoji: string | undefined;
active: boolean; active: boolean;
onClick: () => Promise<T>; onClick: () => Promise<T>;
@ -68,6 +69,7 @@ class confirmationMessage {
disabled: boolean, disabled: boolean,
callback: (() => Promise<unknown>) | null = async () => null, callback: (() => Promise<unknown>) | null = async () => null,
callbackClicked: string | null, callbackClicked: string | null,
callbackNotClicked: string | null,
emoji?: string, emoji?: string,
initial?: boolean initial?: boolean
) { ) {
@ -75,6 +77,7 @@ class confirmationMessage {
title: title, title: title,
disabled: disabled, disabled: disabled,
value: callbackClicked, value: callbackClicked,
notValue: callbackNotClicked,
emoji: emoji, emoji: emoji,
active: initial ?? false, active: initial ?? false,
onClick: callback ?? (async () => null), onClick: callback ?? (async () => null),
@ -145,10 +148,12 @@ class confirmationMessage {
"\n\n" + "\n\n" +
Object.values(this.customButtons) Object.values(this.customButtons)
.map((v) => { .map((v) => {
if (v.value === null) return ""; if (v.active) {
return v.active ? `*${v.value}*\n` : ""; return v.value ? `*${v.value}*\n` : "";
}) } else {
.join("") return v.notValue ? `*${v.notValue}*\n` : "";
}
}).join("")
) )
.setStatus(this.color) .setStatus(this.color)
], ],
@ -163,7 +168,8 @@ class confirmationMessage {
} else { } else {
m = (await this.interaction.reply(object)) as unknown as Message; m = (await this.interaction.reply(object)) as unknown as Message;
} }
} catch { } catch (e) {
console.log(e);
cancelled = true; cancelled = true;
continue; continue;
} }

@ -1,6 +1,5 @@
import type Discord from "discord.js"; import type Discord from "discord.js";
import { Collection, MongoClient } from "mongodb"; import { Collection, MongoClient } from "mongodb";
// @ts-expect-error
import config from "../config/main.json" assert { type: "json" }; import config from "../config/main.json" assert { type: "json" };
const mongoClient = new MongoClient(config.mongoUrl); const mongoClient = new MongoClient(config.mongoUrl);
@ -17,7 +16,6 @@ export class Guilds {
} }
async setup(): Promise<Guilds> { async setup(): Promise<Guilds> {
// @ts-expect-error
this.defaultData = (await import("../config/default.json", { assert: { type: "json" } })) this.defaultData = (await import("../config/default.json", { assert: { type: "json" } }))
.default as unknown as GuildConfig; .default as unknown as GuildConfig;
return this; return this;

@ -9,3 +9,29 @@ export const LinkWarningFooter = {
text: "The button below will take you to a website set by the server moderators. Do not enter any passwords unless it is from a trusted website.", text: "The button below will take you to a website set by the server moderators. Do not enter any passwords unless it is from a trusted website.",
iconURL: "https://cdn.discordapp.com/emojis/952295894370369587.webp?size=128&quality=lossless" iconURL: "https://cdn.discordapp.com/emojis/952295894370369587.webp?size=128&quality=lossless"
} }
class Embed {
embed: EmojiEmbed = new EmojiEmbed();
title: string = "";
description = "";
pageId = 0;
setEmbed(embed: EmojiEmbed) {
this.embed = embed;
return this;
}
setTitle(title: string) {
this.title = title;
return this;
}
setDescription(description: string) {
this.description = description;
return this;
}
setPageId(pageId: number) {
this.pageId = pageId;
return this;
}
}
export { Embed };

@ -2,7 +2,6 @@ import { Agenda } from "@hokify/agenda";
import client from "./client.js"; import client from "./client.js";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
// @ts-expect-error
import config from "../config/main.json" assert { type: "json" }; import config from "../config/main.json" assert { type: "json" };
class EventScheduler { class EventScheduler {

@ -1,4 +1,3 @@
// @ts-expect-error
import emojis from "../config/emojis.json" assert { type: "json" }; import emojis from "../config/emojis.json" assert { type: "json" };
interface EmojisIndex { interface EmojisIndex {

@ -6,6 +6,7 @@ interface GuildData {
filters: GuildConfig["filters"]; filters: GuildConfig["filters"];
logging: GuildConfig["logging"]; logging: GuildConfig["logging"];
tickets: GuildConfig["tickets"]; tickets: GuildConfig["tickets"];
tags: GuildConfig["tags"];
} }
class Memory { class Memory {
@ -20,7 +21,7 @@ class Memory {
} }
} }
}, 1000 * 60 * 30); }, 1000 * 60 * 30);
} };
async readGuildInfo(guild: string): Promise<GuildData> { async readGuildInfo(guild: string): Promise<GuildData> {
if (!this.memory.has(guild)) { if (!this.memory.has(guild)) {
@ -29,10 +30,15 @@ class Memory {
lastUpdated: Date.now(), lastUpdated: Date.now(),
filters: guildData.filters, filters: guildData.filters,
logging: guildData.logging, logging: guildData.logging,
tickets: guildData.tickets tickets: guildData.tickets,
tags: guildData.tags
}); });
} }
return this.memory.get(guild)!; return this.memory.get(guild)!;
};
async forceUpdate(guild: string) {
if (this.memory.has(guild)) this.memory.delete(guild);
} }
} }

@ -0,0 +1,18 @@
import Fuse from "fuse.js";
function getResults(typed: string, options: string[]): string[] {
options = options.filter((option) => option.length <= 100); // thanks discord. 6000 character limit on slash command inputs but only 100 for autocomplete.
if (!typed)
return options
.slice(0, 25)
.sort()
// @ts-expect-error
const fuse = new Fuse(options, {
useExtendedSearch: true,
findAllMatches: true,
minMatchCharLength: typed.length > 3 ? 3 : typed.length,
}).search(typed);
return fuse.slice(0, 25).map((option: {item: string }) => option.item );
}
export { getResults }
Loading…
Cancel
Save