blob: ca0bcef8469e3f377ef644ac62e22bd8646bbc8a [file] [log] [blame]
pineafanad54d752022-04-18 19:01:43 +01001import Discord, { CommandInteraction, GuildMember, MessageActionRow } from "discord.js";
2import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
3import { WrappedCheck } from "jshaiku";
4import generateEmojiEmbed 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";
9import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
10import readConfig from '../../utils/readConfig.js'
11
12const command = (builder: SlashCommandSubcommandBuilder) =>
13 builder
14 .setName("unnamed")
15 .setDescription("Gives a user a role")
16 .addUserOption(option => option.setName("user").setDescription("The user to UNNAMED").setRequired(true)) // TODO
17 .addIntegerOption(option => option.setName("days").setDescription("The number of days to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(27).setRequired(false))
18 .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(23).setRequired(false))
19 .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
20 .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
21 .addStringOption(option => option.setName("reason").setDescription("The reason for the UNNAMED").setRequired(false))
22 .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are UNNAMED | Default yes").setRequired(false)
23 .addChoices([["Yes", "yes"], ["No", "no"]]))
24
25const callback = async (interaction: CommandInteraction) => {
26 // @ts-ignore
27 const { log, NucleusColors, renderUser, entry } = interaction.client.logger
28 let config = await readConfig(interaction.guild.id);
29 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: [
40 new generateEmojiEmbed()
41 .setEmoji("PUNISH.MUTE.GREEN") // TODO
42 .setTitle("UNNAMED")
43 .setDescription("How long should the user be UNNAMED")
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();
95 if (component.customId == "cancel") return interaction.editReply({embeds: [new generateEmojiEmbed()
96 .setEmoji("PUNISH.MUTE.RED") // TODO
97 .setTitle("UNNAMED")
98 .setDescription("UNNAMED 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: [
113 new generateEmojiEmbed()
114 .setEmoji("PUNISH.MUTE.GREEN") // TODO
115 .setTitle("UNNAMED")
116 .setDescription("Loading...")
117 .setStatus("Success")
118 ], ephemeral: true, fetchReply: true})
119 }
120 // TODO:[Modals] Replace this with a modal
121 let confirmation = await new confirmationMessage(interaction)
122 .setEmoji("PUNISH.MUTE.RED") // TODO
123 .setTitle("UNNAMED")
124 .setDescription(keyValueList({
125 "user": `<@!${user.id}> (${user.user.username})`,
126 "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
127 "reason": `\n> ${reason ? reason : "*No reason provided*"}`
128 })
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}>?`) // TODO
131 .setColor("Danger")
pineafan4092b862022-05-20 19:27:23 +0100132 .addCustomBoolean(
pineafanad54d752022-04-18 19:01:43 +0100133 "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
pineafan4092b862022-05-20 19:27:23 +0100134 async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.client),
135 "An appeal ticket will be created when Confirm is clicked")
pineafanad54d752022-04-18 19:01:43 +0100136// pluralize("day", interaction.options.getInteger("delete"))
137// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
138 .send()
139 if (confirmation.success) {
140 let dmd = false
141 let dm;
142 try {
143 if (interaction.options.getString("notify") != "no") {
144 dm = await (interaction.options.getMember("user") as GuildMember).send({
145 embeds: [new generateEmojiEmbed()
146 .setEmoji("PUNISH.MUTE.RED") // TODO
147 .setTitle("UNNAMED")
148 .setDescription(`You have been muted in ${interaction.guild.name}` + // TODO
149 (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".\n\n" +
150 `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>)`)) // TODO
151 .setStatus("Danger")
152 ]
153 })
154 dmd = true
155 }
156 } catch {}
157 try {
158 await ((interaction.options.getMember("user") as GuildMember).roles.add(interaction.guild.roles.cache.find((r) => r.id === config.moderation.role.role)))
159 // TODO: Store when to remove the role
160 } catch {
161 await interaction.editReply({embeds: [new generateEmojiEmbed()
162 .setEmoji("PUNISH.MUTE.RED")
163 .setTitle(`Mute`)
164 .setDescription("Something went wrong and the user was not UNNAMED")
165 .setStatus("Danger")
166 ], components: []})
167 if (dmd) await dm.delete()
168 return
169 }
170 let failed = (dmd == false && interaction.options.getString("notify") != "no")
171 await interaction.editReply({embeds: [new generateEmojiEmbed()
172 .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`) // TODO
173 .setTitle(`Mute`) // TODO
174 .setDescription("The member was muted" + (failed ? ", but could not be notified" : "")) // TODO
175 .setStatus(failed ? "Warning" : "Success")
176 ], components: []})
177 let data = {
178 meta:{
179 type: 'memberMute', // TODO
180 displayName: 'Member Muted', // TODO
181 calculateType: 'guildMemberPunish',
182 color: NucleusColors.yellow,
183 emoji: 'PUNISH.WARN.YELLOW', // TODO
184 timestamp: new Date().getTime()
185 },
186 list: {
187 user: entry((interaction.options.getMember("user") as GuildMember).user.id, renderUser((interaction.options.getMember("user") as GuildMember).user)),
188 mutedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)), // TODO
189 time: entry(muteTime, `${humanizeDuration(muteTime * 1000, {round: true})}`),
190 reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
191 },
192 hidden: {
193 guild: interaction.guild.id
194 }
195 }
196 log(data, interaction.client);
197 } else {
198 await interaction.editReply({embeds: [new generateEmojiEmbed()
199 .setEmoji("PUNISH.MUTE.GREEN") // TODO
200 .setTitle(`Mute`) // TODO
201 .setDescription("No changes were made")
202 .setStatus("Success")
203 ], components: []})
204 }
205}
206
207const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
208 let member = (interaction.member as GuildMember)
209 let me = (interaction.guild.me as GuildMember)
210 let apply = (interaction.options.getMember("user") as GuildMember)
211 if (member == null || me == null || apply == null) throw "That member is not in the server"
212 let memberPos = member.roles ? member.roles.highest.position : 0
213 let mePos = me.roles ? me.roles.highest.position : 0
214 let applyPos = apply.roles ? apply.roles.highest.position : 0
215 // Check if Nucleus can UNNAMED the member
216 if (! (mePos > applyPos)) throw "I do not have a role higher than that member"
217 // Check if Nucleus has permission to UNNAMED
PineappleFan5fe720d2022-05-19 12:01:49 +0100218 if (! me.permissions.has("MANAGE_ROLES")) throw "I do not have the `manage_roles` permission";
pineafanad54d752022-04-18 19:01:43 +0100219 // Do not allow the user to have admin or be the owner
PineappleFan5fe720d2022-05-19 12:01:49 +0100220 if (apply.permissions.has("ADMINISTRATOR") || apply.id == interaction.guild.ownerId) throw "You cannot mute an admin or the owner"
pineafanad54d752022-04-18 19:01:43 +0100221 // Do not allow muting Nucleus
PineappleFan5fe720d2022-05-19 12:01:49 +0100222 if (member.id == me.id) throw "I cannot UNNAMED myself"
pineafanad54d752022-04-18 19:01:43 +0100223 // Allow the owner to UNNAMED anyone
pineafan663dc472022-05-10 18:13:47 +0100224 if (member.id == interaction.guild.ownerId) return true
pineafanad54d752022-04-18 19:01:43 +0100225 // Check if the user has moderate_members permission
pineafan663dc472022-05-10 18:13:47 +0100226 if (! member.permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission";
pineafanad54d752022-04-18 19:01:43 +0100227 // Check if the user is below on the role list
228 if (! (memberPos > applyPos)) throw "You do not have a role higher than that member"
229 // Allow UNNAMED
230 return true
231}
232
233export { command, callback, check };