From 1dee28f37ea1dfd9678fdfc6eda82eb0d2ef44ce Mon Sep 17 00:00:00 2001 From: PineaFan Date: Mon, 16 Jan 2023 22:09:07 +0000 Subject: [PATCH] Working on viewas --- package.json | 1 + src/commands/mod/{info.ts => about.ts} | 6 +- src/commands/mod/ban.ts | 176 +++++++------- src/commands/mod/kick.ts | 16 +- src/commands/mod/mute.ts | 16 +- src/commands/mod/nick.ts | 8 +- src/commands/mod/purge.ts | 280 +++++++++++----------- src/commands/mod/slowmode.ts | 48 ++-- src/commands/mod/viewas.ts | 314 ++++++++++++++----------- src/commands/mod/warn.ts | 16 +- src/commands/nucleus/suggest.ts | 28 ++- src/events/interactionCreate.ts | 31 ++- src/utils/confirmationMessage.ts | 33 ++- src/utils/performanceTesting/record.ts | 42 ++-- src/utils/singleNotify.ts | 8 +- 15 files changed, 524 insertions(+), 499 deletions(-) rename src/commands/mod/{info.ts => about.ts} (98%) diff --git a/package.json b/package.json index f088be0..28b3783 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "node-tesseract-ocr": "^2.2.1", "pastebin-api": "^5.1.1", "structured-clone": "^0.2.2", + "systeminformation": "^5.17.3", "typescript": "^5.0.0-dev.20230102", "uuid": "^8.3.2" }, diff --git a/src/commands/mod/info.ts b/src/commands/mod/about.ts similarity index 98% rename from src/commands/mod/info.ts rename to src/commands/mod/about.ts index fac87a0..c69f4a9 100644 --- a/src/commands/mod/info.ts +++ b/src/commands/mod/about.ts @@ -1,4 +1,4 @@ -import { LoadingEmbed } from './../../utils/defaultEmbeds.js'; +import { LoadingEmbed } from '../../utils/defaultEmbeds.js'; import type { HistorySchema } from "../../utils/database.js"; import Discord, { CommandInteraction, @@ -22,8 +22,8 @@ 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"}) + .setName("about") + // .setNameLocalizations({"ru": "info", "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/ban.ts b/src/commands/mod/ban.ts index ba60d1e..330edfd 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -59,6 +59,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ) .setColor("Danger") .addReasonButton(reason ?? "") + .setFailedMessage("No changes were made", "Success", "PUNISH.BAN.GREEN") .send(reason !== null); reason = reason ?? ""; if (confirmation.cancelled) timedOut = true; @@ -66,116 +67,103 @@ const callback = async (interaction: CommandInteraction): Promise => { else if (confirmation.newReason) reason = confirmation.newReason; else if (confirmation.components) notify = confirmation.components["notify"]!.active; } while (!timedOut && !chosen) - if (timedOut) return; - if (confirmation.success) { - reason = reason.length ? reason : null - let dmSent = false; - let dmMessage; - 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[]; - } = { - embeds: [ - new EmojiEmbed() - .setEmoji("PUNISH.BAN.RED") - .setTitle("Banned") - .setDescription( - `You have been banned in ${interaction.guild.name}` + - (reason ? ` for:\n${reason}` : ".\n*No reason was provided.*") - ) - .setStatus("Danger") - ], - components: [] - }; - if (config.moderation.ban.text && config.moderation.ban.link) { - messageData.embeds[0]!.setFooter(LinkWarningFooter) - messageData.components.push(new ActionRowBuilder() - .addComponents(new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setLabel(config.moderation.ban.text) - .setURL(config.moderation.ban.link) - ) - ) - } - dmMessage = await (interaction.options.getMember("user") as GuildMember).send(messageData); - dmSent = true; - } - } catch { - dmSent = false; - } - try { - const member = interaction.options.getMember("user") as GuildMember; - const days: number = interaction.options.get("delete")?.value as number | null ?? 0; - member.ban({ - deleteMessageSeconds: days * 24 * 60 * 60, - reason: reason ?? "*No reason provided*" - }); - await client.database.history.create("ban", interaction.guild.id, member.user, interaction.user, reason); - const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger; - const data = { - meta: { - type: "memberBan", - displayName: "Member Banned", - calculateType: "guildMemberPunish", - color: NucleusColors.red, - emoji: "PUNISH.BAN.RED", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(member.user.id, `\`${member.user.id}\``), - name: entry(member.user.id, renderUser(member.user)), - banned: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())), - bannedBy: entry(interaction.user.id, renderUser(interaction.user)), - reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"), - accountCreated: entry(member.user.createdAt.toString(), renderDelta(member.user.createdAt.getTime())), - serverMemberCount: interaction.guild.memberCount - }, - hidden: { - guild: interaction.guild.id - } - }; - log(data); - } catch { - await interaction.editReply({ + if (timedOut || !confirmation.success) return; + reason = reason.length ? reason : null + let dmSent = false; + let dmMessage; + 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[]; + } = { embeds: [ new EmojiEmbed() .setEmoji("PUNISH.BAN.RED") - .setTitle("Ban") - .setDescription("Something went wrong and the user was not banned") + .setTitle("Banned") + .setDescription( + `You have been banned in ${interaction.guild.name}` + + (reason ? ` for:\n${reason}` : ".\n*No reason was provided.*") + ) .setStatus("Danger") ], components: [] - }); - if (dmSent && dmMessage) await dmMessage.delete(); - return; + }; + if (config.moderation.ban.text && config.moderation.ban.link) { + messageData.embeds[0]!.setFooter(LinkWarningFooter) + messageData.components.push(new ActionRowBuilder() + .addComponents(new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel(config.moderation.ban.text) + .setURL(config.moderation.ban.link) + ) + ) + } + dmMessage = await (interaction.options.getMember("user") as GuildMember).send(messageData); + dmSent = true; } - const failed = !dmSent && notify; - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`) - .setTitle("Ban") - .setDescription("The member was banned" + (failed ? ", but could not be notified" : "")) - .setStatus(failed ? "Warning" : "Success") - ], - components: [] + } catch { + dmSent = false; + } + try { + const member = interaction.options.getMember("user") as GuildMember; + const days: number = interaction.options.get("delete")?.value as number | null ?? 0; + member.ban({ + deleteMessageSeconds: days * 24 * 60 * 60, + reason: reason ?? "*No reason provided*" }); - } else { + await client.database.history.create("ban", interaction.guild.id, member.user, interaction.user, reason); + const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + const data = { + meta: { + type: "memberBan", + displayName: "Member Banned", + calculateType: "guildMemberPunish", + color: NucleusColors.red, + emoji: "PUNISH.BAN.RED", + timestamp: new Date().getTime() + }, + list: { + memberId: entry(member.user.id, `\`${member.user.id}\``), + name: entry(member.user.id, renderUser(member.user)), + banned: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())), + bannedBy: entry(interaction.user.id, renderUser(interaction.user)), + reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"), + accountCreated: entry(member.user.createdAt.toString(), renderDelta(member.user.createdAt.getTime())), + serverMemberCount: interaction.guild.memberCount + }, + hidden: { + guild: interaction.guild.id + } + }; + log(data); + } catch { await interaction.editReply({ embeds: [ new EmojiEmbed() - .setEmoji("PUNISH.BAN.GREEN") + .setEmoji("PUNISH.BAN.RED") .setTitle("Ban") - .setDescription("No changes were made") - .setStatus("Success") + .setDescription("Something went wrong and the user was not banned") + .setStatus("Danger") ], components: [] }); + if (dmSent && dmMessage) await dmMessage.delete(); + return; } + const failed = !dmSent && notify; + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`) + .setTitle("Ban") + .setDescription("The member was banned" + (failed ? ", but could not be notified" : "")) + .setStatus(failed ? "Warning" : "Success") + ], + components: [] + }); }; const check = async (interaction: CommandInteraction) => { diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index 88b6e14..a4b9774 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -47,6 +47,7 @@ const callback = async (interaction: CommandInteraction): Promise => { notify ) .addReasonButton(reason ?? "") + .setFailedMessage("No changes were made", "Success", "PUNISH.KICK.GREEN") .send(reason !== null); reason = reason ?? ""; if (confirmation.cancelled) timedOut = true; @@ -56,20 +57,7 @@ const callback = async (interaction: CommandInteraction): Promise => { notify = confirmation.components["notify"]!.active; } } while (!timedOut && !success) - if (timedOut) return; - if (!confirmation.success) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("PUNISH.KICK.GREEN") - .setTitle("Kick") - .setDescription("No changes were made") - .setStatus("Success") - ], - components: [] - }); - return; - } + if (timedOut || !confirmation.success) return; let dmSent = false; let dmMessage; const config = await client.database.guilds.read(interaction.guild.id); diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index d68272b..b558e87 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -207,6 +207,7 @@ const callback = async (interaction: CommandInteraction): Promise => { notify ) .addReasonButton(reason ?? "") + .setFailedMessage("No changes were made", "Success", "PUNISH.MUTE.GREEN") .send(true); reason = reason ?? ""; if (confirmation.cancelled) timedOut = true; @@ -217,20 +218,7 @@ const callback = async (interaction: CommandInteraction): Promise => { createAppealTicket = confirmation.components["appeal"]!.active; } } while (!timedOut && !success) - if (timedOut) return; - if (!confirmation.success) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("PUNISH.BAN.GREEN") - .setTitle("Softban") - .setDescription("No changes were made") - .setStatus("Success") - ], - components: [] - }); - return; - } + if (timedOut || !confirmation.success) return; const status: {timeout: boolean | null, role: boolean | null, dm: boolean | null} = {timeout: null, role: null, dm: null}; let dmMessage; try { diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index 81935a2..c5e45b6 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -169,22 +169,22 @@ const callback = async (interaction: CommandInteraction): Promise => { const check = (interaction: CommandInteraction) => { const member = interaction.member as GuildMember; - const me = interaction.guild.me!; + const me = interaction.guild!.members.me!; const apply = interaction.options.getMember("user") as GuildMember; - if (member === null || me === null || apply === null) throw new Error("That member is not in the server"); const memberPos = member.roles.cache.size ? member.roles.highest.position : 0; const mePos = me.roles.cache.size ? me.roles.highest.position : 0; const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0; + if (!interaction.guild) return false; // Do not allow any changing of the owner if (member.id === interaction.guild.ownerId) throw new Error("You cannot change the owner's nickname"); // Check if Nucleus can change the nickname if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member"); // Check if Nucleus has permission to change the nickname - if (!me.permissions.has("MANAGE_NICKNAMES")) throw new Error("I do not have the *Manage Nicknames* permission"); + if (!me.permissions.has("ManageNicknames")) throw new Error("I do not have the *Manage Nicknames* permission"); // Allow the owner to change anyone's nickname if (member.id === interaction.guild.ownerId) return true; // Check if the user has manage_nicknames permission - if (!member.permissions.has("MANAGE_NICKNAMES")) + if (!member.permissions.has("ManageNicknames")) throw new Error("You do not have the *Manage Nicknames* permission"); // Allow changing your own nickname if (member === apply) return true; diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 3020eb8..5425714 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -240,170 +240,158 @@ const callback = async (interaction: CommandInteraction): Promise => { }) ) .setColor("Danger") + .setFailedMessage("No changes were made", "Success", "CHANNEL.PURGE.GREEN") .send(); - if (confirmation.cancelled) return; - if (confirmation.success) { - let messages; - try { - if (!user) { - const toDelete = await (interaction.channel as TextChannel).messages.fetch({ - limit: interaction.options.get("amount")?.value as number - }); - messages = await (channel as TextChannel).bulkDelete(toDelete, true); - } else { - const toDelete = ( - await ( - await (interaction.channel as TextChannel).messages.fetch({ - limit: 100 - }) - ).filter((m) => m.author.id === user.id) - ).first(interaction.options.get("amount")?.value as number); - messages = await (channel as TextChannel).bulkDelete(toDelete, true); - } - } catch (e) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.PURGE.RED") - .setTitle("Purge") - .setDescription("Something went wrong and no messages were deleted") - .setStatus("Danger") - ], - components: [] - }); - } - if (!messages) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.PURGE.RED") - .setTitle("Purge") - .setDescription("No messages could be deleted") - .setStatus("Danger") - ], - components: [] + if (confirmation.cancelled || !confirmation.success) return; + let messages; + try { + if (!user) { + const toDelete = await (interaction.channel as TextChannel).messages.fetch({ + limit: interaction.options.get("amount")?.value as number }); - return; + messages = await (channel as TextChannel).bulkDelete(toDelete, true); + } else { + const toDelete = ( + await ( + await (interaction.channel as TextChannel).messages.fetch({ + limit: 100 + }) + ).filter((m) => m.author.id === user.id) + ).first(interaction.options.get("amount")?.value as number); + messages = await (channel as TextChannel).bulkDelete(toDelete, true); } - if (user) { - await client.database.history.create( - "purge", - interaction.guild.id, - user.user, - interaction.user, - (interaction.options.get("reason")?.value as (string | null)) ?? "*No reason provided*", - null, - null, - messages.size.toString() - ); + } catch (e) { + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.RED") + .setTitle("Purge") + .setDescription("Something went wrong and no messages were deleted") + .setStatus("Danger") + ], + 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) { + await client.database.history.create( + "purge", + interaction.guild.id, + user.user, + interaction.user, + (interaction.options.get("reason")?.value as (string | null)) ?? "*No reason provided*", + null, + null, + messages.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 GuildChannel)), + messagesCleared: entry(messages.size.toString(), messages.size.toString()) + }, + hidden: { + guild: interaction.guild.id } - 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 GuildChannel)), - messagesCleared: entry(messages.size.toString(), messages.size.toString()) - }, - hidden: { - guild: interaction.guild.id + }; + log(data); + let out = ""; + messages.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`; + }); } - }; - log(data); - let out = ""; - messages.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"; + 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().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 }); - const attachmentObject = { - attachment: Buffer.from(out), - name: `purge-${channel.id}-${Date.now()}.txt`, - description: "Purge log" - }; - const m = (await interaction.editReply({ + } catch { + return; + } + if (component.customId === "download") { + interaction.editReply({ embeds: [ new EmojiEmbed() .setEmoji("CHANNEL.PURGE.GREEN") .setTitle("Purge") - .setDescription("Messages cleared") + .setDescription("Transcript uploaded above") .setStatus("Success") ], - components: [ - new Discord.ActionRowBuilder().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: [] - }); - } + components: [], + files: [attachmentObject] + }); } else { - await interaction.editReply({ + interaction.editReply({ embeds: [ new EmojiEmbed() .setEmoji("CHANNEL.PURGE.GREEN") .setTitle("Purge") - .setDescription("No changes were made") + .setDescription("Messages cleared") .setStatus("Success") ], components: [] diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts index b565deb..9bd994d 100644 --- a/src/commands/mod/slowmode.ts +++ b/src/commands/mod/slowmode.ts @@ -47,45 +47,33 @@ const callback = async (interaction: CommandInteraction): Promise => { }) + "Are you sure you want to set the slowmode in this channel?" ) .setColor("Danger") + .setFailedMessage("No changes were made", "Danger", "CHANNEL.SLOWMODE.ON") .send(); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - (interaction.channel as TextChannel).setRateLimitPerUser(time); - } catch (e) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.SLOWMODE.OFF") - .setTitle("Slowmode") - .setDescription("Something went wrong while setting the slowmode") - .setStatus("Danger") - ], - components: [] - }); - } + if (confirmation.cancelled || !confirmation.success) return; + try { + (interaction.channel as TextChannel).setRateLimitPerUser(time); + } catch (e) { await interaction.editReply({ embeds: [ new EmojiEmbed() - .setEmoji("CHANNEL.SLOWMODE.ON") + .setEmoji("CHANNEL.SLOWMODE.OFF") .setTitle("Slowmode") - .setDescription("The channel slowmode was set successfully") - .setStatus("Success") - ], - components: [] - }); - } else { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.SLOWMODE.ON") - .setTitle("Slowmode") - .setDescription("No changes were made") - .setStatus("Success") + .setDescription("Something went wrong while setting the slowmode") + .setStatus("Danger") ], components: [] }); } + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setEmoji("CHANNEL.SLOWMODE.ON") + .setTitle("Slowmode") + .setDescription("The channel slowmode was set successfully") + .setStatus("Success") + ], + components: [] + }); }; const check = (interaction: CommandInteraction) => { diff --git a/src/commands/mod/viewas.ts b/src/commands/mod/viewas.ts index a713f55..8b2864a 100644 --- a/src/commands/mod/viewas.ts +++ b/src/commands/mod/viewas.ts @@ -1,13 +1,13 @@ import Discord, { - CategoryChannel, CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, - SelectMenuBuilder, - ButtonStyle + ButtonStyle, + NonThreadGuildBasedChannel } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { GuildBasedChannel } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import pageIndicator from "../../utils/createPageIndicator.js"; @@ -19,145 +19,185 @@ const command = (builder: SlashCommandSubcommandBuilder) => .addUserOption((option) => option.setName("member").setDescription("The member to view as").setRequired(true)); const callback = async (interaction: CommandInteraction): Promise => { - let channels = []; - let m; - interaction.guild.channels.cache.forEach((channel) => { - if (!channel.parent && channel.type !== "GUILD_CATEGORY") channels.push(channel); + /* + * { + categoryObject: channel[], + categoryObject: channel[], + "null": channel[] + } + */ + + const channels: Record = {"": [] as GuildBasedChannel[]}; + + interaction.guild!.channels.fetch().then(channelCollection => { + channelCollection.forEach(channel => { + if (!channel) return; // if no channel + if (channel.type === Discord.ChannelType.GuildCategory) { + if(!channels[channel!.id]) channels[channel!.id] = [channel]; + } else if (channel.parent) { + if (!channels[channel.parent.id]) channels[channel.parent.id] = [channel]; + else (channels[channel.parent.id as string])!.push(channel); + } else { + channels[""]!.push(channel); + } + }); }); - channels = [channels]; - channels = channels.concat( - interaction.guild.channels.cache - .filter((c) => c.type === "GUILD_CATEGORY") - .map((c) => (c as CategoryChannel).children.map((c) => c)) - ); - const autoSortBelow = ["GUILD_VOICE", "GUILD_STAGE_VOICE"]; - channels = channels.map((c) => - c.sort((a, b) => { - if (autoSortBelow.includes(a.type) && autoSortBelow.includes(b.type)) return a.position - b.position; + + const member = interaction.options.getMember("member") as Discord.GuildMember; + const autoSortBelow = [Discord.ChannelType.GuildVoice, Discord.ChannelType.GuildStageVoice]; + // for each category, sort its channels. This should be based on the order of the channels, with voice and stage channels sorted below text + channels = Object.values(channels).map((c) => { + return c.sort((a: GuildBasedChannel, b: GuildBasedChannel) => { + if (a.type === Discord.ChannelType.PrivateThread || b.type === Discord.ChannelType.PrivateThread) + if (autoSortBelow.includes(a.type) && autoSortBelow.includes(b.type)) return a.position ?? 0 - b.position ; if (autoSortBelow.includes(a.type)) return 1; if (autoSortBelow.includes(b.type)) return -1; - return a.position - b.position; - }) - ); - // Sort all arrays by the position of the first channels parent position - channels = channels.sort((a, b) => { - if (!a[0].parent) return -1; - if (!b[0].parent) return 1; - return a[0].parent.position - b[0].parent.position; - }); - const member = interaction.options.getMember("member") as Discord.GuildMember; - m = await interaction.reply({ - embeds: [ - new EmojiEmbed() - .setEmoji("MEMBER.JOIN") - .setTitle("Viewing as " + member.displayName) - .setStatus("Success") - ], - ephemeral: true, - fetchReply: true - }); - let page = 0; - let timedOut = false; - while (!timedOut) { - m = await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("MEMBER.JOIN") - .setTitle("Viewing as " + member.displayName) - .setStatus("Success") - .setDescription( - `**${channels[page][0].parent ? channels[page][0].parent.name : "Uncategorised"}**` + - "\n" + - channels[page] - .map((c) => { - let channelType = c.type; - if (interaction.guild.rulesChannelId === c.id) channelType = "RULES"; - else if ("nsfw" in c && c.nsfw) channelType += "_NSFW"; - return c.permissionsFor(member).has("VIEW_CHANNEL") - ? `${getEmojiByName("ICONS.CHANNEL." + channelType)} ${c.name}\n` + - (() => { - if ("threads" in c && c.threads.cache.size > 0) { - return ( - c.threads.cache - .map( - (t) => - ` ${ - getEmojiByName("ICONS.CHANNEL.THREAD_PIPE") + - " " + - getEmojiByName("ICONS.CHANNEL.THREAD_CHANNEL") - } ${t.name}` - ) - .join("\n") + "\n" - ); - } - return ""; - })() - : ""; - }) - .join("") + - "\n" + - pageIndicator(channels.length, page) - ) - ], - components: [ - new ActionRowBuilder().addComponents([ - new SelectMenuBuilder() - .setOptions( - channels.map((c, index) => ({ - label: c[0].parent ? c[0].parent.name : "Uncategorised", - value: index.toString(), - default: page === index - })) - ) - .setCustomId("select") - .setMaxValues(1) - .setMinValues(1) - .setPlaceholder("Select a category") - ]), - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel( - page === 0 - ? "" - : channels[page - 1][0].parent - ? channels[page - 1][0].parent.name - : "Uncategorised" - ) - .setDisabled(page === 0) - .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) - .setStyle(ButtonStyle.Primary) - .setCustomId("previous"), - new ButtonBuilder() - .setLabel( - page === channels.length - 1 - ? "" - : channels[page + 1][0].parent - ? channels[page + 1][0].parent.name - : "Uncategorised" - ) - .setDisabled(page === channels.length - 1) - .setEmoji(getEmojiByName("CONTROL.RIGHT", "id")) - .setStyle(ButtonStyle.Primary) - .setCustomId("next") - ]) - ] + return a - b; }); - let i; - try { - i = await m.awaitMessageComponent({ time: 300000 }); - } catch (e) { - timedOut = true; - continue; - } - i.deferUpdate(); - if (i.customId === "next") { - page++; - } else if (i.customId === "previous") { - page--; - } else if (i.customId === "select") { - page = parseInt(i.values[0]); - } } + + + //OLD CODE START + // const unprocessedChannels: GuildBasedChannel[] = []; + // let m; + // interaction.guild!.channels.cache.forEach((channel) => { + // if (!channel.parent && channel.type !== Discord.ChannelType.GuildCategory) unprocessedChannels.push(channel); + // }); + // let channels: GuildBasedChannel[][] = [unprocessedChannels]; + // channels = channels.concat( + // interaction.guild!.channels.cache + // .filter((c) => c.type === Discord.ChannelType.GuildCategory) + // .map((c) => (c as CategoryChannel).children.map((c) => c)) + // ); + // const autoSortBelow = ["GUILD_VOICE", "GUILD_STAGE_VOICE"]; + // channels = channels.map((c) => + // c.sort((a, b) => { + // if (autoSortBelow.includes(a.type) && autoSortBelow.includes(b.type)) return a.position - b.position; + // if (autoSortBelow.includes(a.type)) return 1; + // if (autoSortBelow.includes(b.type)) return -1; + // return a.position - b.position; + // }) + // ); + // // Sort all arrays by the position of the first channels parent position + // channels = channels.sort((a, b) => { + // if (!a[0].parent) return -1; + // if (!b[0].parent) return 1; + // return a[0].parent.position - b[0].parent.position; + // }); + // const member = interaction.options.getMember("member") as Discord.GuildMember; + // m = await interaction.reply({ + // embeds: [ + // new EmojiEmbed() + // .setEmoji("MEMBER.JOIN") + // .setTitle("Viewing as " + member.displayName) + // .setStatus("Success") + // ], + // ephemeral: true, + // fetchReply: true + // }); + // let page = 0; + // let timedOut = false; + // while (!timedOut) { + // m = await interaction.editReply({ + // embeds: [ + // new EmojiEmbed() + // .setEmoji("MEMBER.JOIN") + // .setTitle("Viewing as " + member.displayName) + // .setStatus("Success") + // .setDescription( + // `**${channels[page][0].parent ? channels[page][0].parent.name : "Uncategorised"}**` + + // "\n" + + // channels[page] + // .map((c) => { + // let channelType = c.type; + // if (interaction.guild.rulesChannelId === c.id) channelType = "RULES"; + // else if ("nsfw" in c && c.nsfw) channelType += "_NSFW"; + // return c.permissionsFor(member).has("VIEW_CHANNEL") + // ? `${getEmojiByName("ICONS.CHANNEL." + channelType)} ${c.name}\n` + + // (() => { + // if ("threads" in c && c.threads.cache.size > 0) { + // return ( + // c.threads.cache + // .map( + // (t) => + // ` ${ + // getEmojiByName("ICONS.CHANNEL.THREAD_PIPE") + + // " " + + // getEmojiByName("ICONS.CHANNEL.THREAD_CHANNEL") + // } ${t.name}` + // ) + // .join("\n") + "\n" + // ); + // } + // return ""; + // })() + // : ""; + // }) + // .join("") + + // "\n" + + // pageIndicator(channels.length, page) + // ) + // ], + // components: [ + // new ActionRowBuilder().addComponents([ + // new SelectMenuBuilder() + // .setOptions( + // channels.map((c, index) => ({ + // label: c[0].parent ? c[0].parent.name : "Uncategorised", + // value: index.toString(), + // default: page === index + // })) + // ) + // .setCustomId("select") + // .setMaxValues(1) + // .setMinValues(1) + // .setPlaceholder("Select a category") + // ]), + // new ActionRowBuilder().addComponents([ + // new ButtonBuilder() + // .setLabel( + // page === 0 + // ? "" + // : channels[page - 1][0].parent + // ? channels[page - 1][0].parent.name + // : "Uncategorised" + // ) + // .setDisabled(page === 0) + // .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) + // .setStyle(ButtonStyle.Primary) + // .setCustomId("previous"), + // new ButtonBuilder() + // .setLabel( + // page === channels.length - 1 + // ? "" + // : channels[page + 1][0].parent + // ? channels[page + 1][0].parent.name + // : "Uncategorised" + // ) + // .setDisabled(page === channels.length - 1) + // .setEmoji(getEmojiByName("CONTROL.RIGHT", "id")) + // .setStyle(ButtonStyle.Primary) + // .setCustomId("next") + // ]) + // ] + // }); + // let i; + // try { + // i = await m.awaitMessageComponent({ time: 300000 }); + // } catch (e) { + // timedOut = true; + // continue; + // } + // i.deferUpdate(); + // if (i.customId === "next") { + // page++; + // } else if (i.customId === "previous") { + // page--; + // } else if (i.customId === "select") { + // page = parseInt(i.values[0]); + // } + // } + }; const check = (interaction: CommandInteraction) => { diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index d920bb0..c4aa7c3 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -56,6 +56,7 @@ const callback = async (interaction: CommandInteraction): Promise => { notify ) .addReasonButton(reason ?? "") + .setFailedMessage("No changes were made", "Success", "PUNISH.WARN.GREEN") .send(reason !== null); reason = reason ?? ""; if (confirmation.cancelled) timedOut = true; @@ -66,20 +67,7 @@ const callback = async (interaction: CommandInteraction): Promise => { createAppealTicket = confirmation.components["appeal"]!.active; } } while (!timedOut && !success) - if (timedOut) return; - if (!confirmation.success) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("PUNISH.WARN.GREEN") - .setTitle("Warn") - .setDescription("No changes were made") - .setStatus("Success") - ], - components: [] - }); - return; - } + if (timedOut || !confirmation.success) return; let dmSent = false; const config = await client.database.guilds.read(interaction.guild.id); try { diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts index a75e8a0..e31696b 100644 --- a/src/commands/nucleus/suggest.ts +++ b/src/commands/nucleus/suggest.ts @@ -1,9 +1,11 @@ -import type { CommandInteraction } from "discord.js"; -import type Discord from "discord.js"; +import { LoadingEmbed } from './../../utils/defaultEmbeds.js'; +import { ButtonStyle, CommandInteraction } from "discord.js"; +import Discord from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; +import getEmojiByName from '../../utils/getEmojiByName.js'; const command = (builder: SlashCommandSubcommandBuilder) => builder @@ -17,6 +19,7 @@ const callback = async (interaction: CommandInteraction): Promise => { await interaction.guild?.members.fetch(interaction.member!.user.id) const { renderUser } = client.logger; const suggestion = interaction.options.get("suggestion")?.value as string; + await interaction.reply({embeds: LoadingEmbed, ephemeral: true}) const confirmation = await new confirmationMessage(interaction) .setEmoji("ICONS.OPP.ADD") .setTitle("Suggest") @@ -26,7 +29,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ) .setColor("Danger") .setInverted(true) - .send(); + .send(true); if (confirmation.cancelled) return; if (confirmation.success) { await (client.channels.cache.get("955161206459600976") as Discord.TextChannel).send({ @@ -34,11 +37,22 @@ const callback = async (interaction: CommandInteraction): Promise => { new EmojiEmbed() .setTitle("Suggestion") .setDescription( - `**From:** ${renderUser(interaction.member!.user as Discord.User)}\n**Suggestion:**\n> ${suggestion}` + `**From:** ${renderUser(interaction.member!.user as Discord.User)}\n**Suggestion:**\n> ${suggestion}\n\n` + + `**Server:** ${interaction.guild!.name} (${interaction.guild!.id})\n`, ) - .setStatus("Danger") - .setEmoji("NUCLEUS.LOGO") - ] + .setStatus("Warning") + ], components: [new Discord.ActionRowBuilder().addComponents( + new Discord.ButtonBuilder() + .setCustomId("suggestionAccept") + .setLabel("Accept") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("ICONS.ADD", "id")), + new Discord.ButtonBuilder() + .setCustomId("suggestionDeny") + .setLabel("Delete") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("ICONS.REMOVE", "id")) + )] }); await interaction.editReply({ embeds: [ diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 62e9609..a22045b 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -5,7 +5,9 @@ import close from "../actions/tickets/delete.js"; import createTranscript from "../premium/createTranscript.js"; import type { Interaction } from "discord.js"; +import type Discord from "discord.js"; import type { NucleusClient } from "../utils/client.js"; +import EmojiEmbed from "../utils/generateEmojiEmbed.js"; export const event = "interactionCreate"; @@ -18,20 +20,29 @@ async function interactionCreate(interaction: Interaction) { case "createticket": { return await create(interaction); } case "closeticket": { return await close(interaction); } case "createtranscript": { return await createTranscript(interaction); } + case "suggestionAccept": { return await modifySuggestion(interaction, true); } + case "suggestionDeny": { return await modifySuggestion(interaction, false); } } - // } else if (interaction.type === "APPLICATION_COMMAND_AUTOCOMPLETE") { - // const int = interaction as AutocompleteInteraction; - // switch (`${int.commandName} ${int.options.getSubcommandGroup(false)} ${int.options.getSubcommand(false)}`) { - // case "settings null stats": { - // return int.respond(generateStatsChannelAutocomplete(int.options.getString("name") ?? "")); - // } - // case "settings null welcome": { - // return int.respond(generateWelcomeMessageAutocomplete(int.options.getString("message") ?? "")); - // } - // } } } +async function modifySuggestion(interaction: Discord.MessageComponentInteraction, accept: boolean) { + const message = await interaction.message; + await message.fetch(); + if (message.embeds.length === 0) return; + const embed = message.embeds[0]; + const newColour = accept ? "Success" : "Danger"; + const footer = {text: `Suggestion ${accept ? "accepted" : "denied"} by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL()}; + + const newEmbed = new EmojiEmbed() + .setTitle(embed!.title!) + .setDescription(embed!.description!) + .setFooter(footer) + .setStatus(newColour); + + await interaction.update({embeds: [newEmbed], components: []}); +} + export async function callback(_client: NucleusClient, interaction: Interaction) { await interactionCreate(interaction); } diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 6682be0..b3202e3 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -30,7 +30,9 @@ class confirmationMessage { title = ""; emoji = ""; redEmoji: string | null = null; - timeoutText: string | null = null; + failedMessage: string | null = null; + failedEmoji: string | null = null; + failedStatus: "Success" | "Danger" | "Warning" | null = null; description = ""; color: "Danger" | "Warning" | "Success" = "Success"; customButtons: Record> = {}; @@ -45,14 +47,13 @@ class confirmationMessage { this.title = title; return this; } - setEmoji(emoji: string, redEmoji?: string) { + setEmoji(emoji: string) { this.emoji = emoji; - if (redEmoji) this.redEmoji = redEmoji; // TODO: go through all confirmation messages and change them to use this, and not do their own edits return this; } setDescription(description: string, timedOut?: string) { this.description = description; - if (timedOut) this.timeoutText = timedOut; // TODO also this + if (timedOut) this.failedMessage = timedOut; return this; } setColor(color: "Danger" | "Warning" | "Success") { @@ -63,6 +64,12 @@ class confirmationMessage { this.inverted = inverted; return this; } + setFailedMessage(text: string, failedStatus: "Success" | "Danger" | "Warning" | null, failedEmoji: string | null = null) { + this.failedMessage = text; + this.failedStatus = failedStatus; + this.failedEmoji = failedEmoji; + return this; + } addCustomBoolean( customId: string, title: string, @@ -243,8 +250,7 @@ class confirmationMessage { out = await modalInteractionCollector( m, (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === - this.interaction.channelId, + (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === this.interaction.channelId, (m) => m.customId === "reason" ); } catch (e) { @@ -277,6 +283,17 @@ class confirmationMessage { await this.timeoutError() returnValue.cancelled = true; } + if (success == false) { + await this.interaction.editReply({ + embeds: [new EmojiEmbed() + .setTitle(this.title) + .setDescription(this.failedMessage ?? "") + .setStatus(this.failedStatus ?? "Danger") + .setEmoji(this.failedEmoji ?? this.redEmoji ?? this.emoji) + ], components: [] + }); + return {success: false} + } if (newReason) returnValue.newReason = newReason; const typedReturnValue = returnValue as {cancelled: true} | @@ -294,10 +311,10 @@ class confirmationMessage { .setTitle(this.title) .setDescription("We closed this message because it was not used for a while.") .setStatus("Danger") - .setEmoji(this.redEmoji ?? this.emoji) + .setEmoji("CONTROL.BLOCKCROSS") ], components: [] - }) + }) } } diff --git a/src/utils/performanceTesting/record.ts b/src/utils/performanceTesting/record.ts index 2d9524b..9f840af 100644 --- a/src/utils/performanceTesting/record.ts +++ b/src/utils/performanceTesting/record.ts @@ -1,7 +1,9 @@ import client from "../client.js"; -import { resourceUsage } from "process"; -import { spawn } from "child_process"; +import * as CP from 'child_process'; +import * as process from 'process'; +import systeminformation from "systeminformation"; import config from "../../config/main.json" assert { type: "json" }; +import singleNotify from "../singleNotify.js"; const discordPing = () => { @@ -18,17 +20,11 @@ const databaseReadTime = async () => { 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 - }) +const resources = async () => { return { - memory: current.sharedMemorySize, - cpu: current.userCPUTime + current.systemCPUTime, - temperature: temperatureData + memory: process.memoryUsage().rss / 1024 / 1024, + cpu: parseFloat(CP.execSync(`ps -p ${process.pid} -o %cpu=`).toString().trim()), + temperature: (await systeminformation.cpuTemperature()).main } } @@ -36,12 +32,24 @@ const record = async () => { const results = { discord: discordPing(), databaseRead: await databaseReadTime(), - resources: resources() + resources: await resources() + }; + if (results.discord > 1000 || results.databaseRead > 500 || results.resources.cpu > 100) { + singleNotify( + "performanceTest", + config.developmentGuildID, + `Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${results.resources.memory}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``, + "Critical", + config.owners + ) + } else { + singleNotify("performanceTest", config.developmentGuildID, true) } + client.database.performanceTest.record(results) - setInterval(async () => { - record(); - }, 10 * 1000); + setTimeout(async () => { + await record(); + }, 60 * 1000); } -export { record }; \ No newline at end of file +export { record }; diff --git a/src/utils/singleNotify.ts b/src/utils/singleNotify.ts index e762487..8e3aa60 100644 --- a/src/utils/singleNotify.ts +++ b/src/utils/singleNotify.ts @@ -13,7 +13,8 @@ export default async function ( type: string, guild: string, message: string | true, - severity: "Critical" | "Warning" | "Info" = "Info" + severity: "Critical" | "Warning" | "Info" = "Info", + pings?: string[] ) { const data = await client.database.guilds.read(guild); if (data.logging.staff.channel === null) return; @@ -30,6 +31,11 @@ export default async function ( const channel = await client.channels.fetch(data.logging.staff.channel); if (!channel) return; if (!channel.isTextBased()) return; + if (pings) { + await channel.send({ + content: pings.map((ping) => `<@${ping}>`).join(" ") + }); + } await channel.send({ embeds: [ new EmojiEmbed()