diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 1a27a2d..2d7d036 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -33,9 +33,10 @@ const callback = async (interaction: CommandInteraction) => { // const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } .send()) { let dmd = false + let dm; try { if (interaction.options.getString("notify") != "no") { - await (interaction.options.getMember("user") as GuildMember).send({ + dm = await (interaction.options.getMember("user") as GuildMember).send({ embeds: [new EmojiEmbed() .setEmoji("PUNISH.BAN.RED") .setTitle("Banned") @@ -52,13 +53,6 @@ const callback = async (interaction: CommandInteraction) => { days: Number(interaction.options.getInteger("delete") ?? 0), reason: interaction.options.getString("reason") ?? "No reason provided" }) - let failed = (dmd == false && interaction.options.getString("notify") != "no") - 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 { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.BAN.RED") @@ -66,7 +60,16 @@ const callback = async (interaction: CommandInteraction) => { .setDescription("Something went wrong and the user was not banned") .setStatus("Danger") ], components: []}) + if (dmd) await dm.delete() + return } + let failed = (dmd == false && interaction.options.getString("notify") != "no") + 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: []}) } else { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.BAN.GREEN") @@ -78,8 +81,15 @@ const callback = async (interaction: CommandInteraction) => { } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { + let member = (interaction.member as GuildMember) + let me = (interaction.guild.me as GuildMember) + let apply = (interaction.options.getMember("user") as GuildMember) + if (member == null || me == null || apply == null) throw "That member is not in the server" + let memberPos = member.roles ? member.roles.highest.position : 0 + let mePos = me.roles ? me.roles.highest.position : 0 + let applyPos = apply.roles ? apply.roles.highest.position : 0 // Check if Nucleus can ban the member - if (! (interaction.guild.me.roles.highest.position > (interaction.member as GuildMember).roles.highest.position)) throw "I do not have a role higher than that member" + if (! (mePos > applyPos)) throw "I do not have a role higher than that member" // Check if Nucleus has permission to ban if (! interaction.guild.me.permissions.has("BAN_MEMBERS")) throw "I do not have the `ban_members` permission"; // Do not allow banning Nucleus @@ -89,7 +99,7 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { // Check if the user has ban_members permission if (! (interaction.member as GuildMember).permissions.has("BAN_MEMBERS")) throw "You do not have the `ban_members` permission"; // Check if the user is below on the role list - if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + if (! (memberPos > applyPos)) throw "You do not have a role higher than that member" // Allow ban return true } diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index 32cd9fd..56e00e2 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -31,9 +31,10 @@ const callback = async (interaction: CommandInteraction) => { // const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } .send()) { let dmd = false + let dm; try { if (interaction.options.getString("notify") != "no") { - await (interaction.options.getMember("user") as GuildMember).send({ + dm = await (interaction.options.getMember("user") as GuildMember).send({ embeds: [new EmojiEmbed() .setEmoji("PUNISH.KICK.RED") .setTitle("Kicked") @@ -47,13 +48,6 @@ const callback = async (interaction: CommandInteraction) => { } catch {} try { (interaction.options.getMember("user") as GuildMember).kick(interaction.options.getString("reason") ?? "No reason provided.") - let failed = (dmd == false && interaction.options.getString("notify") != "no") - await interaction.editReply({embeds: [new EmojiEmbed() - .setEmoji(`PUNISH.KICK.${failed ? "YELLOW" : "GREEN"}`) - .setTitle(`Kick`) - .setDescription("The member was kicked" + (failed ? ", but could not be notified" : "")) - .setStatus(failed ? "Warning" : "Success") - ], components: []}) } catch { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.KICK.RED") @@ -61,7 +55,16 @@ const callback = async (interaction: CommandInteraction) => { .setDescription("Something went wrong and the user was not kicked") .setStatus("Danger") ], components: []}) + if (dmd) await dm.delete() + return } + let failed = (dmd == false && interaction.options.getString("notify") != "no") + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.KICK.${failed ? "YELLOW" : "GREEN"}`) + .setTitle(`Kick`) + .setDescription("The member was kicked" + (failed ? ", but could not be notified" : "")) + .setStatus(failed ? "Warning" : "Success") + ], components: []}) } else { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.KICK.GREEN") @@ -73,8 +76,15 @@ const callback = async (interaction: CommandInteraction) => { } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { + let member = (interaction.member as GuildMember) + let me = (interaction.guild.me as GuildMember) + let apply = (interaction.options.getMember("user") as GuildMember) + if (member == null || me == null || apply == null) throw "That member is not in the server" + let memberPos = member.roles ? member.roles.highest.position : 0 + let mePos = me.roles ? me.roles.highest.position : 0 + let applyPos = apply.roles ? apply.roles.highest.position : 0 // Check if Nucleus can kick the member - if (! (interaction.guild.me.roles.highest.position > (interaction.member as GuildMember).roles.highest.position)) throw "I do not have a role higher than that member" + if (! (mePos > applyPos)) throw "I do not have a role higher than that member" // Check if Nucleus has permission to kick if (! interaction.guild.me.permissions.has("KICK_MEMBERS")) throw "I do not have the `kick_members` permission"; // Do not allow kicking Nucleus @@ -84,7 +94,7 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { // Check if the user has kick_members permission if (! (interaction.member as GuildMember).permissions.has("KICK_MEMBERS")) throw "You do not have the `kick_members` permission"; // Check if the user is below on the role list - if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + if (! (memberPos > applyPos)) throw "You do not have a role higher than that member" // Allow kick return true } diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index ae59e7e..7c8cdd9 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -17,7 +17,8 @@ const command = (builder: SlashCommandSubcommandBuilder) => .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false)) .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false)) .addStringOption(option => option.setName("reason").setDescription("The reason for the mute").setRequired(false)) - .addStringOption(option => option.setName("notify").setDescription("The user to notify they have been muted").setRequired(false)) + .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are kicked | Default yes").setRequired(false) + .addChoices([["Yes", "yes"], ["No", "no"]])) // TODO: notify the user when the mute is lifted const callback = async (interaction: CommandInteraction) => { @@ -112,13 +113,14 @@ const callback = async (interaction: CommandInteraction) => { .setStatus("Success") ], ephemeral: true, fetchReply: true}) } + // TODO:[Modals] Replace this with a modal if (await new confirmationMessage(interaction) .setEmoji("PUNISH.MUTE.RED") .setTitle("Mute") .setDescription(keyValueList({ "user": `<@!${user.id}> (${user.user.username})`, "time": `${humanizeDuration(muteTime * 1000, {round: true})}`, - "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}` + "reason": `\n> ${reason ? reason : "*No reason provided*"}` }) + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n` + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`) @@ -127,9 +129,10 @@ const callback = async (interaction: CommandInteraction) => { // const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } .send(true)) { let dmd = false + let dm; try { if (interaction.options.getString("notify") != "no") { - await (interaction.options.getMember("user") as GuildMember).send({ + dm = await (interaction.options.getMember("user") as GuildMember).send({ embeds: [new EmojiEmbed() .setEmoji("PUNISH.MUTE.RED") .setTitle("Muted") @@ -144,13 +147,6 @@ const callback = async (interaction: CommandInteraction) => { } catch {} try { (interaction.options.getMember("user") as GuildMember).timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided") - let failed = (dmd == false && interaction.options.getString("notify") != "no") - 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" : "")) - .setStatus(failed ? "Warning" : "Success") - ], components: []}) } catch { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.MUTE.RED") @@ -158,7 +154,16 @@ const callback = async (interaction: CommandInteraction) => { .setDescription("Something went wrong and the user was not kicked") .setStatus("Danger") ], components: []}) + if (dmd) await dm.delete() + return } + let failed = (dmd == false && interaction.options.getString("notify") != "no") + 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" : "")) + .setStatus(failed ? "Warning" : "Success") + ], components: []}) } else { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.MUTE.GREEN") @@ -170,8 +175,15 @@ const callback = async (interaction: CommandInteraction) => { } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { + let member = (interaction.member as GuildMember) + let me = (interaction.guild.me as GuildMember) + let apply = (interaction.options.getMember("user") as GuildMember) + if (member == null || me == null || apply == null) throw "That member is not in the server" + let memberPos = member.roles ? member.roles.highest.position : 0 + let mePos = me.roles ? me.roles.highest.position : 0 + let applyPos = apply.roles ? apply.roles.highest.position : 0 // Check if Nucleus can mute the member - if (! (interaction.guild.me.roles.highest.position > (interaction.member as GuildMember).roles.highest.position)) throw "I do not have a role higher than that member" + if (! (mePos > applyPos)) throw "I do not have a role higher than that member" // Check if Nucleus has permission to mute if (! interaction.guild.me.permissions.has("MODERATE_MEMBERS")) throw "I do not have the `moderate_members` permission"; // Do not allow the user to have admin or be the owner @@ -183,7 +195,7 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { // Check if the user has moderate_members permission if (! (interaction.member as GuildMember).permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission"; // Check if the user is below on the role list - if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + if (! (memberPos > applyPos)) throw "You do not have a role higher than that member" // Allow mute return true } diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 6d895f5..1dc75eb 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -21,6 +21,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => .addStringOption(option => option.setName("reason").setDescription("The reason for the purge").setRequired(false)) const callback = async (interaction: CommandInteraction) => { + let user = interaction.options.getMember("user") as GuildMember ?? null let channel = (interaction.options.getChannel("channel") as GuildChannel) ?? interaction.channel let thischannel if ((interaction.options.getChannel("channel") as GuildChannel) == null) { @@ -55,7 +56,7 @@ const callback = async (interaction: CommandInteraction) => { ephemeral: true, fetchReply: true }) - let deleted = [] + let deleted = [] as Discord.Message[] while (true) { let m = await interaction.editReply({ embeds: [ @@ -111,7 +112,15 @@ const callback = async (interaction: CommandInteraction) => { if (component.customId === "done") break; let amount; try { amount = parseInt(component.customId); } catch { break; } - await (channel as TextChannel).bulkDelete(amount, true); // TODO: Add to deleted list | TODO: Support for users + let messages; + (interaction.channel as TextChannel).messages.fetch({limit: amount}).then(async (ms) => { + if (user) { + ms = ms.filter(m => m.author.id === user.id) + } + messages = await (channel as TextChannel).bulkDelete(ms, true); + }) + deleted = deleted.concat(messages.map(m => m)) // TODO: .values doesnt work so using .map + // TODO: Support for users } if (deleted.length === 0) return await interaction.editReply({ embeds: [ @@ -123,16 +132,53 @@ const callback = async (interaction: CommandInteraction) => { ], components: [] }) - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.PURGE.GREEN") - .setTitle("Purge") - .setDescription(`Deleted ${deleted.length} messages`) - .setStatus("Success") - ], - components: [] - }) + let attachmentObject; + try { + let out = "" + deleted.reverse().forEach(message => { + out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n` + let lines = message.content.split("\n") + lines.forEach(line => {out += `> ${line}\n`}) + out += `\n\n` + }) + attachmentObject = { + attachment: Buffer.from(out), + name: `purge-${channel.id}-${Date.now()}.txt`, + description: "Purge log" + } + } catch {} + let m = await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`CHANNEL.PURGE.GREEN`) + .setTitle(`Purge`) + .setDescription("Messages cleared") + .setStatus("Success") + ], components: [new Discord.MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("download") + .setLabel("Download transcript") + .setStyle("SUCCESS") + .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id")) + ])]}) + let component; + try { + component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000}); + } catch {} + if (component && component.customId === "download") { + interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.GREEN") + .setTitle(`Purge`) + .setDescription("Uploaded") + .setStatus("Success") + ], components: [], files: [attachmentObject]}) + } else { + interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.GREEN") + .setTitle(`Purge`) + .setDescription("Messages cleared") + .setStatus("Success") + ], components: []}) + } + return } else { if (await new confirmationMessage(interaction) .setEmoji("CHANNEL.PURGE.RED") @@ -146,27 +192,67 @@ const callback = async (interaction: CommandInteraction) => { // pluralize("day", interaction.options.getInteger("amount")) // const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } .send()) { + let messages; + try { + (interaction.channel as TextChannel).messages.fetch({limit: interaction.options.getInteger("amount")}).then(async (ms) => { + if (user) { + ms = ms.filter(m => m.author.id === user.id) + } + messages = await (channel as TextChannel).bulkDelete(ms, true); + }) // TODO: fix for purge amount by user, not just checking x + } catch(e) { + console.log(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: []}) + } + let attachmentObject; try { - let messages = await (channel as TextChannel).bulkDelete(interaction.options.getInteger("amount"), true) // TODO: Support for users let out = "" messages.reverse().forEach(message => { - out += `${message.author.username}#${message.author.discriminator} (${message.author.id})\n` + out += `${message.author.username}#${message.author.discriminator} (${message.author.id}) [${new Date(message.createdTimestamp).toISOString()}]\n` let lines = message.content.split("\n") - lines.forEach(line => {out += `> ${line}\n`}) // TODO: Humanize timestamp + lines.forEach(line => {out += `> ${line}\n`}) out += `\n\n` - }) // TODO: Upload as file - await interaction.editReply({embeds: [new EmojiEmbed() - .setEmoji(`CHANNEL.PURGE.GREEN`) + }) + attachmentObject = { + attachment: Buffer.from(out), + name: `purge-${channel.id}-${Date.now()}.txt`, + description: `Purge log` + } + } catch {} + let m = await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`CHANNEL.PURGE.GREEN`) + .setTitle(`Purge`) + .setDescription("Messages cleared") + .setStatus("Success") + ], components: [new Discord.MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("download") + .setLabel("Download transcript") + .setStyle("SUCCESS") + .setEmoji(getEmojiByName("CONTROL.DOWNLOAD", "id")) + ])]}) + let component; + try { + component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000}); + } catch {} + if (component && component.customId === "download") { + interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.GREEN") .setTitle(`Purge`) - .setDescription("Messages cleared") + .setDescription("Uploaded") .setStatus("Success") - ], components: []}) - } catch { - await interaction.editReply({embeds: [new EmojiEmbed() - .setEmoji("CHANNEL.PURGE.RED") + ], components: [], files: [attachmentObject]}) + } else { + interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.GREEN") .setTitle(`Purge`) - .setDescription("Something went wrong and no messages were deleted") - .setStatus("Danger") + .setDescription("Messages cleared") + .setStatus("Success") ], components: []}) } } else { diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts index 6da3a68..bcebbd5 100644 --- a/src/commands/mod/softban.ts +++ b/src/commands/mod/softban.ts @@ -38,8 +38,8 @@ const callback = async (interaction: CommandInteraction) => { await (interaction.options.getMember("user") as GuildMember).send({ embeds: [new EmojiEmbed() .setEmoji("PUNISH.BAN.RED") - .setTitle("Kick") - .setDescription(`You have been kicked from ${interaction.guild.name}` + + .setTitle("Softbanned") + .setDescription(`You have been softbanned from ${interaction.guild.name}` + (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided.")) .setStatus("Danger") ] @@ -48,17 +48,11 @@ const callback = async (interaction: CommandInteraction) => { } } catch {} try { - (interaction.options.getMember("user") as GuildMember).ban({ + await (interaction.options.getMember("user") as GuildMember).ban({ days: Number(interaction.options.getInteger("delete") ?? 0), reason: interaction.options.getString("reason") - }) // TODO: unban here - let failed = (dmd == false && interaction.options.getString("notify") != "no") - await interaction.editReply({embeds: [new EmojiEmbed() - .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`) - .setTitle(`Softban`) - .setDescription("The member was softbanned" + (failed ? ", but could not be notified" : "")) - .setStatus(failed ? "Warning" : "Success") - ], components: []}) + }); + await interaction.guild.members.unban(interaction.options.getMember("user") as GuildMember, "Softban"); } catch { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.BAN.RED") @@ -67,6 +61,13 @@ const callback = async (interaction: CommandInteraction) => { .setStatus("Danger") ], components: []}) } + let failed = (dmd == false && interaction.options.getString("notify") != "no") + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`) + .setTitle(`Softban`) + .setDescription("The member was softbanned" + (failed ? ", but could not be notified" : "")) + .setStatus(failed ? "Warning" : "Success") + ], components: []}) } else { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.BAN.GREEN") @@ -78,8 +79,15 @@ const callback = async (interaction: CommandInteraction) => { } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { + let member = (interaction.member as GuildMember) + let me = (interaction.guild.me as GuildMember) + let apply = (interaction.options.getMember("user") as GuildMember) + if (member == null || me == null || apply == null) throw "That member is not in the server" + let memberPos = member.roles ? member.roles.highest.position : 0 + let mePos = me.roles ? me.roles.highest.position : 0 + let applyPos = apply.roles ? apply.roles.highest.position : 0 // Check if Nucleus can ban the member - if (! (interaction.guild.me.roles.highest.position > (interaction.member as GuildMember).roles.highest.position)) throw "I do not have a role higher than that member" + if (! (mePos > applyPos)) throw "I do not have a role higher than that member" // Check if Nucleus has permission to ban if (! interaction.guild.me.permissions.has("BAN_MEMBERS")) throw "I do not have the `ban_members` permission"; // Do not allow softbanning Nucleus @@ -89,7 +97,7 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { // Check if the user has ban_members permission if (! (interaction.member as GuildMember).permissions.has("BAN_MEMBERS")) throw "You do not have the `ban_members` permission"; // Check if the user is below on the role list - if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + if (! (memberPos > applyPos)) throw "You do not have a role higher than that member" // Allow softban return true } diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts index 5194bce..2c522a4 100644 --- a/src/commands/mod/unmute.ts +++ b/src/commands/mod/unmute.ts @@ -1,18 +1,102 @@ -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, GuildMember } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { WrappedCheck } from "jshaiku"; +import confirmationMessage from "../../utils/confirmationMessage.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import keyValueList from "../../utils/generateKeyValueList.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("unmute") - .setDescription("Unmutes a member") + .setDescription("Unmutes a user") + .addUserOption(option => option.setName("user").setDescription("The user to unmute").setRequired(true)) + .addStringOption(option => option.setName("reason").setDescription("The reason for the unmute").setRequired(false)) + .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are unmuted | Default no").setRequired(false) + .addChoices([["Yes", "yes"], ["No", "no"]]) + ) -const callback = (interaction: CommandInteraction) => { - interaction.reply("Command incomplete [mod/unmute]"); +const callback = async (interaction: CommandInteraction) => { + // TODO:[Modals] Replace this with a modal + if (await new confirmationMessage(interaction) + .setEmoji("PUNISH.MUTE.RED") + .setTitle("Unmute") + .setDescription(keyValueList({ + "user": `<@!${(interaction.options.getMember("user") as GuildMember).id}> (${(interaction.options.getMember("user") as GuildMember).user.username})`, + "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}` + }) + + `The user **will${interaction.options.getString("notify") === "yes" ? '' : ' not'}** be notified\n\n` + + `Are you sure you want to unmute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`) + .setColor("Danger") +// pluralize("day", interaction.options.getInteger("delete")) +// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } + .send()) { + let dmd = false + let dm; + try { + if (interaction.options.getString("notify") != "no") { + dm = await (interaction.options.getMember("user") as GuildMember).send({ + embeds: [new EmojiEmbed() + .setEmoji("PUNISH.MUTE.GREEN") + .setTitle("Unmuted") + .setDescription(`You have been unmuted in ${interaction.guild.name}` + + (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided.")) + .setStatus("Success") + ] + }) + dmd = true + } + } catch {} + try { + (interaction.options.getMember("user") as GuildMember).timeout(0, interaction.options.getString("reason") || "No reason provided") + } catch { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.MUTE.RED") + .setTitle(`Unmute`) + .setDescription("Something went wrong and the user was not unmuted") + .setStatus("Danger") + ], components: []}) + if (dmd) await dm.delete() + return + } + let failed = (dmd == false && interaction.options.getString("notify") != "no") + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`) + .setTitle(`Unmute`) + .setDescription("The member was unmuted" + (failed ? ", but could not be notified" : "")) + .setStatus(failed ? "Warning" : "Success") + ], components: []}) + } else { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.MUTE.GREEN") + .setTitle(`Unmute`) + .setDescription("No changes were made") + .setStatus("Success") + ], components: []}) + } } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { - return true; + let member = (interaction.member as GuildMember) + let me = (interaction.guild.me as GuildMember) + let apply = (interaction.options.getMember("user") as GuildMember) + if (member == null || me == null || apply == null) throw "That member is not in the server" + let memberPos = member.roles ? member.roles.highest.position : 0 + let mePos = me.roles ? me.roles.highest.position : 0 + let applyPos = apply.roles ? apply.roles.highest.position : 0 + // Check if Nucleus can unmute the member + if (! (mePos > applyPos)) throw "I do not have a role higher than that member" + // Check if Nucleus has permission to unmute + if (! interaction.guild.me.permissions.has("MODERATE_MEMBERS")) throw "I do not have the `moderate_members` permission"; + // Do not allow the user to have admin or be the owner + if ((interaction.options.getMember("user") as GuildMember).permissions.has("ADMINISTRATOR") || (interaction.options.getMember("user") as GuildMember).id == interaction.guild.ownerId) throw "You cannot unmute an admin or the owner" + // Allow the owner to unmute anyone + if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true + // Check if the user has moderate_members permission + if (! (interaction.member as GuildMember).permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission"; + // Check if the user is below on the role list + if (! (memberPos > applyPos)) throw "You do not have a role higher than that member" + // Allow unmute + return true } export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index cbf7701..442f290 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -1,4 +1,4 @@ -import { CommandInteraction, GuildMember } from "discord.js"; +import Discord, { CommandInteraction, GuildMember, MessageActionRow } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { WrappedCheck } from "jshaiku"; import confirmationMessage from "../../utils/confirmationMessage.js"; @@ -43,15 +43,6 @@ const callback = async (interaction: CommandInteraction) => { }) dmd = true } - } catch {} - try { - let failed = (dmd == false && interaction.options.getString("notify") != "no") // TODO: some way of dealing with not DMing users - await interaction.editReply({embeds: [new EmojiEmbed() - .setEmoji(`PUNISH.WARN.${failed ? "YELLOW" : "GREEN"}`) - .setTitle(`Warn`) - .setDescription(failed ? "The user cannot be messaged and was not warned" : "The user was warned") - .setStatus(failed ? "Warning" : "Success") - ], components: []}) } catch { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.WARN.RED") @@ -60,6 +51,73 @@ const callback = async (interaction: CommandInteraction) => { .setStatus("Danger") ], components: []}) } + let failed = (dmd == false && interaction.options.getString("notify") != "no") + if (!failed) { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.WARN.GREEN`) + .setTitle(`Warn`) + .setDescription("The user was warned") + .setStatus("Success") + ], components: []}) + } else { + let m = await interaction.editReply({ + embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.WARN.RED`) + .setTitle(`Warn`) + .setDescription("The user's DMs are not open\n\nWhat would you like to do?") + .setStatus("Danger") + ], components: [ + new MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("log") + .setLabel("Ignore and log") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("here") + .setLabel("Warn here") + .setStyle("SECONDARY") + .setDisabled((interaction.options.getMember("user") as GuildMember).permissionsIn(interaction.channel as Discord.TextChannel).has("VIEW_CHANNEL") === false), + ]) + ], + }) + let component; + try { + component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000}); + } catch (e) { + return await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.WARN.GREEN`) + .setTitle(`Warn`) + .setDescription("No changes were made") + .setStatus("Success") + ], components: []}) + } + if ( component.customId == "here" ) { + await interaction.channel.send({ + embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.WARN.RED`) + .setTitle(`Warn`) + .setDescription(`You have been warned` + + (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided.")) + .setStatus("Danger") + ], + content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`, + allowedMentions: {users: [(interaction.options.getMember("user") as GuildMember).id]} + }) + return await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.WARN.GREEN`) + .setTitle(`Warn`) + .setDescription("The user was warned") + .setStatus("Success") + ], components: []}) + } else { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.WARN.GREEN`) + .setTitle(`Warn`) + .setDescription("The warn was logged") + .setStatus("Success") + ], components: []}) + } + } } else { await interaction.editReply({embeds: [new EmojiEmbed() .setEmoji("PUNISH.WARN.GREEN") @@ -71,6 +129,14 @@ const callback = async (interaction: CommandInteraction) => { } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { + return true + let member = (interaction.member as GuildMember) + let me = (interaction.guild.me as GuildMember) + let apply = (interaction.options.getMember("user") as GuildMember) + if (member == null || me == null || apply == null) throw "That member is not in the server" + let memberPos = member.roles ? member.roles.highest.position : 0 + let mePos = me.roles ? me.roles.highest.position : 0 + let applyPos = apply.roles ? apply.roles.highest.position : 0 // Do not allow warning bots if ((interaction.member as GuildMember).user.bot) throw "I cannot warn bots" // Allow the owner to warn anyone @@ -78,7 +144,7 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { // Check if the user has moderate_members permission if (! (interaction.member as GuildMember).permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission"; // Check if the user is below on the role list - if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + if (! (memberPos > applyPos)) throw "You do not have a role higher than that member" // Allow warn return true } diff --git a/src/config/emojis.json b/src/config/emojis.json index fc3a145..1764932 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -32,6 +32,7 @@ "CROSS": "947441948543815720", "LEFT": "947441951148486728", "RIGHT": "947441957473488916", + "DOWNLOAD": "947959513032585236", "PILL": { "TICK": "753314339082993832", "CROSS": "753314339389309100" diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 5464e31..7deb5f5 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -61,6 +61,7 @@ class confirmationMessage { } catch (e) { return false; // TODO: Check the type of the error; change the error message here } + component.deferUpdate(); return component.customId === "yes" }