diff --git a/ClicksMigratingProblems/index.js b/ClicksMigratingProblems/index.js index e5b302c..34fc2c5 100644 --- a/ClicksMigratingProblems/index.js +++ b/ClicksMigratingProblems/index.js @@ -48,7 +48,11 @@ for (const file of files) { }, invite: { enabled: data.invite ? data.invite.enabled : false, - channels: data.invite ? data.invite.whitelist.channels.map((channel) => channel.toString()) : [] + allowed: { + channels: data.invite ? data.invite.whitelist.channels.map((channel) => channel.toString()) : [], + users: [], + roles: [] + } }, pings: { mass: 5, diff --git a/events2do b/events2do new file mode 100644 index 0000000..15f4cbd --- /dev/null +++ b/events2do @@ -0,0 +1,30 @@ +-rw-r--r-- 1 pineapplefan pineapplefan 2735 Dec 29 11:03 channelCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 3661 Dec 29 11:04 channelDelete.ts +-rw-r--r-- 1 pineapplefan pineapplefan 7430 Nov 15 20:45 channelUpdate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 960 Nov 15 20:39 commandError.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1097 Aug 8 21:15 emojiCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1184 Aug 8 21:15 emojiDelete.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1183 Aug 8 21:15 emojiUpdate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1849 Dec 29 11:04 guildBanAdd.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1558 Dec 29 11:04 guildBanRemove.ts +-rw-r--r-- 1 pineapplefan pineapplefan 267 Dec 29 11:04 guildCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 5553 Dec 29 11:04 guildMemberUpdate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 3863 Jan 6 17:42 guildUpdate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1710 Jan 6 18:38 interactionCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1482 Dec 29 11:04 inviteCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1471 Dec 29 11:04 inviteDelete.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1387 Dec 29 11:04 memberJoin.ts +-rw-r--r-- 1 pineapplefan pineapplefan 3260 Jan 2 21:41 memberLeave.ts +-rw-r--r-- 1 pineapplefan pineapplefan 14919 Dec 29 11:04 messageCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 4907 Dec 29 11:04 ! messageDelete.ts +-rw-r--r-- 1 pineapplefan pineapplefan 4907 Dec 29 11:04 ? messageEdit.ts: Check message publishing +-rw-r--r-- 1 pineapplefan pineapplefan 1268 Dec 29 11:04 roleCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1915 Dec 29 11:04 roleDelete.ts +-rw-r--r-- 1 pineapplefan pineapplefan 2562 Dec 29 11:04 roleUpdate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1262 Dec 29 11:04 stickerCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1349 Dec 29 11:04 stickerDelete.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1272 Dec 29 11:04 stickerUpdate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 1967 Dec 29 11:04 threadCreate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 2140 Dec 29 11:04 threadDelete.ts +-rw-r--r-- 1 pineapplefan pineapplefan 2464 Dec 29 11:04 threadUpdate.ts +-rw-r--r-- 1 pineapplefan pineapplefan 6352 Dec 29 11:04 webhookUpdate.ts \ No newline at end of file diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts index 48ca49c..1c493bb 100644 --- a/src/actions/roleMenu.ts +++ b/src/actions/roleMenu.ts @@ -1,11 +1,10 @@ -import { Interaction, ButtonBuilder, CommandInteraction, Role, ButtonStyle } from "discord.js"; +import { Interaction, ButtonBuilder, CommandInteraction, Role, ButtonStyle, ButtonInteraction } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import { ActionRowBuilder, SelectMenuBuilder } from "discord.js"; import getEmojiByName from "../utils/getEmojiByName.js"; import client from "../utils/client.js"; import { LoadingEmbed } from "../utils/defaultEmbeds.js"; import type { GuildConfig } from "../utils/database.js"; -import type { ButtonComponent } from "@discordjs/builders"; export interface RoleMenuSchema { guild: string; @@ -17,7 +16,7 @@ export interface RoleMenuSchema { interaction: Interaction; } -export async function callback(interaction: CommandInteraction) { +export async function callback(interaction: CommandInteraction | ButtonInteraction) { if(!interaction.guild) return interaction.reply({ content: "This command can only be used in a server.", ephemeral: true }); if(!interaction.member) return interaction.reply({ content: "You must be in a server to use this command.", ephemeral: true }); const config = await client.database.guilds.read(interaction.guild.id); diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts index e1821ef..595a5b3 100644 --- a/src/actions/tickets/create.ts +++ b/src/actions/tickets/create.ts @@ -1,4 +1,4 @@ -import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js"; +import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, ButtonInteraction } from "discord.js"; import { tickets, toHexArray } from "../../utils/calculate.js"; import client from "../../utils/client.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -9,7 +9,7 @@ function capitalize(s: string) { return s.length < 3 ? s.toUpperCase() : s[0].toUpperCase() + s.slice(1).toLowerCase(); } -export default async function (interaction: CommandInteraction) { +export default async function (interaction: CommandInteraction | ButtonInteraction) { if (!interaction.guild) return; const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger; diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index 33860b7..cab38ec 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -1,9 +1,9 @@ -import Discord, { ButtonBuilder, ActionRowBuilder, ButtonStyle } from "discord.js"; +import Discord, { ButtonBuilder, ActionRowBuilder, ButtonStyle, ButtonInteraction } from "discord.js"; import client from "../../utils/client.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; -export default async function (interaction: Discord.CommandInteraction) { +export default async function (interaction: Discord.CommandInteraction | ButtonInteraction) { if (!interaction.guild) return; const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger; diff --git a/src/commands/help.ts b/src/commands/help.ts index e85cf6b..c9bba7d 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,4 +1,4 @@ -import type { CommandInteraction } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js"; import { SlashCommandBuilder } from "@discordjs/builders"; const command = new SlashCommandBuilder() @@ -6,7 +6,12 @@ const command = new SlashCommandBuilder() .setDescription("Shows help for commands"); const callback = async (interaction: CommandInteraction): Promise => { - interaction.reply("hel p D:"); // TODO: FINISH THIS FOR RELEASE + interaction.reply({components: [new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Verify") + .setStyle(ButtonStyle.Primary) + .setCustomId("verifybutton") + )]}); // TODO: FINISH THIS FOR RELEASE }; const check = (_interaction: CommandInteraction) => { diff --git a/src/commands/mod/_meta.ts b/src/commands/mod/_meta.ts index 987a1c1..af8006c 100644 --- a/src/commands/mod/_meta.ts +++ b/src/commands/mod/_meta.ts @@ -3,6 +3,6 @@ import { command } from "../../utils/commandRegistration/slashCommandBuilder.js" const name = "mod"; const description = "Perform moderator actions"; -const subcommand = await command(name, description, `mod`) +const subcommand = await command(name, description, `mod`); export { name, description, subcommand as command }; diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 7346bcc..ba60d1e 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -74,6 +74,7 @@ const callback = async (interaction: CommandInteraction): Promise => { const config = await client.database.guilds.read(interaction.guild.id); try { if (notify) { + if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") } const messageData: { embeds: EmojiEmbed[]; components: ActionRowBuilder[]; @@ -83,7 +84,8 @@ const callback = async (interaction: CommandInteraction): Promise => { .setEmoji("PUNISH.BAN.RED") .setTitle("Banned") .setDescription( - `You have been banned in ${interaction.guild.name}` + (reason ? ` for:\n> ${reason}` : ".") + `You have been banned in ${interaction.guild.name}` + + (reason ? ` for:\n${reason}` : ".\n*No reason was provided.*") ) .setStatus("Danger") ], diff --git a/src/commands/mod/info.ts b/src/commands/mod/info.ts index 6b5c81e..fac87a0 100644 --- a/src/commands/mod/info.ts +++ b/src/commands/mod/info.ts @@ -23,6 +23,7 @@ import pageIndicator from "../../utils/createPageIndicator.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("info") + // .setNameLocalizations({"ru": "about", "zh-CN": "history", "zh-TW": "notes", "pt-BR": "flags"}) .setDescription("Shows moderator information about a user") .addUserOption((option) => option.setName("user").setDescription("The user to get information about").setRequired(true) diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index dd71892..88b6e14 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -75,6 +75,7 @@ const callback = async (interaction: CommandInteraction): Promise => { const config = await client.database.guilds.read(interaction.guild.id); try { if (notify) { + if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") } const messageData: { embeds: EmojiEmbed[]; components: ActionRowBuilder[]; @@ -85,7 +86,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setTitle("Kicked") .setDescription( `You have been kicked from ${interaction.guild.name}` + - (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided.*") + (reason ? ` for:\n${reason}` : ".\n*No reason was provided.*") ) .setStatus("Danger") ], diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index 05a9ec2..d68272b 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -13,6 +13,7 @@ import { areTicketsEnabled, create } from "../../actions/createModActionTicket.j const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("mute") + // .setNameLocalizations({"ru": "silence"}) .setDescription("Mutes a member, stopping them from talking in the server") .addUserOption((option) => option.setName("user").setDescription("The user to mute").setRequired(true)) .addIntegerOption((option) => @@ -234,6 +235,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let dmMessage; try { if (notify) { + if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") } const messageData: { embeds: EmojiEmbed[]; components: ActionRowBuilder[]; @@ -244,7 +246,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setTitle("Muted") .setDescription( `You have been muted in ${interaction.guild.name}` + - (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided*") + "\n\n" + + (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") + "\n\n" + `You will be unmuted at: at ` + ` ()` + "\n\n" + diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index 0975375..81935a2 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -1,4 +1,4 @@ -import { CommandInteraction, GuildMember } from "discord.js"; +import type { CommandInteraction, GuildMember } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -8,6 +8,7 @@ import client from "../../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("nick") + // .setNameLocalizations({"ru": "name", "zh-CN": "nickname"}) .setDescription("Changes a users nickname") .addUserOption((option) => option.setName("user").setDescription("The user to change").setRequired(true)) .addStringOption((option) => diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 6087890..3020eb8 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -29,11 +29,7 @@ const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; const user = (interaction.options.getMember("user") as GuildMember | null); const channel = interaction.channel as GuildChannel; - if ( - !["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes( - channel.type.toString() - ) - ) { + if (channel.isTextBased()) { return await interaction.reply({ embeds: [ new EmojiEmbed() diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index 390baa5..d920bb0 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -84,6 +84,7 @@ const callback = async (interaction: CommandInteraction): Promise => { const config = await client.database.guilds.read(interaction.guild.id); try { if (notify) { + if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") } const messageData: { embeds: EmojiEmbed[]; components: ActionRowBuilder[]; @@ -94,7 +95,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setTitle("Warned") .setDescription( `You have been warned in ${interaction.guild.name}` + - (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided*") + + (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") + "\n\n" + (createAppealTicket ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>` diff --git a/src/commands/nucleus/_meta.ts b/src/commands/nucleus/_meta.ts index d66b5d2..521b338 100644 --- a/src/commands/nucleus/_meta.ts +++ b/src/commands/nucleus/_meta.ts @@ -5,4 +5,6 @@ const description = "Commands relating to Nucleus itself"; const subcommand = await command(name, description, `nucleus`) -export { name, description, subcommand as command }; +const allowedInDMs = true; + +export { name, description, subcommand as command, allowedInDMs }; diff --git a/src/commands/nucleus/guide.ts b/src/commands/nucleus/guide.ts index ffc441a..d3370ba 100644 --- a/src/commands/nucleus/guide.ts +++ b/src/commands/nucleus/guide.ts @@ -1,11 +1,12 @@ -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { CommandInteraction } from 'discord.js'; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import guide from "../../reflex/guide.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("guide").setDescription("Shows the welcome guide for the bot"); -const callback = async (interaction) => { - guide(interaction.guild, interaction); +const callback = async (interaction: CommandInteraction) => { + guide(interaction.guild!, interaction); }; const check = () => { diff --git a/src/commands/nucleus/invite.ts b/src/commands/nucleus/invite.ts index 7c36d62..fd65e51 100644 --- a/src/commands/nucleus/invite.ts +++ b/src/commands/nucleus/invite.ts @@ -1,5 +1,5 @@ import { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -16,12 +16,12 @@ const callback = async (interaction: CommandInteraction): Promise => { .setStatus("Danger") ], components: [ - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents([ new ButtonBuilder() .setLabel("Invite") .setStyle(ButtonStyle.Link) .setURL( - `https://discord.com/api/oauth2/authorize?client_id=${client.user.id}&permissions=295157886134&scope=bot%20applications.commands` + `https://discord.com/api/oauth2/authorize?client_id=${client.user!.id}&permissions=295157886134&scope=bot%20applications.commands` ) ]) ], diff --git a/src/commands/nucleus/ping.ts b/src/commands/nucleus/ping.ts index 59b6393..3cbd049 100644 --- a/src/commands/nucleus/ping.ts +++ b/src/commands/nucleus/ping.ts @@ -1,6 +1,6 @@ import { LoadingEmbed } from "./../../utils/defaultEmbeds.js"; -import { CommandInteraction } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { CommandInteraction } from "discord.js"; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -19,8 +19,8 @@ const callback = async (interaction: CommandInteraction): Promise => { .setTitle("Ping") .setDescription( `**Ping:** \`${ping}ms\`\n` + - `**To Discord:** \`${client.ws.ping}ms\`\n` + - `**From Expected:** \`±${Math.abs(ping / 2 - client.ws.ping)}ms\`` + `**To Discord:** \`${client.ws.ping}ms\`\n` + + `**From Expected:** \`±${Math.abs(ping / 2 - client.ws.ping)}ms\`` ) .setEmoji("CHANNEL.SLOWMODE.OFF") .setStatus("Danger") diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts index a67cd36..d8b2807 100644 --- a/src/commands/nucleus/stats.ts +++ b/src/commands/nucleus/stats.ts @@ -1,5 +1,5 @@ -import { CommandInteraction } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { CommandInteraction } from "discord.js"; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts index 49c80fb..a75e8a0 100644 --- a/src/commands/nucleus/suggest.ts +++ b/src/commands/nucleus/suggest.ts @@ -1,4 +1,5 @@ -import Discord, { CommandInteraction } from "discord.js"; +import type { CommandInteraction } from "discord.js"; +import type Discord from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -13,8 +14,9 @@ const command = (builder: SlashCommandSubcommandBuilder) => ); const callback = async (interaction: CommandInteraction): Promise => { + await interaction.guild?.members.fetch(interaction.member!.user.id) const { renderUser } = client.logger; - const suggestion = interaction.options.getString("suggestion"); + const suggestion = interaction.options.get("suggestion")?.value as string; const confirmation = await new confirmationMessage(interaction) .setEmoji("ICONS.OPP.ADD") .setTitle("Suggest") @@ -32,7 +34,7 @@ const callback = async (interaction: CommandInteraction): Promise => { new EmojiEmbed() .setTitle("Suggestion") .setDescription( - `**From:** ${renderUser(interaction.member.user)}\n**Suggestion:**\n> ${suggestion}` + `**From:** ${renderUser(interaction.member!.user as Discord.User)}\n**Suggestion:**\n> ${suggestion}` ) .setStatus("Danger") .setEmoji("NUCLEUS.LOGO") diff --git a/src/commands/server/about.ts b/src/commands/server/about.ts index b895768..e5bea60 100644 --- a/src/commands/server/about.ts +++ b/src/commands/server/about.ts @@ -1,4 +1,4 @@ -import type { CommandInteraction } from "discord.js"; +import { CommandInteraction, GuildMFALevel } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; @@ -8,6 +8,28 @@ import client from "../../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("about").setDescription("Shows info about the server"); + +const verificationTypes = { + 0: "None - Unrestricted", + 1: "Low - Must have a verified email", + 2: "Medium - Must be registered for 5 minutes", + 3: "High - Must be a member for 10 minutes", + 4: "Highest - Must have a verified phone" +} + +const premiumTiers = { + 0: "None", + 1: "Tier 1", + 2: "Tier 2", + 3: "Tier 3" +} + +const filterLevels = { + 0: "Disabled", + 1: "Members without roles", + 2: "All members" +} + const callback = async (interaction: CommandInteraction): Promise => { const guild = interaction.guild!; const { renderUser, renderDelta } = client.logger; @@ -27,28 +49,26 @@ const callback = async (interaction: CommandInteraction): Promise => { `${guild.emojis.cache.size}` + (guild.emojis.cache.size > 1 ? `\n> ${guild.emojis.cache - .first(10) - .map((emoji) => `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`) - .join(" ")}` + - (guild.emojis.cache.size > 10 ? ` and ${guild.emojis.cache.size - 10} more` : "") + .first(10) + .map((emoji) => `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`) + .join(" ")}` + + (guild.emojis.cache.size > 10 ? ` and ${guild.emojis.cache.size - 10} more` : "") : ""), icon: `[Discord](${guild.iconURL()})`, "2 factor authentication": `${ - guild.mfaLevel === "NONE" + guild.mfaLevel === GuildMFALevel.None ? `${getEmojiByName("CONTROL.CROSS")} No` : `${getEmojiByName("CONTROL.TICK")} Yes` }`, - "verification level": `${toCapitals(guild.verificationLevel)}`, - "explicit content filter": `${toCapitals( - guild.explicitContentFilter.toString().replace(/_/, " ") - )}`, - "nitro boost level": `${guild.premiumTier !== "NONE" ? guild.premiumTier.toString()[-1] : "0"}`, + "verification level": `${toCapitals(verificationTypes[guild.verificationLevel])}`, + "explicit content filter": `${filterLevels[guild.explicitContentFilter]}`, + "nitro boost level": `${premiumTiers[guild.premiumTier]}`, channels: `${guild.channels.cache.size}`, roles: `${guild.roles.cache.size}`, members: `${guild.memberCount}` }) ) - .setThumbnail(guild.iconURL({ dynamic: true })) + .setThumbnail(guild.iconURL()) ], ephemeral: true }); diff --git a/src/commands/settings/_meta.ts b/src/commands/settings/_meta.ts index 76e570d..666dbf4 100644 --- a/src/commands/settings/_meta.ts +++ b/src/commands/settings/_meta.ts @@ -4,6 +4,8 @@ const name = "settings"; const description = "Change bot settings"; -const subcommand = await command(name, description, "settings") +const subcommand = await command(name, description, "settings", undefined, undefined, undefined, [ + "ManageGuild" +]) export { name, description, subcommand as command}; diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index f19998a..a768cb8 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -224,6 +224,16 @@ const check = (interaction: CommandInteraction) => { return true; }; +const autocomplete = async (interaction: AutocompleteInteraction): Promise => { + if (!interaction.guild) return []; + const prompt = interaction.options.getString("tag"); + // generateStatsChannelAutocomplete(int.options.getString("name") ?? "") + const results = generateStatsChannelAutocomplete(prompt ?? ""); + return results; +}; + + + export { command }; export { callback }; export { check }; diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index 5284f8a..7f02cd7 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -7,7 +7,8 @@ import Discord, { ButtonBuilder, MessageComponentInteraction, Role, - ButtonStyle + ButtonStyle, + AutocompleteInteraction } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -204,7 +205,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setEmoji("CHANNEL.TEXT.CREATE") ], components: [ - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents([ new ButtonBuilder() .setLabel(lastClicked == "clear-message" ? "Click again to confirm" : "Clear Message") .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) @@ -296,11 +297,42 @@ const callback = async (interaction: CommandInteraction): Promise => { const check = (interaction: CommandInteraction) => { const member = interaction.member as Discord.GuildMember; - if (!member.permissions.has("MANAGE_GUILD")) + if (!member.permissions.has("ManageGuild")) throw new Error("You must have the *Manage Server* permission to use this command"); return true; }; -export { command }; -export { callback }; -export { check }; +const autocomplete = async (interaction: AutocompleteInteraction): Promise => { + const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"] + if (!interaction.guild) return []; + const prompt = interaction.options.getString("message"); + const autocompletions = []; + if ( prompt === null ) { + for (const replacement of validReplacements) { + autocompletions.push(`{${replacement}}`); + }; + return autocompletions; + }; + const beforeLastOpenBracket = prompt.match(/(.*){[^{}]{0,15}$/); + const afterLastOpenBracket = prompt.match(/{[^{}]{0,15}$/); + if (beforeLastOpenBracket !== null) { + if (afterLastOpenBracket !== null) { + for (const replacement of validReplacements) { + if (replacement.startsWith(afterLastOpenBracket[0].slice(1))) { + autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`); + } + } + } else { + for (const replacement of validReplacements) { + autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`); + } + } + } else { + for (const replacement of validReplacements) { + autocompletions.push(`${prompt} {${replacement}}`); + } + } + return autocompletions; +}; + +export { command, callback, check, autocomplete }; diff --git a/src/commands/tag.ts b/src/commands/tag.ts index 859b7fc..a65947c 100644 --- a/src/commands/tag.ts +++ b/src/commands/tag.ts @@ -58,10 +58,8 @@ const check = () => { const autocomplete = async (interaction: AutocompleteInteraction): Promise => { if (!interaction.guild) return []; const prompt = interaction.options.getString("tag"); - console.log(prompt) const possible = Object.keys((await client.memory.readGuildInfo(interaction.guild.id)).tags); const results = getResults(prompt ?? "", possible); - console.log(results) return results; }; diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts index 72ad1eb..aa45690 100644 --- a/src/commands/user/about.ts +++ b/src/commands/user/about.ts @@ -79,7 +79,7 @@ async function userAbout(guild: Discord.Guild, member: Discord.GuildMember, inte Staff: "Discord Staff", VerifiedDeveloper: "Verified Bot Developer", ActiveDeveloper: "Active Developer", - Quarantined: "Quarantined [What does this mean?](https://support.discord.com/hc/en-us/articles/6461420677527)", + Quarantined: "Quarantined [[What does this mean?]](https://support.discord.com/hc/en-us/articles/6461420677527)", Spammer: "Likely Spammer" // CertifiedModerator // VerifiedBot diff --git a/src/config/default.json b/src/config/default.json index 824f12c..8e4197c 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -19,7 +19,11 @@ }, "invite": { "enabled": false, - "channels": [] + "allowed": { + "users": [], + "roles": [], + "channels": [] + } }, "pings": { "mass": 5, diff --git a/src/config/emojis.json b/src/config/emojis.json index 168d84b..cbf3dc3 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -41,7 +41,8 @@ "GUILD_STAGE_VOICE": "853668786842763294", "THREAD_CHANNEL": "990210005108158514", "THREAD_PIPE": "990213168183779348", - "RULES": "990213153080115250" + "RULES": "990213153080115250", + "FORUM": "1061706437526552716" } }, "CONTROL": { diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts index b3cba33..dda37af 100644 --- a/src/events/channelCreate.ts +++ b/src/events/channelCreate.ts @@ -1,47 +1,54 @@ -import type { GuildAuditLogsEntry } from "discord.js"; +import { AuditLogEvent, ChannelType, GuildAuditLogsEntry } from "discord.js"; import type { GuildBasedChannel } from "discord.js"; import type { NucleusClient } from "../utils/client.js"; export const event = "channelCreate"; export async function callback(client: NucleusClient, channel: GuildBasedChannel) { const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; - const auditLog = await getAuditLog(channel.guild, "CHANNEL_CREATE"); - const audit = auditLog.entries.filter((entry: GuildAuditLogsEntry) => entry.target!.id === channel.id).first(); - if (audit.executor.id === client.user.id) return; + const auditLog = (await getAuditLog(channel.guild, AuditLogEvent.ChannelCreate)) + .filter((entry: GuildAuditLogsEntry) => (entry.target as GuildBasedChannel)!.id === channel.id)[0]; + if (!auditLog) return; + if (auditLog.executor!.id === client.user!.id) return; let emoji; let readableType; let displayName; switch (channel.type) { - case "GUILD_TEXT": { + case ChannelType.GuildText: { emoji = "CHANNEL.TEXT.CREATE"; readableType = "Text"; displayName = "Text Channel"; break; } - case "GUILD_NEWS": { + case ChannelType.GuildAnnouncement: { emoji = "CHANNEL.TEXT.CREATE"; readableType = "Announcement"; displayName = "Announcement Channel"; break; } - case "GUILD_VOICE": { + case ChannelType.GuildVoice: { emoji = "CHANNEL.VOICE.CREATE"; readableType = "Voice"; displayName = "Voice Channel"; break; } - case "GUILD_STAGE_VOICE": { + case ChannelType.GuildStageVoice: { emoji = "CHANNEL.VOICE.CREATE"; readableType = "Stage"; displayName = "Stage Channel"; break; } - case "GUILD_CATEGORY": { + case ChannelType.GuildCategory: { emoji = "CHANNEL.CATEGORY.CREATE"; readableType = "Category"; displayName = "Category"; break; } + case ChannelType.GuildForum: { + emoji = "CHANNEL.TEXT.CREATE"; + readableType = "Forum"; + displayName = "Forum Channel"; + break; + } default: { emoji = "CHANNEL.TEXT.CREATE"; readableType = "Channel"; @@ -65,12 +72,12 @@ export async function callback(client: NucleusClient, channel: GuildBasedChannel channel.parent ? channel.parent.id : null, channel.parent ? channel.parent.name : "Uncategorised" ), - createdBy: entry(audit.executor.id, renderUser(audit.executor)), - created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp)) + createdBy: entry(auditLog.executor!.id, renderUser(auditLog.executor!)), + created: entry(channel.createdTimestamp, renderDelta(channel.createdTimestamp!)) }, hidden: { guild: channel.guild.id } }; log(data); -} +}; diff --git a/src/events/channelUpdate.ts b/src/events/channelUpdate.ts index a7af453..df212a2 100644 --- a/src/events/channelUpdate.ts +++ b/src/events/channelUpdate.ts @@ -5,6 +5,7 @@ export const event = "channelUpdate"; export async function callback(client, oc, nc) { const config = await client.memory.readGuildInfo(nc.guild.id); + return; const { getAuditLog, log, NucleusColors, entry, renderDelta, renderUser, renderChannel } = client.logger; if (nc.parent && nc.parent.id === config.tickets.category) return; diff --git a/src/events/guildUpdate.ts b/src/events/guildUpdate.ts index e463007..eefab4b 100644 --- a/src/events/guildUpdate.ts +++ b/src/events/guildUpdate.ts @@ -78,7 +78,7 @@ export async function callback(client: NucleusClient, before: Guild, after: Guil const data = { meta: { type: "guildUpdate", - displayName: "Guild Edited", + displayName: "Server Edited", calculateType: "guildUpdate", color: NucleusColors.yellow, emoji: "GUILD.YELLOW", diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 1796146..62e9609 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -4,7 +4,7 @@ import create from "../actions/tickets/create.js"; import close from "../actions/tickets/delete.js"; import createTranscript from "../premium/createTranscript.js"; -import type { Interaction, MessageComponentInteraction } from "discord.js"; +import type { Interaction } from "discord.js"; import type { NucleusClient } from "../utils/client.js"; export const event = "interactionCreate"; @@ -12,30 +12,16 @@ export const event = "interactionCreate"; async function interactionCreate(interaction: Interaction) { if (interaction.isButton()) { - const int = interaction as MessageComponentInteraction; - switch (int.customId) { - case "rolemenu": { - return await roleMenu(interaction); - } - case "verifybutton": { - return verify(int); - } - case "createticket": { - return create(interaction); - } - case "closeticket": { - return close(interaction); - } - case "createtranscript": { - return createTranscript(int); - } + switch (interaction.customId) { + case "rolemenu": { return await roleMenu(interaction); } + case "verifybutton": { return await verify(interaction); } + case "createticket": { return await create(interaction); } + case "closeticket": { return await close(interaction); } + case "createtranscript": { return await createTranscript(interaction); } } // } else if (interaction.type === "APPLICATION_COMMAND_AUTOCOMPLETE") { // const int = interaction as AutocompleteInteraction; // switch (`${int.commandName} ${int.options.getSubcommandGroup(false)} ${int.options.getSubcommand(false)}`) { - // case "tag null null": { - // return int.respond(getAutocomplete(int.options.getString("tag") ?? "", await tagAutocomplete(int))); - // } // case "settings null stats": { // return int.respond(generateStatsChannelAutocomplete(int.options.getString("name") ?? "")); // } diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 3f85de6..2f3a077 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -12,7 +12,7 @@ export const event = "messageCreate"; export async function callback(_client: NucleusClient, message: Message) { if (!message.guild) return; if (message.author.bot) return; - if (message.channel.type === "DM") return; + if (message.channel.isDMBased()) return; try { await statsChannelUpdate(client, await message.guild.members.fetch(message.author.id)); } catch (e) { @@ -38,7 +38,7 @@ export async function callback(_client: NucleusClient, message: Message) { mentions: message.mentions.users.size, attachments: entry(message.attachments.size, message.attachments.size + attachmentJump), repliedTo: entry( - message.reference ? message.reference.messageId : null, + (message.reference ? message.reference.messageId : null) ?? null, message.reference ? `[[Jump to message]](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.reference.messageId})` : "None" diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts index ddeff96..f8433fc 100644 --- a/src/events/messageDelete.ts +++ b/src/events/messageDelete.ts @@ -1,27 +1,24 @@ import type { NucleusClient } from "../utils/client.js"; -import type { GuildAuditLogsEntry, Message } from "discord.js"; +import Discord, { AuditLogEvent, GuildAuditLogsEntry, Message, User } from "discord.js"; export const event = "messageDelete"; export async function callback(client: NucleusClient, message: Message) { try { - if (message.author.id === client.user.id) return; + if (message.author.id === client.user!.id) return; if (client.noLog.includes(`${message.id}/${message.channel.id}/${message.id}`)) return; const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger; - const auditLog = await getAuditLog(message.guild, "MEMBER_BAN_ADD"); - const audit = auditLog.entries - .filter((entry: GuildAuditLogsEntry) => entry.target!.id === message.author.id) - .first(); - if (audit) { - if (audit.createdAt - 100 < new Date().getTime()) return; + const auditLog = (await getAuditLog(message.guild!, AuditLogEvent.MemberBanAdd)) + .filter((entry: GuildAuditLogsEntry) => (entry.target! as User).id === message.author.id)[0]; + if (auditLog) { + if (auditLog.createdTimestamp - 1000 < new Date().getTime()) return; } const replyTo = message.reference; let content = message.cleanContent; content.replace("`", "\\`"); if (content.length > 256) content = content.substring(0, 253) + "..."; const attachments = - message.attachments.size + - ( + message.attachments.size + ( message.content.match( /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/gi ) ?? [] @@ -30,9 +27,7 @@ export async function callback(client: NucleusClient, message: Message) { const config = (await client.database.guilds.read(message.guild!.id)).logging.attachments.saved[ message.channel.id + message.id ]; - if (config) { - attachmentJump = ` [[View attachments]](${config})`; - } + if (config) { attachmentJump = ` [[View attachments]](${config})`; } const data = { meta: { type: "messageDelete", @@ -48,17 +43,14 @@ export async function callback(client: NucleusClient, message: Message) { list: { messageId: entry(message.id, `\`${message.id}\``), sentBy: entry(message.author.id, renderUser(message.author)), - sentIn: entry(message.channel.id, renderChannel(message.channel)), + sentIn: entry(message.channel.id, renderChannel(message.channel as Discord.GuildChannel | Discord.ThreadChannel)), deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())), mentions: message.mentions.users.size, attachments: entry(attachments, attachments + attachmentJump), repliedTo: entry( - replyTo, - replyTo - ? `[[Jump to message]](https://discord.com/channels/${message.guild!.id}/${ - message.channel.id - }/${replyTo.messageId})` - : "None" + replyTo ? replyTo.messageId! : null, + replyTo ? `[[Jump to message]](https://discord.com/channels/${message.guild!.id}/${message.channel.id}/${replyTo.messageId})` + : "None" ) }, hidden: { diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts index 37178a7..20624fe 100644 --- a/src/events/messageEdit.ts +++ b/src/events/messageEdit.ts @@ -1,10 +1,11 @@ import type { NucleusClient } from "../utils/client.js"; import type { Message, MessageReference } from "discord.js"; +import type Discord from "discord.js"; export const event = "messageUpdate"; export async function callback(client: NucleusClient, oldMessage: Message, newMessage: Message) { - if (newMessage.author.id === client.user.id) return; + if (newMessage.author.id === client.user!.id) return; if (!newMessage.guild) return; const { log, NucleusColors, entry, renderUser, renderDelta, renderNumberDelta, renderChannel } = client.logger; const replyTo: MessageReference | null = newMessage.reference; @@ -17,8 +18,8 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe if (config) { attachmentJump = ` [[View attachments]](${config})`; } - if (newContent === oldContent) { - if (!oldMessage.flags.has("CROSSPOSTED") && newMessage.flags.has("CROSSPOSTED")) { + if (newContent === oldContent && newMessage.attachments.size === oldMessage.attachments.size) { + if (!replyTo) { const data = { meta: { type: "messageAnnounce", @@ -34,14 +35,14 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe list: { messageId: entry(newMessage.id, `\`${newMessage.id}\``), sentBy: entry(newMessage.author.id, renderUser(newMessage.author)), - sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel)), + sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel as Discord.GuildBasedChannel)), sent: entry( - new Date(newMessage.createdTimestamp), - renderDelta(new Date(newMessage.createdTimestamp)) + newMessage.createdTimestamp, + renderDelta(newMessage.createdTimestamp) ), published: entry( - new Date(newMessage.editedTimestamp!), - renderDelta(new Date(newMessage.editedTimestamp!)) + newMessage.editedTimestamp!, + renderDelta(newMessage.editedTimestamp!) ), mentions: renderNumberDelta(oldMessage.mentions.users.size, newMessage.mentions.users.size), attachments: entry( @@ -81,16 +82,16 @@ export async function callback(client: NucleusClient, oldMessage: Message, newMe list: { messageId: entry(newMessage.id, `\`${newMessage.id}\``), sentBy: entry(newMessage.author.id, renderUser(newMessage.author)), - sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel)), - sent: entry(new Date(newMessage.createdTimestamp), renderDelta(new Date(newMessage.createdTimestamp))), - edited: entry(new Date(newMessage.editedTimestamp), renderDelta(new Date(newMessage.editedTimestamp))), + sentIn: entry(newMessage.channel.id, renderChannel(newMessage.channel as Discord.GuildBasedChannel)), + sent: entry(newMessage.createdTimestamp, renderDelta(newMessage.createdTimestamp)), + edited: entry(newMessage.editedTimestamp, renderDelta(newMessage.editedTimestamp)), mentions: renderNumberDelta(oldMessage.mentions.users.size, newMessage.mentions.users.size), attachments: entry( renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size), renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size) + attachmentJump ), repliedTo: entry( - replyTo, + replyTo ? replyTo.messageId! : null, replyTo ? `[[Jump to message]](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${replyTo.messageId})` : "None" diff --git a/src/index.ts b/src/index.ts index f7fdde5..aff1b2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ import runServer from "./api/index.js"; import client from "./utils/client.js"; -// @ts-expect-error import config from "./config/main.json" assert { type: "json" }; import register from "./utils/commandRegistration/register.js"; +import { record as recordPerformance } from "./utils/performanceTesting/record.js"; client.on("ready", () => { console.log(`Logged in as ${client.user!.tag}!`); @@ -18,3 +18,5 @@ process.on("uncaughtException", (err) => { if (config.enableDevelopment) { await client.login(config.developmentToken); } else { await client.login(config.token); } + +await recordPerformance(); \ No newline at end of file diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts index 0c491b7..abda27d 100644 --- a/src/premium/attachmentLogs.ts +++ b/src/premium/attachmentLogs.ts @@ -4,7 +4,7 @@ import singleNotify from "../utils/singleNotify.js"; import { saveAttachment } from "../reflex/scanners.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import addPlural from "../utils/plurals.js"; -import type { Message } from "discord.js"; +import type { GuildTextBasedChannel, Message } from "discord.js"; export default async function logAttachment(message: Message): Promise { if (!message.guild) throw new Error("Tried to log an attachment in a non-guild message"); @@ -31,7 +31,7 @@ export default async function logAttachment(message: Message): Promise().addComponents([ new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(url), new ButtonBuilder() .setLabel("Delete") @@ -99,7 +100,7 @@ export default async function (interaction: CommandInteraction | MessageComponen .setEmoji("CONTROL.DOWNLOAD") ], components: [ - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents([ new ButtonBuilder() .setLabel("Delete") .setStyle(ButtonStyle.Danger) @@ -128,8 +129,8 @@ export default async function (interaction: CommandInteraction | MessageComponen }, list: { ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"), - deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user)), - deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())) + deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)), + deleted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())) }, hidden: { guild: interaction.guild!.id diff --git a/src/reflex/verify.ts b/src/reflex/verify.ts index 6458439..2372130 100644 --- a/src/reflex/verify.ts +++ b/src/reflex/verify.ts @@ -5,7 +5,8 @@ import Discord, { MessageComponentInteraction, Role, ButtonStyle, - PermissionsBitField + PermissionsBitField, + ButtonInteraction } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import fetch from "node-fetch"; @@ -28,7 +29,7 @@ function step(i: number) { return "\n\n" + createPageIndicator(5, i); } -export default async function (interaction: CommandInteraction | MessageComponentInteraction) { +export default async function (interaction: CommandInteraction | ButtonInteraction) { const verify = client.verify; await interaction.reply({ embeds: LoadingEmbed, diff --git a/src/utils/client.ts b/src/utils/client.ts index a57c639..43cbe11 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -1,8 +1,8 @@ -import Discord, { Client, Interaction, AutocompleteInteraction } from 'discord.js'; +import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits } from 'discord.js'; import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; import type { VerifySchema } from "../reflex/verify.js"; -import { Guilds, History, ModNotes, Premium } from "../utils/database.js"; +import { Guilds, History, ModNotes, Premium, PerformanceTest } from "../utils/database.js"; import EventScheduler from "../utils/eventScheduler.js"; import type { RoleMenuSchema } from "../actions/roleMenu.js"; import config from "../config/main.json" assert { type: "json" }; @@ -21,6 +21,7 @@ class NucleusClient extends Client { notes: ModNotes; premium: Premium; eventScheduler: EventScheduler; + performanceTest: PerformanceTest; }; commands: Record = {}; constructor(database: typeof NucleusClient.prototype.database) { - super({ intents: 32767 }); + super({ intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildPresences, + GatewayIntentBits.GuildMembers + ]}); this.database = database; } } @@ -42,7 +49,8 @@ const client = new NucleusClient({ history: new History(), notes: new ModNotes(), premium: new Premium(), - eventScheduler: new EventScheduler() + eventScheduler: new EventScheduler(), + performanceTest: new PerformanceTest() }); export default client; diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index a4c57c4..d96ca90 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -16,7 +16,7 @@ const colours = { async function registerCommands() { const commands = []; - const files = fs.readdirSync(config.commandsFolder, { withFileTypes: true }).filter( + const files: fs.Dirent[] = fs.readdirSync(config.commandsFolder, { withFileTypes: true }).filter( file => !file.name.endsWith(".ts") && !file.name.endsWith(".map") ); console.log(`Registering ${files.length} commands`) @@ -25,10 +25,15 @@ async function registerCommands() { const last = i === files.length - 1 ? "└" : "├"; if (file.isDirectory()) { console.log(`${last}─ ${colours.yellow}Loading subcommands of ${file.name}${colours.none}`) - commands.push((await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)).command); + const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)).command; + commands.push(fetched); } else if (file.name.endsWith(".js")) { console.log(`${last}─ ${colours.yellow}Loading command ${file.name}${colours.none}`) const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`)); + fetched.command.setDMPermission(fetched.allowedInDMs ?? false) + fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {}) + fetched.command.setDescriptionLocalizations(fetched.descriptionLocalizations ?? {}) + if (fetched.nameLocalizations || fetched.descriptionLocalizations) console.log("AAAAA") commands.push(fetched.command); client.commands["commands/" + fetched.command.name] = fetched; } @@ -97,6 +102,8 @@ async function registerContextMenus() { console.log(`${last}─ ${colours.yellow}Loading message context menu ${file.name}${colours.none}`) const context = (await import(`../../../${config.messageContextFolder}/${file.name}`)); context.command.setType(ApplicationCommandType.Message); + context.command.setDMPermission(context.allowedInDMs ?? false) + context.command.setNameLocalizations(context.nameLocalizations ?? {}) commands.push(context.command); client.commands["contextCommands/message/" + context.command.name] = context; @@ -182,6 +189,7 @@ async function execute(check: Function | undefined, callback: Function | undefin callback(data); } + export default async function register() { let commandList: ( Discord.SlashCommandBuilder | Discord.ContextMenuCommandBuilder )[] = []; commandList = commandList.concat(await registerCommands()); diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts index 76ecabe..b2927d6 100644 --- a/src/utils/commandRegistration/slashCommandBuilder.ts +++ b/src/utils/commandRegistration/slashCommandBuilder.ts @@ -13,7 +13,13 @@ const colours = { } -export async function group(name: string, description: string, path: string) { +export async function group( + name: string, + description: string, + path: string, + nameLocalizations?: Record, + descriptionLocalizations?: Record +) { // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString console.log(`│ ├─ Loading group ${name}`) const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│ ") @@ -22,6 +28,8 @@ export async function group(name: string, description: string, path: string) { subcommandGroup .setName(name) .setDescription(description) + if (nameLocalizations) { subcommandGroup.setNameLocalizations(nameLocalizations) } + if (descriptionLocalizations) { subcommandGroup.setDescriptionLocalizations(descriptionLocalizations) } for (const subcommand of fetched.subcommands) { subcommandGroup.addSubcommand(subcommand.command); @@ -31,7 +39,16 @@ export async function group(name: string, description: string, path: string) { }; } -export async function command(name: string, description: string, path: string, commandString: string | undefined = undefined) { +export async function command( + name: string, + description: string, + path: string, + commandString: string | undefined = undefined, + nameLocalizations?: Record, + descriptionLocalizations?: Record, + userPermissions?: Discord.PermissionsString[], + allowedInDMs?: boolean +) { // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString commandString = "commands/" + (commandString ?? path); const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path); @@ -39,6 +56,14 @@ export async function command(name: string, description: string, path: string, c return (command: SlashCommandBuilder) => { command.setName(name) command.setDescription(description) + command.setNameLocalizations(nameLocalizations ?? {}) + command.setDescriptionLocalizations(descriptionLocalizations ?? {}) + command.setDMPermission(allowedInDMs ?? false) + if (userPermissions) { + const bitfield = new Discord.PermissionsBitField() + bitfield.add(userPermissions) + command.setDefaultMemberPermissions(bitfield.bitfield) + } for (const subcommand of fetched.subcommands) { let fetchedCommand; diff --git a/src/utils/database.ts b/src/utils/database.ts index b14c5c4..ba1d89d 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -149,6 +149,33 @@ export class History { } } +export class PerformanceTest { + performanceData: Collection; + + constructor() { + this.performanceData = database.collection("performance"); + } + + async record(data: PerformanceDataSchema) { + data.timestamp = new Date(); + await this.performanceData.insertOne(data); + } + async read() { + return await this.performanceData.find({}).toArray(); + } +} + +export interface PerformanceDataSchema { + timestamp?: Date; + discord: number; + databaseRead: number; + resources: { + cpu: number; + memory: number; + temperature: number; + } +} + export class ModNotes { modNotes: Collection; @@ -205,7 +232,11 @@ export interface GuildConfig { }; invite: { enabled: boolean; - channels: string[]; + allowed: { + channels: string[]; + roles: string[]; + users: string[]; + }; }; pings: { mass: number; diff --git a/src/utils/log.ts b/src/utils/log.ts index b097798..7ab7903 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -25,7 +25,7 @@ export const Logger = { const delta = num2 - num1; return `${num1} -> ${num2} (${delta > 0 ? "+" : ""}${delta})`; }, - entry(value: string | null, displayValue: string): { value: string | null; displayValue: string } { + entry(value: string | number | null, displayValue: string): { value: string | null; displayValue: string } { return { value: value, displayValue: displayValue }; }, renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel) { @@ -44,16 +44,15 @@ export const Logger = { }, async getAuditLog(guild: Discord.Guild, event: Discord.GuildAuditLogsResolvable): Promise { await wait(250); - const auditLog = await guild.fetchAuditLogs({ type: event }); - return auditLog as unknown as Discord.GuildAuditLogsEntry[]; + const auditLog = (await guild.fetchAuditLogs({ type: event })).entries.map(m => m) + return auditLog as Discord.GuildAuditLogsEntry[]; }, // eslint-disable-next-line @typescript-eslint/no-explicit-any async log(log: any): Promise { const config = await client.database.guilds.read(log.hidden.guild); if (!config.logging.logs.enabled) return; - if (!(log.meta.calculateType === true)) { - if (!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) - console.log("Not logging this type of event"); + if (!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) { + console.log("Not logging this type of event"); return; } if (config.logging.logs.channel) { diff --git a/src/utils/logTranscripts.ts b/src/utils/logTranscripts.ts new file mode 100644 index 0000000..d5ab0b2 --- /dev/null +++ b/src/utils/logTranscripts.ts @@ -0,0 +1,3 @@ +function JSONTranscriptFromMessageArray(messages: Discord.Message[]) { + +} \ No newline at end of file diff --git a/src/utils/performanceTesting/record.ts b/src/utils/performanceTesting/record.ts new file mode 100644 index 0000000..2d9524b --- /dev/null +++ b/src/utils/performanceTesting/record.ts @@ -0,0 +1,47 @@ +import client from "../client.js"; +import { resourceUsage } from "process"; +import { spawn } from "child_process"; +import config from "../../config/main.json" assert { type: "json" }; + + +const discordPing = () => { + return client.ws.ping; +} + +const databaseReadTime = async () => { + const guild = await client.guilds.fetch(config.managementGuildID); + const user = guild.ownerId; + const currentYear = new Date().getFullYear(); + const start = Date.now(); + client.database.history.read(guild.id, user, currentYear - 1); + const end = Date.now(); + return end - start; +} + +const resources = () => { + const current = resourceUsage(); + const temperatureRaw = spawn("acpi", ["-t"]) + let temperatureData: number = 0; + temperatureRaw.stdout.on("data", (data) => { + return temperatureData = data.toString().split(", ")[1].split(" ")[0]; // °C + }) + return { + memory: current.sharedMemorySize, + cpu: current.userCPUTime + current.systemCPUTime, + temperature: temperatureData + } +} + +const record = async () => { + const results = { + discord: discordPing(), + databaseRead: await databaseReadTime(), + resources: resources() + } + client.database.performanceTest.record(results) + setInterval(async () => { + record(); + }, 10 * 1000); +} + +export { record }; \ No newline at end of file