blob: 79beeb34186a90460a7f5b12381cad17fa2c1991 [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";
pineafan6702cef2022-06-13 17:52:37 +01009import client from "../../utils/client.js";
pineafan8b4b17f2022-02-27 20:42:52 +000010
11const command = (builder: SlashCommandSubcommandBuilder) =>
12 builder
13 .setName("mute")
14 .setDescription("Mutes a member using Discord's \"Timeout\" feature")
15 .addUserOption(option => option.setName("user").setDescription("The user to mute").setRequired(true))
16 .addIntegerOption(option => option.setName("days").setDescription("The number of days to mute the user for | Default 0").setMinValue(0).setMaxValue(27).setRequired(false))
17 .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to mute the user for | Default 0").setMinValue(0).setMaxValue(23).setRequired(false))
18 .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
19 .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
20 .addStringOption(option => option.setName("reason").setDescription("The reason for the mute").setRequired(false))
pineafan377794f2022-04-18 19:01:01 +010021 .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 +000022 .addChoices([["Yes", "yes"], ["No", "no"]]))
pineafan8b4b17f2022-02-27 20:42:52 +000023
pineafan6702cef2022-06-13 17:52:37 +010024const callback = async (interaction: CommandInteraction): Promise<any> => {
PineappleFanb3dd83c2022-06-17 10:53:48 +010025 const { log, NucleusColors, renderUser, entry } = client.logger
pineafan8b4b17f2022-02-27 20:42:52 +000026 const user = interaction.options.getMember("user") as GuildMember
27 const reason = interaction.options.getString("reason")
28 const time = {
29 days: interaction.options.getInteger("days") || 0,
30 hours: interaction.options.getInteger("hours") || 0,
31 minutes: interaction.options.getInteger("minutes") || 0,
32 seconds: interaction.options.getInteger("seconds") || 0
33 }
pineafan6702cef2022-06-13 17:52:37 +010034 let config = await client.database.read(interaction.guild.id)
pineafane625d782022-05-09 18:04:32 +010035 let serverSettingsDescription = (config.moderation.mute.timeout ? "given a timeout" : "")
36 if (config.moderation.mute.role) serverSettingsDescription += (serverSettingsDescription ? " and " : "") + `given the <@&${config.moderation.mute.role}> role`
37
pineafan8b4b17f2022-02-27 20:42:52 +000038 let muteTime = (time.days * 24 * 60 * 60) + (time.hours * 60 * 60) + (time.minutes * 60) + time.seconds
39 if (muteTime == 0) {
40 let m = await interaction.reply({embeds: [
pineafan377794f2022-04-18 19:01:01 +010041 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000042 .setEmoji("PUNISH.MUTE.GREEN")
43 .setTitle("Mute")
44 .setDescription("How long should the user be muted")
45 .setStatus("Success")
46 ], components: [
47 new MessageActionRow().addComponents([
48 new Discord.MessageButton()
49 .setCustomId("1m")
50 .setLabel("1 Minute")
51 .setStyle("SECONDARY"),
52 new Discord.MessageButton()
53 .setCustomId("10m")
54 .setLabel("10 Minutes")
55 .setStyle("SECONDARY"),
56 new Discord.MessageButton()
57 .setCustomId("30m")
58 .setLabel("30 Minutes")
59 .setStyle("SECONDARY"),
60 new Discord.MessageButton()
61 .setCustomId("1h")
62 .setLabel("1 Hour")
63 .setStyle("SECONDARY")
64 ]),
65 new MessageActionRow().addComponents([
66 new Discord.MessageButton()
67 .setCustomId("6h")
68 .setLabel("6 Hours")
69 .setStyle("SECONDARY"),
70 new Discord.MessageButton()
71 .setCustomId("12h")
72 .setLabel("12 Hours")
73 .setStyle("SECONDARY"),
74 new Discord.MessageButton()
75 .setCustomId("1d")
76 .setLabel("1 Day")
77 .setStyle("SECONDARY"),
78 new Discord.MessageButton()
79 .setCustomId("1w")
80 .setLabel("1 Week")
81 .setStyle("SECONDARY")
82 ]),
83 new MessageActionRow().addComponents([
84 new Discord.MessageButton()
85 .setCustomId("cancel")
86 .setLabel("Cancel")
87 .setStyle("DANGER")
88 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
89 ])
90 ], ephemeral: true, fetchReply: true})
91 let component;
92 try {
pineafanc6158ab2022-06-17 16:34:07 +010093 component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 300000});
pineafan8b4b17f2022-02-27 20:42:52 +000094 } catch { return }
95 component.deferUpdate();
pineafan377794f2022-04-18 19:01:01 +010096 if (component.customId == "cancel") return interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +000097 .setEmoji("PUNISH.MUTE.RED")
98 .setTitle("Mute")
99 .setDescription("Mute cancelled")
100 .setStatus("Danger")
101 ]})
102 switch (component.customId) {
103 case "1m": { muteTime = 60; break; }
104 case "10m": { muteTime = 60 * 10; break; }
105 case "30m": { muteTime = 60 * 30; break; }
106 case "1h": { muteTime = 60 * 60; break; }
107 case "6h": { muteTime = 60 * 60 * 6; break; }
108 case "12h": { muteTime = 60 * 60 * 12; break; }
109 case "1d": { muteTime = 60 * 60 * 24; break; }
110 case "1w": { muteTime = 60 * 60 * 24 * 7; break; }
111 }
112 } else {
113 await interaction.reply({embeds: [
pineafan377794f2022-04-18 19:01:01 +0100114 new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000115 .setEmoji("PUNISH.MUTE.GREEN")
116 .setTitle("Mute")
117 .setDescription("Loading...")
118 .setStatus("Success")
119 ], ephemeral: true, fetchReply: true})
120 }
pineafan5d1908e2022-02-28 21:34:47 +0000121 // TODO:[Modals] Replace this with a modal
pineafan377794f2022-04-18 19:01:01 +0100122 let confirmation = await new confirmationMessage(interaction)
pineafan8b4b17f2022-02-27 20:42:52 +0000123 .setEmoji("PUNISH.MUTE.RED")
124 .setTitle("Mute")
125 .setDescription(keyValueList({
126 "user": `<@!${user.id}> (${user.user.username})`,
127 "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
pineafan5d1908e2022-02-28 21:34:47 +0000128 "reason": `\n> ${reason ? reason : "*No reason provided*"}`
pineafan8b4b17f2022-02-27 20:42:52 +0000129 })
pineafane625d782022-05-09 18:04:32 +0100130 + `The user will be ` + serverSettingsDescription + "\n"
pineafan8b4b17f2022-02-27 20:42:52 +0000131 + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
132 + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
133 .setColor("Danger")
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;
pineafan6702cef2022-06-13 17:52:37 +0100138 let config = await client.database.read(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 {
pineafane625d782022-05-09 18:04:32 +0100160 if (config.moderation.mute.timeout) {
161 (interaction.options.getMember("user") as GuildMember).timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided")
162 }
163 if (config.moderation.mute.role) {
164 (interaction.options.getMember("user") as GuildMember).roles.add(config.moderation.mute.role)
165 }
pineafan8b4b17f2022-02-27 20:42:52 +0000166 } catch {
pineafan377794f2022-04-18 19:01:01 +0100167 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000168 .setEmoji("PUNISH.MUTE.RED")
169 .setTitle(`Mute`)
pineafan377794f2022-04-18 19:01:01 +0100170 .setDescription("Something went wrong and the user was not mute")
pineafan8b4b17f2022-02-27 20:42:52 +0000171 .setStatus("Danger")
172 ], components: []})
pineafan5d1908e2022-02-28 21:34:47 +0000173 if (dmd) await dm.delete()
174 return
pineafan8b4b17f2022-02-27 20:42:52 +0000175 }
pineafan5d1908e2022-02-28 21:34:47 +0000176 let failed = (dmd == false && interaction.options.getString("notify") != "no")
pineafan377794f2022-04-18 19:01:01 +0100177 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan5d1908e2022-02-28 21:34:47 +0000178 .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`)
179 .setTitle(`Mute`)
180 .setDescription("The member was muted" + (failed ? ", but could not be notified" : ""))
181 .setStatus(failed ? "Warning" : "Success")
182 ], components: []})
pineafan377794f2022-04-18 19:01:01 +0100183 let data = {
184 meta:{
185 type: 'memberMute',
186 displayName: 'Member Muted',
187 calculateType: 'guildMemberPunish',
188 color: NucleusColors.yellow,
189 emoji: 'PUNISH.WARN.YELLOW',
190 timestamp: new Date().getTime()
191 },
192 list: {
193 user: entry((interaction.options.getMember("user") as GuildMember).user.id, renderUser((interaction.options.getMember("user") as GuildMember).user)),
194 mutedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
195 time: entry(muteTime, `${humanizeDuration(muteTime * 1000, {round: true})}`),
196 reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
197 },
198 hidden: {
199 guild: interaction.guild.id
200 }
201 }
PineappleFanb3dd83c2022-06-17 10:53:48 +0100202 log(data, client);
pineafan8b4b17f2022-02-27 20:42:52 +0000203 } else {
pineafan377794f2022-04-18 19:01:01 +0100204 await interaction.editReply({embeds: [new generateEmojiEmbed()
pineafan8b4b17f2022-02-27 20:42:52 +0000205 .setEmoji("PUNISH.MUTE.GREEN")
206 .setTitle(`Mute`)
207 .setDescription("No changes were made")
208 .setStatus("Success")
209 ], components: []})
210 }
211}
212
213const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
pineafan5d1908e2022-02-28 21:34:47 +0000214 let member = (interaction.member as GuildMember)
215 let me = (interaction.guild.me as GuildMember)
216 let apply = (interaction.options.getMember("user") as GuildMember)
217 if (member == null || me == null || apply == null) throw "That member is not in the server"
218 let memberPos = member.roles ? member.roles.highest.position : 0
219 let mePos = me.roles ? me.roles.highest.position : 0
220 let applyPos = apply.roles ? apply.roles.highest.position : 0
pineafan8b4b17f2022-02-27 20:42:52 +0000221 // Check if Nucleus can mute the member
pineafan5d1908e2022-02-28 21:34:47 +0000222 if (! (mePos > applyPos)) throw "I do not have a role higher than that member"
pineafan8b4b17f2022-02-27 20:42:52 +0000223 // Check if Nucleus has permission to mute
PineappleFan5fe720d2022-05-19 12:01:49 +0100224 if (! me.permissions.has("MODERATE_MEMBERS")) throw "I do not have the `moderate_members` permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000225 // Do not allow the user to have admin or be the owner
PineappleFan5fe720d2022-05-19 12:01:49 +0100226 if (apply.permissions.has("ADMINISTRATOR") || (interaction.options.getMember("user") as GuildMember).id == interaction.guild.ownerId) throw "You cannot mute an admin or the owner"
pineafan8b4b17f2022-02-27 20:42:52 +0000227 // Do not allow muting Nucleus
PineappleFan5fe720d2022-05-19 12:01:49 +0100228 if (member.id == me.id) throw "I cannot mute myself"
pineafan8b4b17f2022-02-27 20:42:52 +0000229 // Allow the owner to mute anyone
pineafan663dc472022-05-10 18:13:47 +0100230 if (member.id == interaction.guild.ownerId) return true
pineafan8b4b17f2022-02-27 20:42:52 +0000231 // Check if the user has moderate_members permission
pineafan663dc472022-05-10 18:13:47 +0100232 if (! member.permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000233 // Check if the user is below on the role list
pineafan5d1908e2022-02-28 21:34:47 +0000234 if (! (memberPos > applyPos)) throw "You do not have a role higher than that member"
pineafan8b4b17f2022-02-27 20:42:52 +0000235 // Allow mute
236 return true
237}
238
239export { command, callback, check };