blob: 02d253134ed7600bd5a7af7d8cbc9739da53d650 [file] [log] [blame]
pineafan8b4b17f2022-02-27 20:42:52 +00001import Discord, { CommandInteraction, GuildMember, MessageActionRow } from "discord.js";
2import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
3import { WrappedCheck } from "jshaiku";
4import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
5import getEmojiByName from "../../utils/getEmojiByName.js";
6import confirmationMessage from "../../utils/confirmationMessage.js";
7import keyValueList from "../../utils/generateKeyValueList.js";
8import humanizeDuration from "humanize-duration";
9
10const command = (builder: SlashCommandSubcommandBuilder) =>
11 builder
12 .setName("mute")
13 .setDescription("Mutes a member using Discord's \"Timeout\" feature")
14 .addUserOption(option => option.setName("user").setDescription("The user to mute").setRequired(true))
15 .addIntegerOption(option => option.setName("days").setDescription("The number of days to mute the user for | Default 0").setMinValue(0).setMaxValue(27).setRequired(false))
16 .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to mute the user for | Default 0").setMinValue(0).setMaxValue(23).setRequired(false))
17 .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
18 .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
19 .addStringOption(option => option.setName("reason").setDescription("The reason for the mute").setRequired(false))
pineafan5d1908e2022-02-28 21:34:47 +000020 .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are kicked | Default yes").setRequired(false)
21 .addChoices([["Yes", "yes"], ["No", "no"]]))
pineafan8b4b17f2022-02-27 20:42:52 +000022 // TODO: notify the user when the mute is lifted
23
24const callback = async (interaction: CommandInteraction) => {
25 const user = interaction.options.getMember("user") as GuildMember
26 const reason = interaction.options.getString("reason")
27 const time = {
28 days: interaction.options.getInteger("days") || 0,
29 hours: interaction.options.getInteger("hours") || 0,
30 minutes: interaction.options.getInteger("minutes") || 0,
31 seconds: interaction.options.getInteger("seconds") || 0
32 }
33 let muteTime = (time.days * 24 * 60 * 60) + (time.hours * 60 * 60) + (time.minutes * 60) + time.seconds
34 if (muteTime == 0) {
35 let m = await interaction.reply({embeds: [
36 new EmojiEmbed()
37 .setEmoji("PUNISH.MUTE.GREEN")
38 .setTitle("Mute")
39 .setDescription("How long should the user be muted")
40 .setStatus("Success")
41 ], components: [
42 new MessageActionRow().addComponents([
43 new Discord.MessageButton()
44 .setCustomId("1m")
45 .setLabel("1 Minute")
46 .setStyle("SECONDARY"),
47 new Discord.MessageButton()
48 .setCustomId("10m")
49 .setLabel("10 Minutes")
50 .setStyle("SECONDARY"),
51 new Discord.MessageButton()
52 .setCustomId("30m")
53 .setLabel("30 Minutes")
54 .setStyle("SECONDARY"),
55 new Discord.MessageButton()
56 .setCustomId("1h")
57 .setLabel("1 Hour")
58 .setStyle("SECONDARY")
59 ]),
60 new MessageActionRow().addComponents([
61 new Discord.MessageButton()
62 .setCustomId("6h")
63 .setLabel("6 Hours")
64 .setStyle("SECONDARY"),
65 new Discord.MessageButton()
66 .setCustomId("12h")
67 .setLabel("12 Hours")
68 .setStyle("SECONDARY"),
69 new Discord.MessageButton()
70 .setCustomId("1d")
71 .setLabel("1 Day")
72 .setStyle("SECONDARY"),
73 new Discord.MessageButton()
74 .setCustomId("1w")
75 .setLabel("1 Week")
76 .setStyle("SECONDARY")
77 ]),
78 new MessageActionRow().addComponents([
79 new Discord.MessageButton()
80 .setCustomId("cancel")
81 .setLabel("Cancel")
82 .setStyle("DANGER")
83 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
84 ])
85 ], ephemeral: true, fetchReply: true})
86 let component;
87 try {
88 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
89 } catch { return }
90 component.deferUpdate();
91 if (component.customId == "cancel") return interaction.editReply({embeds: [new EmojiEmbed()
92 .setEmoji("PUNISH.MUTE.RED")
93 .setTitle("Mute")
94 .setDescription("Mute cancelled")
95 .setStatus("Danger")
96 ]})
97 switch (component.customId) {
98 case "1m": { muteTime = 60; break; }
99 case "10m": { muteTime = 60 * 10; break; }
100 case "30m": { muteTime = 60 * 30; break; }
101 case "1h": { muteTime = 60 * 60; break; }
102 case "6h": { muteTime = 60 * 60 * 6; break; }
103 case "12h": { muteTime = 60 * 60 * 12; break; }
104 case "1d": { muteTime = 60 * 60 * 24; break; }
105 case "1w": { muteTime = 60 * 60 * 24 * 7; break; }
106 }
107 } else {
108 await interaction.reply({embeds: [
109 new EmojiEmbed()
110 .setEmoji("PUNISH.MUTE.GREEN")
111 .setTitle("Mute")
112 .setDescription("Loading...")
113 .setStatus("Success")
114 ], ephemeral: true, fetchReply: true})
115 }
pineafan5d1908e2022-02-28 21:34:47 +0000116 // TODO:[Modals] Replace this with a modal
pineafan8b4b17f2022-02-27 20:42:52 +0000117 if (await new confirmationMessage(interaction)
118 .setEmoji("PUNISH.MUTE.RED")
119 .setTitle("Mute")
120 .setDescription(keyValueList({
121 "user": `<@!${user.id}> (${user.user.username})`,
122 "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
pineafan5d1908e2022-02-28 21:34:47 +0000123 "reason": `\n> ${reason ? reason : "*No reason provided*"}`
pineafan8b4b17f2022-02-27 20:42:52 +0000124 })
125 + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
126 + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
127 .setColor("Danger")
128// pluralize("day", interaction.options.getInteger("delete"))
129// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
130 .send(true)) {
131 let dmd = false
pineafan5d1908e2022-02-28 21:34:47 +0000132 let dm;
pineafan8b4b17f2022-02-27 20:42:52 +0000133 try {
134 if (interaction.options.getString("notify") != "no") {
pineafan5d1908e2022-02-28 21:34:47 +0000135 dm = await (interaction.options.getMember("user") as GuildMember).send({
pineafan8b4b17f2022-02-27 20:42:52 +0000136 embeds: [new EmojiEmbed()
137 .setEmoji("PUNISH.MUTE.RED")
138 .setTitle("Muted")
139 .setDescription(`You have been muted in ${interaction.guild.name}` +
pineafan1dc15722022-03-14 21:27:34 +0000140 (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".\n\n" +
pineafan8b4b17f2022-02-27 20:42:52 +0000141 `You will be unmuted at: <t:${Math.round((new Date).getTime() / 1000) + muteTime}:D> at <t:${Math.round((new Date).getTime() / 1000) + muteTime}:T> (<t:${Math.round((new Date).getTime() / 1000) + muteTime}:R>)`))
142 .setStatus("Danger")
143 ]
144 })
145 dmd = true
146 }
147 } catch {}
148 try {
149 (interaction.options.getMember("user") as GuildMember).timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided")
pineafan8b4b17f2022-02-27 20:42:52 +0000150 } catch {
151 await interaction.editReply({embeds: [new EmojiEmbed()
152 .setEmoji("PUNISH.MUTE.RED")
153 .setTitle(`Mute`)
154 .setDescription("Something went wrong and the user was not kicked")
155 .setStatus("Danger")
156 ], components: []})
pineafan5d1908e2022-02-28 21:34:47 +0000157 if (dmd) await dm.delete()
158 return
pineafan8b4b17f2022-02-27 20:42:52 +0000159 }
pineafan5d1908e2022-02-28 21:34:47 +0000160 let failed = (dmd == false && interaction.options.getString("notify") != "no")
161 await interaction.editReply({embeds: [new EmojiEmbed()
162 .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`)
163 .setTitle(`Mute`)
164 .setDescription("The member was muted" + (failed ? ", but could not be notified" : ""))
165 .setStatus(failed ? "Warning" : "Success")
166 ], components: []})
pineafan8b4b17f2022-02-27 20:42:52 +0000167 } else {
168 await interaction.editReply({embeds: [new EmojiEmbed()
169 .setEmoji("PUNISH.MUTE.GREEN")
170 .setTitle(`Mute`)
171 .setDescription("No changes were made")
172 .setStatus("Success")
173 ], components: []})
174 }
175}
176
177const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
pineafan5d1908e2022-02-28 21:34:47 +0000178 let member = (interaction.member as GuildMember)
179 let me = (interaction.guild.me as GuildMember)
180 let apply = (interaction.options.getMember("user") as GuildMember)
181 if (member == null || me == null || apply == null) throw "That member is not in the server"
182 let memberPos = member.roles ? member.roles.highest.position : 0
183 let mePos = me.roles ? me.roles.highest.position : 0
184 let applyPos = apply.roles ? apply.roles.highest.position : 0
pineafan8b4b17f2022-02-27 20:42:52 +0000185 // Check if Nucleus can mute the member
pineafan5d1908e2022-02-28 21:34:47 +0000186 if (! (mePos > applyPos)) throw "I do not have a role higher than that member"
pineafan8b4b17f2022-02-27 20:42:52 +0000187 // Check if Nucleus has permission to mute
188 if (! interaction.guild.me.permissions.has("MODERATE_MEMBERS")) throw "I do not have the `moderate_members` permission";
189 // Do not allow the user to have admin or be the owner
190 if ((interaction.options.getMember("user") as GuildMember).permissions.has("ADMINISTRATOR") || (interaction.options.getMember("user") as GuildMember).id == interaction.guild.ownerId) throw "You cannot mute an admin or the owner"
191 // Do not allow muting Nucleus
192 if ((interaction.member as GuildMember).id == interaction.guild.me.id) throw "I cannot mute myself"
193 // Allow the owner to mute anyone
194 if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true
195 // Check if the user has moderate_members permission
196 if (! (interaction.member as GuildMember).permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission";
197 // Check if the user is below on the role list
pineafan5d1908e2022-02-28 21:34:47 +0000198 if (! (memberPos > applyPos)) throw "You do not have a role higher than that member"
pineafan8b4b17f2022-02-27 20:42:52 +0000199 // Allow mute
200 return true
201}
202
203export { command, callback, check };