From 62ce1921351db574ed6e2b2e78697acff91a13ca Mon Sep 17 00:00:00 2001 From: pineafan Date: Thu, 25 Aug 2022 20:34:45 +0100 Subject: [PATCH] typing again --- src/actions/createModActionTicket.ts | 4 +- src/commands/mod/ban.ts | 210 +++++++++-------- src/commands/mod/info.ts | 3 +- src/commands/mod/kick.ts | 49 ++-- src/commands/mod/mute.ts | 274 ++++++++++++----------- src/commands/mod/nick.ts | 6 +- src/commands/mod/softban.ts | 6 +- src/commands/mod/unmute.ts | 6 +- src/commands/mod/warn.ts | 8 +- src/commands/settings/commands.ts | 11 +- src/commands/settings/logs/attachment.ts | 2 +- src/commands/settings/logs/channel.ts | 2 +- src/commands/settings/logs/staff.ts | 2 +- src/commands/settings/stats.ts | 2 +- src/commands/settings/tickets.ts | 10 +- src/commands/settings/verify.ts | 2 +- src/commands/settings/welcome.ts | 2 +- src/commands/tags/create.ts | 2 +- src/commands/tags/delete.ts | 2 +- src/commands/tags/edit.ts | 2 +- src/utils/confirmationMessage.ts | 28 ++- 21 files changed, 343 insertions(+), 290 deletions(-) diff --git a/src/actions/createModActionTicket.ts b/src/actions/createModActionTicket.ts index 0eca621..365c5e3 100644 --- a/src/actions/createModActionTicket.ts +++ b/src/actions/createModActionTicket.ts @@ -7,7 +7,7 @@ export async function create( guild: Discord.Guild, member: Discord.User, createdBy: Discord.User, - reason: string, + reason: string | null, customReason?: string ) { const config = await client.database.guilds.read(guild.id); @@ -26,7 +26,7 @@ export async function create( }); if (config.tickets.supportRole !== null) { overwrites.push({ - id: guild.roles.cache.get(config.tickets.supportRole), + id: guild.roles.cache.get(config.tickets.supportRole)!, allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"], type: "role" }); diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 9a82970..3aea8c8 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -1,4 +1,4 @@ -import { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js"; +import { CommandInteraction, GuildMember, MessageActionRow, MessageButton, User } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; @@ -26,143 +26,171 @@ const callback = async (interaction: CommandInteraction): Promise => { let reason = null; let notify = true; let confirmation; - let cancelled = false; + let chosen = false; let timedOut = false; - while (!timedOut && !cancelled) { + do { confirmation = await new confirmationMessage(interaction) .setEmoji("PUNISH.BAN.RED") .setTitle("Ban") .setDescription( keyValueList({ user: renderUser(interaction.options.getUser("user")), - reason: reason ? "\n> " + (reason ?? "").replaceAll("\n", "\n> ") : "*No reason provided*" + reason: reason ? "\n> " + (reason).replaceAll("\n", "\n> ") : "*No reason provided*" }) + `The user **will${notify ? "" : " not"}** be notified\n` + `${addPlurals( - interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, + interaction.options.getNumber("delete") ?? 0, "day" )} of messages will be deleted\n\n` + `Are you sure you want to ban <@!${(interaction.options.getMember("user") as GuildMember).id}>?` ) + .addCustomBoolean( + "notify", + "Notify user", + false, + undefined, + null, + "ICONS.NOTIFY." + (notify ? "ON" : "OFF"), + notify + ) .setColor("Danger") .addReasonButton(reason ?? "") .send(reason !== null); reason = reason ?? ""; if (confirmation.cancelled) timedOut = true; - else if (confirmation.success) cancelled = true; + else if (confirmation.success !== undefined) chosen = true; else if (confirmation.newReason) reason = confirmation.newReason; - else if (confirmation.components) notify = confirmation.components.notify.active; - } + else if (confirmation.components) notify = confirmation.components["notify"]!.active; + } while (!timedOut && !chosen) if (timedOut) return; - let dmd = false; - let dm; - const config = await client.database.guilds.read(interaction.guild.id); - try { - if (notify) { - dm = await (interaction.options.getMember("user") as GuildMember).send({ + if (confirmation.success) { + reason = reason.length ? reason : null + let dmd = false; + let dm; + const config = await client.database.guilds.read(interaction.guild!.id); + try { + if (notify) { + dm = await (interaction.options.getMember("user") as GuildMember).send({ + embeds: [ + new EmojiEmbed() + .setEmoji("PUNISH.BAN.RED") + .setTitle("Banned") + .setDescription( + `You have been banned in ${interaction.guild!.name}` + (reason ? ` for:\n> ${reason}` : ".") + ) + .setStatus("Danger") + ], + components: [ + new MessageActionRow().addComponents( + config.moderation.ban.text + ? [ + new MessageButton() + .setStyle("LINK") + .setLabel(config.moderation.ban.text) + .setURL(config.moderation.ban.link) + ] + : [] + ) + ] + }); + dmd = true; + } + } catch { + dmd = false; + } + try { + const member = interaction.options.getMember("user") as GuildMember; + member.ban({ + days: Number(interaction.options.getNumber("delete") ?? 0), + 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(), 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, renderDelta(member.user.createdAt)), + serverMemberCount: interaction.guild!.memberCount + }, + hidden: { + guild: interaction.guild!.id + } + }; + log(data); + } catch { + await interaction.editReply({ embeds: [ new EmojiEmbed() .setEmoji("PUNISH.BAN.RED") - .setTitle("Banned") - .setDescription( - `You have been banned in ${interaction.guild.name}` + (reason ? ` for:\n> ${reason}` : ".") - ) + .setTitle("Ban") + .setDescription("Something went wrong and the user was not banned") .setStatus("Danger") ], - components: [ - new MessageActionRow().addComponents( - config.moderation.ban.text - ? [ - new MessageButton() - .setStyle("LINK") - .setLabel(config.moderation.ban.text) - .setURL(config.moderation.ban.link) - ] - : [] - ) - ] + components: [] }); - dmd = true; + if (dmd && dm) await dm.delete(); + return; } - } catch { - dmd = false; - } - try { - const member = interaction.options.getMember("user") as GuildMember; - member.ban({ - days: Number(interaction.options.getNumber("delete") ?? 0), - reason: reason ?? "No reason provided" + const failed = !dmd && 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: [] }); - 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(), 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, renderDelta(member.user.createdAt)), - serverMemberCount: interaction.guild.memberCount - }, - hidden: { - guild: interaction.guild.id - } - }; - log(data); - } catch { + } else { await interaction.editReply({ embeds: [ new EmojiEmbed() - .setEmoji("PUNISH.BAN.RED") + .setEmoji("PUNISH.BAN.GREEN") .setTitle("Ban") - .setDescription("Something went wrong and the user was not banned") - .setStatus("Danger") + .setDescription("No changes were made") + .setStatus("Success") ], components: [] }); - if (dmd) await dm.delete(); - return; } - const failed = !dmd && 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 = (interaction: CommandInteraction) => { +const check = async (interaction: CommandInteraction) => { const member = interaction.member as GuildMember; - const me = interaction.guild.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 ? member.roles.highest.position : 0; - const mePos = me.roles ? me.roles.highest.position : 0; - const applyPos = apply.roles ? apply.roles.highest.position : 0; + const me = interaction.guild!.me!; + let apply = interaction.options.getUser("user") as User | GuildMember; + const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; + const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0; + let applyPos = 0 + try { + apply = await interaction.guild!.members.fetch(apply.id) as GuildMember + applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0; + } catch { + apply = apply as User + } // Do not allow banning the owner - if (member.id === interaction.guild.ownerId) throw new Error("You cannot ban the owner of the server"); + if (member.id === interaction.guild!.ownerId) throw new Error("You cannot ban the owner of the server"); // Check if Nucleus can ban the member if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member"); // Check if Nucleus has permission to ban if (!me.permissions.has("BAN_MEMBERS")) throw new Error("I do not have the *Ban Members* permission"); // Do not allow banning Nucleus - if (member.id === interaction.guild.me.id) throw new Error("I cannot ban myself"); + if (member.id === interaction.guild!.me!.id) throw new Error("I cannot ban myself"); // Allow the owner to ban anyone - if (member.id === interaction.guild.ownerId) return true; + if (member.id === interaction.guild!.ownerId) return true; // Check if the user has ban_members permission if (!member.permissions.has("BAN_MEMBERS")) throw new Error("You do not have the *Ban Members* permission"); // Check if the user is below on the role list diff --git a/src/commands/mod/info.ts b/src/commands/mod/info.ts index da2d540..bc5dd14 100644 --- a/src/commands/mod/info.ts +++ b/src/commands/mod/info.ts @@ -8,6 +8,7 @@ import Discord, { MessageButton, MessageComponentInteraction, ModalSubmitInteraction, + SelectMenuInteraction, TextInputComponent } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; @@ -256,7 +257,7 @@ async function showHistory(member: Discord.GuildMember, interaction: CommandInte } i.deferUpdate(); if (i.customId === "filter") { - filteredTypes = i.values; + filteredTypes = (i as SelectMenuInteraction).values; pageIndex = null; refresh = true; } diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index ef58067..3ca56a6 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -1,6 +1,7 @@ import { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js"; +// @ts-expect-error import humanizeDuration from "humanize-duration"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -19,15 +20,15 @@ const callback = async (interaction: CommandInteraction): Promise => { let notify = true; let confirmation; let timedOut = false; - let success = false; - while (!timedOut && !success) { + let chosen = false; + while (!timedOut && !chosen) { confirmation = await new confirmationMessage(interaction) .setEmoji("PUNISH.KICK.RED") .setTitle("Kick") .setDescription( keyValueList({ user: renderUser(interaction.options.getUser("user")), - reason: reason ? "\n> " + (reason ?? "").replaceAll("\n", "\n> ") : "*No reason provided*" + reason: reason ? "\n> " + (reason).replaceAll("\n", "\n> ") : "*No reason provided*" }) + `The user **will${notify ? "" : " not"}** be notified\n\n` + `Are you sure you want to kick <@!${(interaction.options.getMember("user") as GuildMember).id}>?` @@ -37,16 +38,16 @@ const callback = async (interaction: CommandInteraction): Promise => { .send(reason !== null); reason = reason ?? ""; if (confirmation.cancelled) timedOut = true; - else if (confirmation.success) success = true; + else if (confirmation.success !== undefined) chosen = true; else if (confirmation.newReason) reason = confirmation.newReason; else if (confirmation.components) { - notify = confirmation.components.notify.active; + notify = confirmation.components["notify"]!.active; } } if (timedOut) return; let dmd = false; let dm; - const config = await client.database.guilds.read(interaction.guild.id); + const config = await client.database.guilds.read(interaction.guild!.id); try { if (notify) { dm = await (interaction.options.getMember("user") as GuildMember).send({ @@ -55,7 +56,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setEmoji("PUNISH.KICK.RED") .setTitle("Kicked") .setDescription( - `You have been kicked in ${interaction.guild.name}` + (reason ? ` for:\n> ${reason}` : ".") + `You have been kicked in ${interaction.guild!.name}` + (reason ? ` for:\n> ${reason}` : ".") ) .setStatus("Danger") ], @@ -80,8 +81,14 @@ const callback = async (interaction: CommandInteraction): Promise => { try { (interaction.options.getMember("user") as GuildMember).kick(reason ?? "No reason provided."); const member = interaction.options.getMember("user") as GuildMember; - await client.database.history.create("kick", interaction.guild.id, member.user, interaction.user, reason); + await client.database.history.create("kick", interaction.guild!.id, member.user, interaction.user, reason); const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger; + const timeInServer = member.joinedTimestamp ? entry( + new Date().getTime() - member.joinedTimestamp, + humanizeDuration(new Date().getTime() - member.joinedTimestamp, { + round: true + }) + ) : entry(null, "*Unknown*") const data = { meta: { type: "memberKick", @@ -98,12 +105,7 @@ const callback = async (interaction: CommandInteraction): Promise => { kicked: entry(new Date().getTime(), renderDelta(new Date().getTime())), kickedBy: entry(interaction.user.id, renderUser(interaction.user)), reason: entry(reason, reason ? `\n> ${reason}` : "*No reason provided.*"), - timeInServer: entry( - new Date().getTime() - member.joinedTimestamp, - humanizeDuration(new Date().getTime() - member.joinedTimestamp, { - round: true - }) - ), + timeInServer: timeInServer, accountCreated: entry(member.user.createdAt, renderDelta(member.user.createdAt)), serverMemberCount: member.guild.memberCount }, @@ -123,7 +125,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ], components: [] }); - if (dmd) await dm.delete(); + if (dmd && dm) await dm.delete(); return; } const failed = !dmd && notify; @@ -141,22 +143,21 @@ 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!.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 ? member.roles.highest.position : 0; - const mePos = me.roles ? me.roles.highest.position : 0; - const applyPos = apply.roles ? apply.roles.highest.position : 0; + const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; + const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0; + const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0; // Do not allow kicking the owner - if (member.id === interaction.guild.ownerId) throw new Error("You cannot kick the owner of the server"); + if (member.id === interaction.guild!.ownerId) throw new Error("You cannot kick the owner of the server"); // Check if Nucleus can kick the member if (!(mePos > applyPos)) throw new Error("I do not have a role higher than that member"); // Check if Nucleus has permission to kick if (!me.permissions.has("KICK_MEMBERS")) throw new Error("I do not have the *Kick Members* permission"); // Do not allow kicking Nucleus - if (member.id === interaction.guild.me.id) throw new Error("I cannot kick myself"); + if (member.id === interaction.guild!.me!.id) throw new Error("I cannot kick myself"); // Allow the owner to kick anyone - if (member.id === interaction.guild.ownerId) return true; + if (member.id === interaction.guild!.ownerId) return true; // Check if the user has kick_members permission if (!member.permissions.has("KICK_MEMBERS")) throw new Error("You do not have the *Kick Members* permission"); // Check if the user is below on the role list diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index 327ecdf..2b01ffc 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -1,10 +1,11 @@ import { LoadingEmbed } from "./../../utils/defaultEmbeds.js"; import Discord, { CommandInteraction, GuildMember, Message, MessageActionRow, MessageButton } from "discord.js"; -import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import keyValueList from "../../utils/generateKeyValueList.js"; +// @ts-expect-error import humanizeDuration from "humanize-duration"; import client from "../../utils/client.js"; import { areTicketsEnabled, create } from "../../actions/createModActionTicket.js"; @@ -56,7 +57,7 @@ const callback = async (interaction: CommandInteraction): Promise => { minutes: interaction.options.getInteger("minutes") ?? 0, seconds: interaction.options.getInteger("seconds") ?? 0 }; - const config = await client.database.guilds.read(interaction.guild.id); + const config = await client.database.guilds.read(interaction.guild!.id); let serverSettingsDescription = config.moderation.mute.timeout ? "given a timeout" : ""; if (config.moderation.mute.role) serverSettingsDescription += @@ -158,13 +159,13 @@ const callback = async (interaction: CommandInteraction): Promise => { }); } // TODO:[Modals] Replace this with a modal - let reason = null; + let reason: string | null = null; let notify = true; let createAppealTicket = false; let confirmation; let timedOut = false; let success = false; - while (!timedOut && !success) { + do { confirmation = await new confirmationMessage(interaction) .setEmoji("PUNISH.MUTE.RED") .setTitle("Mute") @@ -174,7 +175,7 @@ const callback = async (interaction: CommandInteraction): Promise => { time: `${humanizeDuration(muteTime * 1000, { round: true })}`, - reason: reason ? "\n> " + (reason ?? "").replaceAll("\n", "\n> ") : "*No reason provided*" + reason: reason ? "\n> " + (reason).replaceAll("\n", "\n> ") : "*No reason provided*" }) + "The user will be " + serverSettingsDescription + @@ -186,9 +187,9 @@ const callback = async (interaction: CommandInteraction): Promise => { .addCustomBoolean( "appeal", "Create appeal ticket", - !(await areTicketsEnabled(interaction.guild.id)), + !(await areTicketsEnabled(interaction.guild!.id)), async () => - await create(interaction.guild, interaction.options.getUser("user"), interaction.user, reason), + await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason), "An appeal ticket will be created when Confirm is clicked", "CONTROL.TICKET", createAppealTicket @@ -197,7 +198,7 @@ const callback = async (interaction: CommandInteraction): Promise => { "notify", "Notify user", false, - null, + undefined, null, "ICONS.NOTIFY." + (notify ? "ON" : "OFF"), notify @@ -209,154 +210,165 @@ const callback = async (interaction: CommandInteraction): Promise => { if (confirmation.success) success = true; if (confirmation.newReason) reason = confirmation.newReason; if (confirmation.components) { - notify = confirmation.components.notify.active; - createAppealTicket = confirmation.components.appeal.active; + notify = confirmation.components["notify"]!.active; + createAppealTicket = confirmation.components["appeal"]!.active; } - } + } while (!timedOut && !success) if (timedOut) return; let dmd = false; let dm; - const config = await client.database.guilds.read(interaction.guild.id); - 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}>` - : "") + 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 MessageActionRow().addComponents( + config.moderation.mute.text + ? [ + new MessageButton() + .setStyle("LINK") + .setLabel(config.moderation.mute.text) + .setURL(config.moderation.mute.link) + ] + : [] ) - .setStatus("Danger") - ], - components: [ - new MessageActionRow().addComponents( - config.moderation.mute.text - ? [ - new MessageButton() - .setStyle("LINK") - .setLabel(config.moderation.mute.text) - .setURL(config.moderation.mute.link) - ] - : [] - ) - ] - }); - dmd = true; + ] + }); + dmd = true; + } + } catch { + dmd = false; } - } catch { - dmd = false; - } - const member = user; - let errors = 0; - try { - if (config.moderation.mute.timeout) { - await member.timeout(muteTime * 1000, reason || "No reason provided"); + 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 + }); + } + } + } catch { + errors++; + } + try { 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, + await client.database.eventScheduler.schedule("unmuteRole", new Date().getTime() + muteTime * 1000, { + guild: interaction.guild!.id, user: user.id, - expires: new Date().getTime() + muteTime * 1000 + role: config.moderation.mute.role }); } + } catch (e) { + console.log(e); + errors++; } - } catch { - errors++; - } - 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, { - guild: interaction.guild.id, - user: user.id, - role: config.moderation.mute.role - }); + 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 (e) { - console.log(e); - errors++; - } - if (errors === 2) { + await client.database.history.create("mute", interaction.guild!.id, member.user, interaction.user, reason); + const failed = !dmd && notify; await interaction.editReply({ embeds: [ new EmojiEmbed() - .setEmoji("PUNISH.MUTE.RED") + .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`) .setTitle("Mute") - .setDescription("Something went wrong and the user was not muted") - .setStatus("Danger") + .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}>` + : "") + ) + .setStatus(failed ? "Warning" : "Success") ], components: [] - }); // TODO: make this clearer - if (dmd) await dm.delete(); - return; + }); + 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") + ], + components: [] + }); } - await client.database.history.create("mute", interaction.guild.id, member.user, interaction.user, reason); - const failed = !dmd && 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" : "") + - (confirmation.components.appeal.response - ? ` and an appeal ticket was opened in <#${confirmation.components.appeal.response}>` - : "") - ) - .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); }; const check = (interaction: CommandInteraction) => { const member = interaction.member as GuildMember; - const me = interaction.guild.me!; + const me = interaction.guild!.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 ? member.roles.highest.position : 0; - const mePos = me.roles ? me.roles.highest.position : 0; - const applyPos = apply.roles ? apply.roles.highest.position : 0; + const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; + const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0; + const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0; // Do not allow muting the owner - if (member.id === interaction.guild.ownerId) throw new Error("You cannot mute the owner of the server"); + if (member.id === interaction.guild!.ownerId) throw new Error("You cannot mute the owner of the server"); // 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 @@ -364,7 +376,7 @@ const check = (interaction: CommandInteraction) => { // 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; + if (member.id === interaction.guild!.ownerId) return true; // Check if the user has moderate_members permission if (!member.permissions.has("MODERATE_MEMBERS")) throw new Error("You do not have the *Moderate Members* permission"); diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts index ff1db23..9a101e8 100644 --- a/src/commands/mod/nick.ts +++ b/src/commands/mod/nick.ts @@ -169,9 +169,9 @@ const check = (interaction: CommandInteraction) => { const me = interaction.guild.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 ? member.roles.highest.position : 0; - const mePos = me.roles ? me.roles.highest.position : 0; - const applyPos = apply.roles ? apply.roles.highest.position : 0; + 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; // 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 diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts index 463ec16..ba6ed37 100644 --- a/src/commands/mod/softban.ts +++ b/src/commands/mod/softban.ts @@ -149,9 +149,9 @@ const check = (interaction: CommandInteraction) => { const me = interaction.guild.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 ? member.roles.highest.position : 0; - const mePos = me.roles ? me.roles.highest.position : 0; - const applyPos = apply.roles ? apply.roles.highest.position : 0; + const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; + const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0; + const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0; // Do not allow softbanning the owner if (member.id === interaction.guild.ownerId) throw new Error("You cannot softban the owner of the server"); // Check if Nucleus can ban the member diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts index fbe6d66..2dd3a2a 100644 --- a/src/commands/mod/unmute.ts +++ b/src/commands/mod/unmute.ts @@ -136,9 +136,9 @@ const check = (interaction: CommandInteraction) => { const me = interaction.guild.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 ? member.roles.highest.position : 0; - const mePos = me.roles ? me.roles.highest.position : 0; - const applyPos = apply.roles ? apply.roles.highest.position : 0; + const memberPos = member.roles.cache.size > 1 ? member.roles.highest.position : 0; + const mePos = me.roles.cache.size > 1 ? me.roles.highest.position : 0; + const applyPos = apply.roles.cache.size > 1 ? apply.roles.highest.position : 0; // Do not allow unmuting the owner if (member.id === interaction.guild.ownerId) throw new Error("You cannot unmute the owner of the server"); // Check if Nucleus can unmute the member diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index 50480fa..68c476b 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -21,7 +21,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let confirmation; let timedOut = false; let success = false; - while (!timedOut && !success) { + do { confirmation = await new confirmationMessage(interaction) .setEmoji("PUNISH.WARN.RED") .setTitle("Warn") @@ -57,13 +57,13 @@ const callback = async (interaction: CommandInteraction): Promise => { .send(reason !== null); reason = reason ?? ""; if (confirmation.cancelled) timedOut = true; - else if (confirmation.success) success = true; + else if (confirmation.success !== undefined) 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; if (confirmation.success) { let dmd = false; @@ -294,7 +294,7 @@ const check = (interaction: CommandInteraction) => { // Do not allow warning bots if (member.user.bot) throw new Error("I cannot warn bots"); // Allow the owner to warn anyone - if (member.id === interaction.guild.ownerId) return true; + if (member.id === interaction.guild!.ownerId) return true; // Check if the user has moderate_members permission if (!member.permissions.has("MODERATE_MEMBERS")) throw new Error("You do not have the *Moderate Members* permission"); diff --git a/src/commands/settings/commands.ts b/src/commands/settings/commands.ts index 294c0fd..6345ec3 100644 --- a/src/commands/settings/commands.ts +++ b/src/commands/settings/commands.ts @@ -34,16 +34,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ) .setColor("Danger") .send(true); - if (confirmation.cancelled) - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Moderation Commands") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("GUILD.ROLES.CREATE") - ] - }); + if (confirmation.cancelled) return if (confirmation.success) { await client.database.guilds.write(interaction.guild.id, { ["moderation.mute.role"]: interaction.options.getRole("role").id diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index 843b391..9c9c02e 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -54,7 +54,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); } const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") + .setEmoji("CHANNEL.TEXT.EDIT", "CHANNEL.TEXT.DELETE") .setTitle("Attachment Log Channel") .setDescription( "This will be the channel all attachments will be sent to.\n\n" + diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts index 206d282..49126d3 100644 --- a/src/commands/settings/logs/channel.ts +++ b/src/commands/settings/logs/channel.ts @@ -53,7 +53,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); } const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") + .setEmoji("CHANNEL.TEXT.EDIT", "CHANNEL.TEXT.DELETE") .setTitle("Log Channel") .setDescription(`Are you sure you want to set the log channel to <#${channel.id}>?`) .setColor("Warning") diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts index c7077cf..023a13e 100644 --- a/src/commands/settings/logs/staff.ts +++ b/src/commands/settings/logs/staff.ts @@ -56,7 +56,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); } const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") + .setEmoji("CHANNEL.TEXT.EDIT", "CHANNEL.TEXT.DELETE") .setTitle("Staff Notifications Channel") .setDescription( "This will be the channel all notifications, updates, user reports etc. will be sent to.\n\n" + diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index ab6022e..932605c 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -78,7 +78,7 @@ const callback = async (interaction: CommandInteraction): Promise => { newName = newName.toLowerCase().replace(/[\s]/g, "-"); } const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") + .setEmoji("CHANNEL.TEXT.EDIT", "CHANNEL.TEXT.DELETE") .setTitle("Stats Channel") .setDescription( `Are you sure you want to set <#${channel.id}> to a stats channel?\n\n*Preview: ${newName.replace( diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 26fa66e..863c659 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -143,7 +143,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.TICKET.ARCHIVED") + .setEmoji("GUILD.TICKET.ARCHIVED", "GUILD.TICKET.CLOSE") .setTitle("Tickets") .setDescription( (options.category ? `**Category:** ${options.category.name}\n` : "") + @@ -151,10 +151,10 @@ const callback = async (interaction: CommandInteraction): Promise => { (options.supportping ? `**Support Ping:** ${options.supportping.name}\n` : "") + (options.enabled !== null ? `**Enabled:** ${ - options.enabled - ? `${getEmojiByName("CONTROL.TICK")} Yes` - : `${getEmojiByName("CONTROL.CROSS")} No` - }\n` + options.enabled + ? `${getEmojiByName("CONTROL.TICK")} Yes` + : `${getEmojiByName("CONTROL.CROSS")} No` + }\n` : "") + "\nAre you sure you want to apply these settings?" ) diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index aa8227f..29beffe 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -62,7 +62,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); } const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.EDIT") + .setEmoji("GUILD.ROLES.EDIT", "GUILD.ROLES.DELETE") .setTitle("Verify Role") .setDescription(`Are you sure you want to set the verify role to <@&${role.id}>?`) .setColor("Warning") diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index 24ccefc..16857ac 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -95,7 +95,7 @@ const callback = async (interaction: CommandInteraction): Promise => { if (channel) options.channel = renderChannel(channel); if (message) options.message = "\n> " + message; const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.EDIT") + .setEmoji("GUILD.ROLES.EDIT", "GUILD.ROLES.DELETE") .setTitle("Welcome Events") .setDescription(generateKeyValueList(options)) .setColor("Warning") diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts index 6d34258..b922da3 100644 --- a/src/commands/tags/create.ts +++ b/src/commands/tags/create.ts @@ -64,7 +64,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ephemeral: true }); const confirmation = await new confirmationMessage(interaction) - .setEmoji("PUNISH.NICKNAME.YELLOW") + .setEmoji("PUNISH.NICKNAME.YELLOW", "PUNISH.NICKNAME.RED") .setTitle("Tag create") .setDescription( keyValueList({ diff --git a/src/commands/tags/delete.ts b/src/commands/tags/delete.ts index 69b4757..2fff637 100644 --- a/src/commands/tags/delete.ts +++ b/src/commands/tags/delete.ts @@ -27,7 +27,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ephemeral: true }); const confirmation = await new confirmationMessage(interaction) - .setEmoji("PUNISH.NICKNAME.YELLOW") + .setEmoji("PUNISH.NICKNAME.YELLOW", "PUNISH.NICKNAME.RED") .setTitle("Tag Delete") .setDescription( keyValueList({ diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts index b5431fd..376abbd 100644 --- a/src/commands/tags/edit.ts +++ b/src/commands/tags/edit.ts @@ -79,7 +79,7 @@ const callback = async (interaction: CommandInteraction): Promise => { ephemeral: true }); const confirmation = await new confirmationMessage(interaction) - .setEmoji("PUNISH.NICKNAME.YELLOW") + .setEmoji("PUNISH.NICKNAME.YELLOW", "PUNISH.NICKNAME.RED") .setTitle("Tag Edit") .setDescription( keyValueList({ diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 58ab9d4..c805395 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -26,6 +26,8 @@ class confirmationMessage { interaction: CommandInteraction; title = ""; emoji = ""; + redEmoji: string | null = null; + timeoutText: string | null = null; description = ""; color: "Danger" | "Warning" | "Success" = "Success"; customButtons: Record> = {}; @@ -40,12 +42,14 @@ class confirmationMessage { this.title = title; return this; } - setEmoji(emoji: string) { + setEmoji(emoji: string, redEmoji?: 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) { + setDescription(description: string, timedOut?: string) { this.description = description; + if (timedOut) this.timeoutText = timedOut; // TODO also this return this; } setColor(color: "Danger" | "Warning" | "Success") { @@ -256,13 +260,29 @@ class confirmationMessage { } const returnValue: Awaited> = {}; - if (returnComponents) returnValue.components = this.customButtons; + if (returnComponents || success !== undefined) returnValue.components = this.customButtons; if (success !== undefined) returnValue.success = success; - if (cancelled) returnValue.cancelled = true; + if (cancelled) { + await this.timeoutError() + returnValue.cancelled = true; + } if (newReason) returnValue.newReason = newReason; return returnValue; } + + async timeoutError(): Promise { + await this.interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle(this.title) + .setDescription("We closed this message because it was not used for a while.") + .setStatus("Danger") + .setEmoji(this.redEmoji ?? this.emoji) + ], + components: [] + }) + } } export default confirmationMessage;