blob: 165d90683d110ad8cac9d9aa758196485eb22d70 [file] [log] [blame]
pineafan377794f2022-04-18 19:01:01 +01001import Discord, { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
pineafan8b4b17f2022-02-27 20:42:52 +00002import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
3import { WrappedCheck } from "jshaiku";
pineafan377794f2022-04-18 19:01:01 +01004import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +00005import getEmojiByName from "../../utils/getEmojiByName.js";
6import confirmationMessage from "../../utils/confirmationMessage.js";
7import keyValueList from "../../utils/generateKeyValueList.js";
8import humanizeDuration from "humanize-duration";
pineafan377794f2022-04-18 19:01:01 +01009import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
10import readConfig from "../../utils/readConfig.js";
pineafan8b4b17f2022-02-27 20:42:52 +000011
12const command = (builder: SlashCommandSubcommandBuilder) =>
13 builder
14 .setName("mute")
15 .setDescription("Mutes a member using Discord's \"Timeout\" feature")
16 .addUserOption(option => option.setName("user").setDescription("The user to mute").setRequired(true))
17 .addIntegerOption(option => option.setName("days").setDescription("The number of days to mute the user for | Default 0").setMinValue(0).setMaxValue(27).setRequired(false))
18 .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to mute the user for | Default 0").setMinValue(0).setMaxValue(23).setRequired(false))
19 .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
20 .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
21 .addStringOption(option => option.setName("reason").setDescription("The reason for the mute").setRequired(false))
pineafan377794f2022-04-18 19:01:01 +010022 .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are muted | Default yes").setRequired(false)
pineafan5d1908e2022-02-28 21:34:47 +000023 .addChoices([["Yes", "yes"], ["No", "no"]]))
pineafan8b4b17f2022-02-27 20:42:52 +000024 // TODO: notify the user when the mute is lifted
25
26const callback = async (interaction: CommandInteraction) => {
pineafan377794f2022-04-18 19:01:01 +010027 // @ts-ignore
28 const { log, NucleusColors, renderUser, entry } = interaction.client.logger
pineafan8b4b17f2022-02-27 20:42:52 +000029 const user = interaction.options.getMember("user") as GuildMember
30 const reason = interaction.options.getString("reason")
31 const time = {
32 days: interaction.options.getInteger("days") || 0,
33 hours: interaction.options.getInteger("hours") || 0,
34 minutes: interaction.options.getInteger("minutes") || 0,
35 seconds: interaction.options.getInteger("seconds") || 0
36 }
37 let muteTime = (time.days * 24 * 60 * 60) + (time.hours * 60 * 60) + (time.minutes * 60) + time.seconds
38 if (muteTime == 0) {
39 let m = await interaction.reply({embeds: [
pineafan377794f2022-04-18 19:01:01 +010040 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000041 .setEmoji("PUNISH.MUTE.GREEN")
42 .setTitle("Mute")
43 .setDescription("How long should the user be muted")
44 .setStatus("Success")
45 ], components: [
46 new MessageActionRow().addComponents([
47 new Discord.MessageButton()
48 .setCustomId("1m")
49 .setLabel("1 Minute")
50 .setStyle("SECONDARY"),
51 new Discord.MessageButton()
52 .setCustomId("10m")
53 .setLabel("10 Minutes")
54 .setStyle("SECONDARY"),
55 new Discord.MessageButton()
56 .setCustomId("30m")
57 .setLabel("30 Minutes")
58 .setStyle("SECONDARY"),
59 new Discord.MessageButton()
60 .setCustomId("1h")
61 .setLabel("1 Hour")
62 .setStyle("SECONDARY")
63 ]),
64 new MessageActionRow().addComponents([
65 new Discord.MessageButton()
66 .setCustomId("6h")
67 .setLabel("6 Hours")
68 .setStyle("SECONDARY"),
69 new Discord.MessageButton()
70 .setCustomId("12h")
71 .setLabel("12 Hours")
72 .setStyle("SECONDARY"),
73 new Discord.MessageButton()
74 .setCustomId("1d")
75 .setLabel("1 Day")
76 .setStyle("SECONDARY"),
77 new Discord.MessageButton()
78 .setCustomId("1w")
79 .setLabel("1 Week")
80 .setStyle("SECONDARY")
81 ]),
82 new MessageActionRow().addComponents([
83 new Discord.MessageButton()
84 .setCustomId("cancel")
85 .setLabel("Cancel")
86 .setStyle("DANGER")
87 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
88 ])
89 ], ephemeral: true, fetchReply: true})
90 let component;
91 try {
92 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
93 } catch { return }
94 component.deferUpdate();
pineafan377794f2022-04-18 19:01:01 +010095 if (component.customId == "cancel") return interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000096 .setEmoji("PUNISH.MUTE.RED")
97 .setTitle("Mute")
98 .setDescription("Mute cancelled")
99 .setStatus("Danger")
100 ]})
101 switch (component.customId) {
102 case "1m": { muteTime = 60; break; }
103 case "10m": { muteTime = 60 * 10; break; }
104 case "30m": { muteTime = 60 * 30; break; }
105 case "1h": { muteTime = 60 * 60; break; }
106 case "6h": { muteTime = 60 * 60 * 6; break; }
107 case "12h": { muteTime = 60 * 60 * 12; break; }
108 case "1d": { muteTime = 60 * 60 * 24; break; }
109 case "1w": { muteTime = 60 * 60 * 24 * 7; break; }
110 }
111 } else {
112 await interaction.reply({embeds: [
pineafan377794f2022-04-18 19:01:01 +0100113 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000114 .setEmoji("PUNISH.MUTE.GREEN")
115 .setTitle("Mute")
116 .setDescription("Loading...")
117 .setStatus("Success")
118 ], ephemeral: true, fetchReply: true})
119 }
pineafan5d1908e2022-02-28 21:34:47 +0000120 // TODO:[Modals] Replace this with a modal
pineafan377794f2022-04-18 19:01:01 +0100121 let confirmation = await new confirmationMessage(interaction)
pineafan8b4b17f2022-02-27 20:42:52 +0000122 .setEmoji("PUNISH.MUTE.RED")
123 .setTitle("Mute")
124 .setDescription(keyValueList({
125 "user": `<@!${user.id}> (${user.user.username})`,
126 "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
pineafan5d1908e2022-02-28 21:34:47 +0000127 "reason": `\n> ${reason ? reason : "*No reason provided*"}`
pineafan8b4b17f2022-02-27 20:42:52 +0000128 })
129 + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
130 + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
131 .setColor("Danger")
132// pluralize("day", interaction.options.getInteger("delete"))
133// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
pineafan377794f2022-04-18 19:01:01 +0100134 .send(true)
135 if (confirmation.success) {
pineafan8b4b17f2022-02-27 20:42:52 +0000136 let dmd = false
pineafan5d1908e2022-02-28 21:34:47 +0000137 let dm;
pineafan377794f2022-04-18 19:01:01 +0100138 let config = await readConfig(interaction.guild.id);
pineafan8b4b17f2022-02-27 20:42:52 +0000139 try {
140 if (interaction.options.getString("notify") != "no") {
pineafan5d1908e2022-02-28 21:34:47 +0000141 dm = await (interaction.options.getMember("user") as GuildMember).send({
pineafan377794f2022-04-18 19:01:01 +0100142 embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000143 .setEmoji("PUNISH.MUTE.RED")
144 .setTitle("Muted")
145 .setDescription(`You have been muted in ${interaction.guild.name}` +
pineafan1dc15722022-03-14 21:27:34 +0000146 (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".\n\n" +
pineafan8b4b17f2022-02-27 20:42:52 +0000147 `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>)`))
148 .setStatus("Danger")
pineafan377794f2022-04-18 19:01:01 +0100149 ],
150 components: [new MessageActionRow().addComponents(config.moderation.mute.text ? [new MessageButton()
151 .setStyle("LINK")
152 .setLabel(config.moderation.mute.text)
153 .setURL(config.moderation.mute.link)
154 ] : [])]
pineafan8b4b17f2022-02-27 20:42:52 +0000155 })
156 dmd = true
157 }
158 } catch {}
159 try {
160 (interaction.options.getMember("user") as GuildMember).timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided")
pineafan8b4b17f2022-02-27 20:42:52 +0000161 } catch {
pineafan377794f2022-04-18 19:01:01 +0100162 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000163 .setEmoji("PUNISH.MUTE.RED")
164 .setTitle(`Mute`)
pineafan377794f2022-04-18 19:01:01 +0100165 .setDescription("Something went wrong and the user was not mute")
pineafan8b4b17f2022-02-27 20:42:52 +0000166 .setStatus("Danger")
167 ], components: []})
pineafan5d1908e2022-02-28 21:34:47 +0000168 if (dmd) await dm.delete()
169 return
pineafan8b4b17f2022-02-27 20:42:52 +0000170 }
pineafan5d1908e2022-02-28 21:34:47 +0000171 let failed = (dmd == false && interaction.options.getString("notify") != "no")
pineafan377794f2022-04-18 19:01:01 +0100172 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000173 .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`)
174 .setTitle(`Mute`)
175 .setDescription("The member was muted" + (failed ? ", but could not be notified" : ""))
176 .setStatus(failed ? "Warning" : "Success")
177 ], components: []})
pineafan377794f2022-04-18 19:01:01 +0100178 let data = {
179 meta:{
180 type: 'memberMute',
181 displayName: 'Member Muted',
182 calculateType: 'guildMemberPunish',
183 color: NucleusColors.yellow,
184 emoji: 'PUNISH.WARN.YELLOW',
185 timestamp: new Date().getTime()
186 },
187 list: {
188 user: entry((interaction.options.getMember("user") as GuildMember).user.id, renderUser((interaction.options.getMember("user") as GuildMember).user)),
189 mutedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
190 time: entry(muteTime, `${humanizeDuration(muteTime * 1000, {round: true})}`),
191 reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
192 },
193 hidden: {
194 guild: interaction.guild.id
195 }
196 }
197 log(data, interaction.client);
pineafan8b4b17f2022-02-27 20:42:52 +0000198 } else {
pineafan377794f2022-04-18 19:01:01 +0100199 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000200 .setEmoji("PUNISH.MUTE.GREEN")
201 .setTitle(`Mute`)
202 .setDescription("No changes were made")
203 .setStatus("Success")
204 ], components: []})
205 }
206}
207
208const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
pineafan5d1908e2022-02-28 21:34:47 +0000209 let member = (interaction.member as GuildMember)
210 let me = (interaction.guild.me as GuildMember)
211 let apply = (interaction.options.getMember("user") as GuildMember)
212 if (member == null || me == null || apply == null) throw "That member is not in the server"
213 let memberPos = member.roles ? member.roles.highest.position : 0
214 let mePos = me.roles ? me.roles.highest.position : 0
215 let applyPos = apply.roles ? apply.roles.highest.position : 0
pineafan8b4b17f2022-02-27 20:42:52 +0000216 // Check if Nucleus can mute the member
pineafan5d1908e2022-02-28 21:34:47 +0000217 if (! (mePos > applyPos)) throw "I do not have a role higher than that member"
pineafan8b4b17f2022-02-27 20:42:52 +0000218 // Check if Nucleus has permission to mute
219 if (! interaction.guild.me.permissions.has("MODERATE_MEMBERS")) throw "I do not have the `moderate_members` permission";
220 // Do not allow the user to have admin or be the owner
221 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"
222 // Do not allow muting Nucleus
223 if ((interaction.member as GuildMember).id == interaction.guild.me.id) throw "I cannot mute myself"
224 // Allow the owner to mute anyone
225 if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true
226 // Check if the user has moderate_members permission
227 if (! (interaction.member as GuildMember).permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission";
228 // Check if the user is below on the role list
pineafan5d1908e2022-02-28 21:34:47 +0000229 if (! (memberPos > applyPos)) throw "You do not have a role higher than that member"
pineafan8b4b17f2022-02-27 20:42:52 +0000230 // Allow mute
231 return true
232}
233
234export { command, callback, check };