From 5bea7e1286535d5fcf7f5e481c6da3f040321513 Mon Sep 17 00:00:00 2001 From: PineaFan Date: Thu, 5 Jan 2023 21:20:04 +0000 Subject: [PATCH] More errors fixed --- src/commands/mod/info.ts | 41 ++--- src/commands/mod/mute.ts | 308 +++++++++++++++++----------------- src/commands/ticket/close.ts | 4 +- src/commands/ticket/create.ts | 4 +- src/reflex/welcome.ts | 10 +- src/utils/getEmojiByName.ts | 1 - 6 files changed, 188 insertions(+), 180 deletions(-) diff --git a/src/commands/mod/info.ts b/src/commands/mod/info.ts index 61a01e2..6b5c81e 100644 --- a/src/commands/mod/info.ts +++ b/src/commands/mod/info.ts @@ -12,9 +12,8 @@ import Discord, { ButtonStyle, StringSelectMenuInteraction, TextInputStyle, - APIMessageComponentEmoji } from "discord.js"; -import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import client from "../../utils/client.js"; @@ -158,26 +157,19 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte let components: (ActionRowBuilder | ActionRowBuilder)[] = [] if (openFilterPane) components = components.concat([ new ActionRowBuilder().addComponents( - new Discord.StringSelectMenuBuilder().setOptions( - // ...Object.entries(types).map(([key, value]) => new StringSelectMenuOptionBuilder() - // .setLabel(value.text) - // .setValue(key) - // .setDefault(filteredTypes.includes(key)) - // .setEmoji(client.emojis.resolve(getEmojiByName(value.emoji, "id"))! as APIMessageComponentEmoji) - // ) - ...Object.entries(types).map(([key, value]) => ({ - label: value.text, - value: key, - default: filteredTypes.includes(key), - emoji: client.emojis.resolve(getEmojiByName(value.emoji, "id"))! as APIMessageComponentEmoji - })) - ) - .setMinValues(1) - .setMaxValues(Object.keys(types).length) - .setCustomId("filter") - .setPlaceholder("Select events to show") - ) - ]) + new Discord.StringSelectMenuBuilder() + .setMinValues(1) + .setMaxValues(Object.keys(types).length) + .setCustomId("filter") + .setPlaceholder("Select events to show") + .setOptions(...Object.entries(types).map(([key, value]) => new Discord.StringSelectMenuOptionBuilder() + .setLabel(value.text) + .setValue(key) + .setDefault(filteredTypes.includes(key)) + // @ts-expect-error + .setEmoji(getEmojiByName(value.emoji, "id")) // FIXME: This gives a type error but is valid + ))) + ]); components = components.concat([new ActionRowBuilder().addComponents([ new ButtonBuilder() .setCustomId("prevYear") @@ -188,7 +180,10 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte .setCustomId("prevPage") .setLabel("Previous page") .setStyle(ButtonStyle.Primary), - new ButtonBuilder().setCustomId("today").setLabel("Today").setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId("today") + .setLabel("Today") + .setStyle(ButtonStyle.Primary), new ButtonBuilder() .setCustomId("nextPage") .setLabel("Next page") diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index 95c8c5a..05a9ec2 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -1,5 +1,5 @@ -import { LoadingEmbed } from "./../../utils/defaultEmbeds.js"; -import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; +import { LinkWarningFooter, LoadingEmbed } from "./../../utils/defaultEmbeds.js"; +import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; @@ -48,15 +48,16 @@ const command = (builder: SlashCommandSubcommandBuilder) => .setRequired(false) ); + const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; const { log, NucleusColors, renderUser, entry, renderDelta } = client.logger; - const user = interaction.options.getMember("user") as GuildMember; - const time = { - days: interaction.options.getInteger("days") ?? 0, - hours: interaction.options.getInteger("hours") ?? 0, - minutes: interaction.options.getInteger("minutes") ?? 0, - seconds: interaction.options.getInteger("seconds") ?? 0 + const member = interaction.options.getMember("user") as GuildMember; + const time: {days: number, hours: number, minutes: number, seconds: number} = { + days: (interaction.options.get("days")?.value as number | null) ?? 0, + hours: (interaction.options.get("hours")?.value as number | null) ?? 0, + minutes: (interaction.options.get("minutes")?.value as number | null) ?? 0, + seconds: (interaction.options.get("seconds")?.value as number | null) ?? 0 }; const config = await client.database.guilds.read(interaction.guild.id); let serverSettingsDescription = config.moderation.mute.timeout ? "given a timeout" : ""; @@ -66,28 +67,28 @@ const callback = async (interaction: CommandInteraction): Promise => { let muteTime = time.days * 24 * 60 * 60 + time.hours * 60 * 60 + time.minutes * 60 + time.seconds; if (muteTime === 0) { - const m = (await interaction.reply({ + const m = await interaction.reply({ embeds: [ new EmojiEmbed() .setEmoji("PUNISH.MUTE.GREEN") .setTitle("Mute") - .setDescription("How long should the user be muted") + .setDescription("How long should the user be muted for?") .setStatus("Success") ], components: [ - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents([ new Discord.ButtonBuilder().setCustomId("1m").setLabel("1 Minute").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("10m").setLabel("10 Minutes").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("30m").setLabel("30 Minutes").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("1h").setLabel("1 Hour").setStyle(ButtonStyle.Secondary) ]), - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents([ new Discord.ButtonBuilder().setCustomId("6h").setLabel("6 Hours").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("12h").setLabel("12 Hours").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("1d").setLabel("1 Day").setStyle(ButtonStyle.Secondary), new Discord.ButtonBuilder().setCustomId("1w").setLabel("1 Week").setStyle(ButtonStyle.Secondary) ]), - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents([ new Discord.ButtonBuilder() .setCustomId("cancel") .setLabel("Cancel") @@ -97,7 +98,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ], ephemeral: true, fetchReply: true - })) as Message; + }); let component; try { component = await m.awaitMessageComponent({ @@ -172,7 +173,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setTitle("Mute") .setDescription( keyValueList({ - user: renderUser(user.user), + user: renderUser(member.user), time: `${humanizeDuration(muteTime * 1000, { round: true })}`, @@ -180,17 +181,15 @@ const callback = async (interaction: CommandInteraction): Promise => { }) + "The user will be " + serverSettingsDescription + - "\n" + - `The user **will${notify ? "" : " not"}** be notified\n\n` + - `Are you sure you want to mute <@!${user.id}>?` + "\n\n" + + `Are you sure you want to mute <@!${member.id}>?` ) .setColor("Danger") .addCustomBoolean( "appeal", "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)), - async () => - await create(interaction.guild, interaction.options.getUser("user")!, interaction.user, reason), + async () => await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason), "An appeal ticket will be created when Confirm is clicked", null, "CONTROL.TICKET", @@ -200,8 +199,8 @@ const callback = async (interaction: CommandInteraction): Promise => { "notify", "Notify user", false, - undefined, null, + "The user will be sent a DM", null, "ICONS.NOTIFY." + (notify ? "ON" : "OFF"), notify @@ -210,163 +209,172 @@ const callback = async (interaction: CommandInteraction): Promise => { .send(true); reason = reason ?? ""; if (confirmation.cancelled) timedOut = true; - if (confirmation.success) success = true; - if (confirmation.newReason) reason = confirmation.newReason; - if (confirmation.components) { + else if (confirmation.success) success = true; + else if (confirmation.newReason) reason = confirmation.newReason; + else if (confirmation.components) { notify = confirmation.components["notify"]!.active; createAppealTicket = confirmation.components["appeal"]!.active; } } while (!timedOut && !success) if (timedOut) return; - let dmd = false; - let dm; - if (confirmation.success) { - try { - if (notify) { - dm = await user.send({ - embeds: [ - new EmojiEmbed() - .setEmoji("PUNISH.MUTE.RED") - .setTitle("Muted") - .setDescription( - `You have been muted in ${interaction.guild.name}` + - (reason - ? ` for:\n> ${reason}` - : ".\n\n" + - `You will be unmuted at: at ()`) + - (confirmation.components!["appeal"]!.response - ? `You can appeal this here: <#${confirmation.components!["appeal"]!.response}>` - : "") - ) - .setStatus("Danger") - ], - components: [ - new ActionRowBuilder().addComponents( - config.moderation.mute.text - ? [ - new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setLabel(config.moderation.mute.text) - .setURL(config.moderation.mute.link) - ] - : [] + if (!confirmation.success) { + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setEmoji("PUNISH.BAN.GREEN") + .setTitle("Softban") + .setDescription("No changes were made") + .setStatus("Success") + ], + components: [] + }); + return; + } + const status: {timeout: boolean | null, role: boolean | null, dm: boolean | null} = {timeout: null, role: null, dm: null}; + let dmMessage; + try { + if (notify) { + const messageData: { + embeds: EmojiEmbed[]; + components: ActionRowBuilder[]; + } = { + embeds: [ + new EmojiEmbed() + .setEmoji("PUNISH.MUTE.RED") + .setTitle("Muted") + .setDescription( + `You have been muted in ${interaction.guild.name}` + + (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided*") + "\n\n" + + `You will be unmuted at: at ` + + ` ()` + "\n\n" + + (createAppealTicket + ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>` + : "") ) - ] - }); - dmd = true; - } - } catch { - dmd = false; - } - const member = user; - let errors = 0; - try { - if (config.moderation.mute.timeout) { - await member.timeout(muteTime * 1000, reason || "*No reason provided*"); - if (config.moderation.mute.role !== null) { - await member.roles.add(config.moderation.mute.role); - await client.database.eventScheduler.schedule("naturalUnmute", new Date().getTime() + muteTime * 1000, { - guild: interaction.guild.id, - user: user.id, - expires: new Date().getTime() + muteTime * 1000 - }); - } + .setStatus("Danger") + ], + components: [] } - } catch { - errors++; + if (config.moderation.mute.text && config.moderation.mute.link) { + messageData.embeds[0]!.setFooter(LinkWarningFooter); + messageData.components.push(new ActionRowBuilder() + .addComponents(new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel(config.moderation.mute.text) + .setURL(config.moderation.mute.link) + ) + ) + }; + dmMessage = await member.send(messageData); + status.dm = true; } - try { + } catch { + status.dm = false; + } + try { + if (config.moderation.mute.timeout) { + await member.timeout(muteTime * 1000, reason || "*No reason provided*"); if (config.moderation.mute.role !== null) { await member.roles.add(config.moderation.mute.role); - await client.database.eventScheduler.schedule("unmuteRole", new Date().getTime() + muteTime * 1000, { + await client.database.eventScheduler.schedule("naturalUnmute", (new Date().getTime() + muteTime * 1000).toString(), { guild: interaction.guild.id, - user: user.id, - role: config.moderation.mute.role + user: member.id, + expires: new Date().getTime() + muteTime * 1000 }); } - } catch (e) { - console.log(e); - errors++; + } else { + status.timeout = true; } - if (errors === 2) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("PUNISH.MUTE.RED") - .setTitle("Mute") - .setDescription("Something went wrong and the user was not muted") - .setStatus("Danger") - ], - components: [] - }); // TODO: make this clearer - if (dmd && dm) await dm.delete(); - return; + } catch { + status.timeout = false; + } + try { + if (config.moderation.mute.role !== null) { + await member.roles.add(config.moderation.mute.role); + await client.database.eventScheduler.schedule("unmuteRole", (new Date().getTime() + muteTime * 1000).toString(), { + guild: interaction.guild.id, + user: member.id, + role: config.moderation.mute.role + }); + } else { + status.role = true; } - await client.database.history.create("mute", interaction.guild.id, member.user, interaction.user, reason); - const failed = !dmd && notify; + } catch { + status.role = false; + } + const countTrue = (items: (boolean | null)[]) => items.filter(item => item === true).length; + const requiredPunishments = countTrue([config.moderation.mute.timeout, config.moderation.mute.role !== null]); + const actualPunishments = countTrue([status.timeout, status.role]); + + await client.database.history.create("mute", interaction.guild.id, member.user, interaction.user, reason); + if (requiredPunishments !== actualPunishments) { + const messages = []; + if (config.moderation.mute.timeout) messages.push(`The member was ${status.timeout ? "" : "not "}timed out`); + if (config.moderation.mute.role !== null) messages.push(`The member was ${status.role ? "" : "not "}given the mute role`); + messages.push(`The member was not sent a DM`); + if (dmMessage && actualPunishments === 0) await dmMessage.delete(); await interaction.editReply({ embeds: [ new EmojiEmbed() - .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`) + .setEmoji("PUNISH.MUTE." + (actualPunishments > 0 ? "YELLOW" : "RED")) .setTitle("Mute") .setDescription( - "The member was muted" + - (failed ? ", but could not be notified" : "") + - (confirmation.components!["appeal"]!.response - ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>` - : "") + "Mute " + (actualPunishments > 0 ? "partially" : "failed") + ":\n" + + messages.map(message => `> ${message}`).join("\n") ) - .setStatus(failed ? "Warning" : "Success") - ], - components: [] - }); - const data = { - meta: { - type: "memberMute", - displayName: "Member Muted", - calculateType: "guildMemberPunish", - color: NucleusColors.yellow, - emoji: "PUNISH.WARN.YELLOW", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(member.user.id, `\`${member.user.id}\``), - name: entry(member.user.id, renderUser(member.user)), - mutedUntil: entry( - new Date().getTime() + muteTime * 1000, - renderDelta(new Date().getTime() + muteTime * 1000) - ), - muted: entry(new Date().getTime(), renderDelta(new Date().getTime() - 1000)), - mutedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user)), - reason: entry(reason, reason ? reason : "*No reason provided*") - }, - hidden: { - guild: interaction.guild.id - } - }; - log(data); - } else { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("PUNISH.BAN.GREEN") - .setTitle("Softban") - .setDescription("No changes were made") - .setStatus("Success") + .setStatus(actualPunishments > 0 ? "Warning" : "Danger") ], components: [] }); } + const data = { + meta: { + type: "memberMute", + displayName: "Member Muted", + calculateType: "guildMemberPunish", + color: NucleusColors.yellow, + emoji: "PUNISH.WARN.YELLOW", + timestamp: new Date().getTime() + }, + list: { + memberId: entry(member.user.id, `\`${member.user.id}\``), + name: entry(member.user.id, renderUser(member.user)), + mutedUntil: entry( + (new Date().getTime() + muteTime * 1000).toString(), + renderDelta(new Date().getTime() + muteTime * 1000) + ), + muted: entry(new Date().getTime.toString(), renderDelta(new Date().getTime() - 1000)), + mutedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)), + reason: entry(reason, reason ? reason : "*No reason provided*") + }, + hidden: { + guild: interaction.guild.id + } + }; + log(data); + const failed = !status.dm && notify; + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`) + .setTitle("Mute") + .setDescription( + "The member was muted" + (failed ? ", but could not be notified" : "") + + (createAppealTicket + ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>` + : "") + ) + .setStatus(failed ? "Warning" : "Success") + ], + components: [] + }); }; const check = (interaction: CommandInteraction) => { if (!interaction.guild) return; 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; const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0; @@ -376,13 +384,13 @@ const check = (interaction: CommandInteraction) => { // Check if Nucleus can mute the member if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member"); // Check if Nucleus has permission to mute - if (!me.permissions.has("MODERATE_MEMBERS")) throw new Error("I do not have the *Moderate Members* permission"); + if (!me.permissions.has("ModerateMembers")) throw new Error("I do not have the *Moderate Members* permission"); // Do not allow muting Nucleus if (member.id === me.id) throw new Error("I cannot mute myself"); // Allow the owner to mute anyone if (member.id === interaction.guild.ownerId) return true; // Check if the user has moderate_members permission - if (!member.permissions.has("MODERATE_MEMBERS")) + if (!member.permissions.has("ModerateMembers")) throw new Error("You do not have the *Moderate Members* permission"); // Check if the user is below on the role list if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member"); diff --git a/src/commands/ticket/close.ts b/src/commands/ticket/close.ts index e2efcc3..d2ffaf9 100644 --- a/src/commands/ticket/close.ts +++ b/src/commands/ticket/close.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 close from "../../actions/tickets/delete.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("close").setDescription("Closes a ticket"); diff --git a/src/commands/ticket/create.ts b/src/commands/ticket/create.ts index 3d0b5ce..91442b5 100644 --- a/src/commands/ticket/create.ts +++ b/src/commands/ticket/create.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 create from "../../actions/tickets/create.js"; const command = (builder: SlashCommandSubcommandBuilder) => diff --git a/src/reflex/welcome.ts b/src/reflex/welcome.ts index 47ed140..7722086 100644 --- a/src/reflex/welcome.ts +++ b/src/reflex/welcome.ts @@ -3,6 +3,7 @@ import convertCurlyBracketString from "../utils/convertCurlyBracketString.js"; import client from "../utils/client.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import { GuildChannel, GuildMember, BaseGuildTextChannel } from "discord.js"; +import singleNotify from "../utils/singleNotify.js"; export async function callback(_client: NucleusClient, member: GuildMember) { if (member.user.bot) return; @@ -24,7 +25,7 @@ export async function callback(_client: NucleusClient, member: GuildMember) { embeds: [new EmojiEmbed().setDescription(string).setStatus("Success")] }); } else { - const channel: GuildChannel | null = await member.guild.channels.fetch(config.welcome.channel); + const channel: GuildChannel | null = await member.guild.channels.fetch(config.welcome.channel) as GuildChannel | null; if (!channel) return; // TODO: SEN if (!(channel instanceof BaseGuildTextChannel)) return; if (channel.guild.id !== member.guild.id) return; @@ -34,7 +35,12 @@ export async function callback(_client: NucleusClient, member: GuildMember) { content: (config.welcome.ping ? `<@${config.welcome.ping}>` : "") + `<@${member.id}>` }); } catch (err) { - console.error(err); // TODO: SEN + singleNotify( + "welcomeChannelDeleted", + member.guild.id, + "The welcome channel has been deleted or is no longer accessible. Use /settings welcome to set a new one", + "Warning" + ) } } } diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts index 350ed4c..3fa2b53 100644 --- a/src/utils/getEmojiByName.ts +++ b/src/utils/getEmojiByName.ts @@ -5,7 +5,6 @@ interface EmojisIndex { } function getEmojiByName(name: string | null, format?: string): string { - console.log(name) if (!name) return ""; const parts = name.split("."); let id: string | EmojisIndex | EmojisIndex[] | undefined = emojis;