blob: 87e6e07a81069d6822f6b6424663e51d8653783e [file] [log] [blame]
Skyler Grey11236ba2022-08-08 21:13:33 +01001import Discord, { CommandInteraction, GuildMember, MessageActionRow, MessageButton } from "discord.js";
pineafan4f164f32022-02-26 22:07:12 +00002import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
pineafan8b4b17f2022-02-27 20:42:52 +00003import confirmationMessage from "../../utils/confirmationMessage.js";
pineafan4edb7762022-06-26 19:21:04 +01004import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +00005import keyValueList from "../../utils/generateKeyValueList.js";
Skyler Grey11236ba2022-08-08 21:13:33 +01006import { create, areTicketsEnabled } from "../../actions/createModActionTicket.js";
pineafan63fc5e22022-08-04 22:04:10 +01007import client from "../../utils/client.js";
pineafan4f164f32022-02-26 22:07:12 +00008
9const command = (builder: SlashCommandSubcommandBuilder) =>
10 builder
pineafan63fc5e22022-08-04 22:04:10 +010011 .setName("warn")
12 .setDescription("Warns a user")
Skyler Grey11236ba2022-08-08 21:13:33 +010013 .addUserOption((option) => option.setName("user").setDescription("The user to warn").setRequired(true));
pineafan4f164f32022-02-26 22:07:12 +000014
pineafan3a02ea32022-08-11 21:35:04 +010015const callback = async (interaction: CommandInteraction): Promise<unknown> => {
pineafan63fc5e22022-08-04 22:04:10 +010016 const { log, NucleusColors, renderUser, entry } = client.logger;
pineafan8b4b17f2022-02-27 20:42:52 +000017 // TODO:[Modals] Replace this with a modal
pineafan73a7c4a2022-07-24 10:38:04 +010018 let reason = null;
pineafan02ba0232022-07-24 22:16:15 +010019 let notify = true;
20 let createAppealTicket = false;
pineafan73a7c4a2022-07-24 10:38:04 +010021 let confirmation;
22 while (true) {
23 confirmation = await new confirmationMessage(interaction)
24 .setEmoji("PUNISH.WARN.RED")
25 .setTitle("Warn")
Skyler Grey75ea9172022-08-06 10:22:23 +010026 .setDescription(
27 keyValueList({
28 user: renderUser(interaction.options.getUser("user")),
Skyler Grey11236ba2022-08-08 21:13:33 +010029 reason: reason ? "\n> " + (reason ?? "").replaceAll("\n", "\n> ") : "*No reason provided*"
Skyler Grey75ea9172022-08-06 10:22:23 +010030 }) +
31 `The user **will${notify ? "" : " not"}** be notified\n\n` +
Skyler Grey11236ba2022-08-08 21:13:33 +010032 `Are you sure you want to warn <@!${(interaction.options.getMember("user") as GuildMember).id}>?`
Skyler Grey75ea9172022-08-06 10:22:23 +010033 )
pineafan73a7c4a2022-07-24 10:38:04 +010034 .setColor("Danger")
35 .addCustomBoolean(
Skyler Grey75ea9172022-08-06 10:22:23 +010036 "appeal",
37 "Create appeal ticket",
38 !(await areTicketsEnabled(interaction.guild.id)),
39 async () =>
Skyler Grey11236ba2022-08-08 21:13:33 +010040 await create(interaction.guild, interaction.options.getUser("user"), interaction.user, reason),
Skyler Grey75ea9172022-08-06 10:22:23 +010041 "An appeal ticket will be created when Confirm is clicked",
42 "CONTROL.TICKET",
43 createAppealTicket
44 )
45 .addCustomBoolean(
46 "notify",
47 "Notify user",
48 false,
49 null,
50 null,
51 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
52 notify
53 )
pineafan73a7c4a2022-07-24 10:38:04 +010054 .addReasonButton(reason ?? "")
pineafan63fc5e22022-08-04 22:04:10 +010055 .send(reason !== null);
56 reason = reason ?? "";
57 if (confirmation.cancelled) return;
58 if (confirmation.success) break;
59 if (confirmation.newReason) reason = confirmation.newReason;
pineafan02ba0232022-07-24 22:16:15 +010060 if (confirmation.components) {
pineafan63fc5e22022-08-04 22:04:10 +010061 notify = confirmation.components.notify.active;
62 createAppealTicket = confirmation.components.appeal.active;
pineafan02ba0232022-07-24 22:16:15 +010063 }
pineafan73a7c4a2022-07-24 10:38:04 +010064 }
pineafan377794f2022-04-18 19:01:01 +010065 if (confirmation.success) {
pineafan63fc5e22022-08-04 22:04:10 +010066 let dmd = false;
pineafan8b4b17f2022-02-27 20:42:52 +000067 try {
pineafan02ba0232022-07-24 22:16:15 +010068 if (notify) {
Skyler Grey11236ba2022-08-08 21:13:33 +010069 const config = await client.database.guilds.read(interaction.guild.id);
70 await (interaction.options.getMember("user") as GuildMember).send({
Skyler Grey75ea9172022-08-06 10:22:23 +010071 embeds: [
72 new EmojiEmbed()
73 .setEmoji("PUNISH.WARN.RED")
74 .setTitle("Warned")
75 .setDescription(
76 `You have been warned in ${interaction.guild.name}` +
77 (reason ? ` for:\n> ${reason}` : ".") +
78 "\n\n" +
79 (confirmation.components.appeal.response
80 ? `You can appeal this here ticket: <#${confirmation.components.appeal.response}>`
81 : "")
82 )
83 .setStatus("Danger")
84 .setFooter({
85 text: config.moderation.warn.text
86 ? "The button below is set by the server admins. Do not enter any passwords or other account details on the linked site."
87 : "",
88 iconURL:
89 "https://cdn.discordapp.com/emojis/952295894370369587.webp?size=128&quality=lossless"
90 })
pineafan02ba0232022-07-24 22:16:15 +010091 ],
Skyler Grey75ea9172022-08-06 10:22:23 +010092 components: config.moderation.warn.text
93 ? [
94 new MessageActionRow().addComponents([
95 new MessageButton()
96 .setStyle("LINK")
97 .setLabel(config.moderation.warn.text)
98 .setURL(config.moderation.warn.link)
99 ])
100 ]
101 : []
pineafan63fc5e22022-08-04 22:04:10 +0100102 });
103 dmd = true;
pineafan8b4b17f2022-02-27 20:42:52 +0000104 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100105 } catch {
106 dmd = false;
107 }
pineafan63fc5e22022-08-04 22:04:10 +0100108 const data = {
Skyler Grey75ea9172022-08-06 10:22:23 +0100109 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100110 type: "memberWarn",
111 displayName: "Member warned",
112 calculateType: "guildMemberPunish",
pineafan1dc15722022-03-14 21:27:34 +0000113 color: NucleusColors.yellow,
pineafan63fc5e22022-08-04 22:04:10 +0100114 emoji: "PUNISH.WARN.YELLOW",
pineafan1dc15722022-03-14 21:27:34 +0000115 timestamp: new Date().getTime()
116 },
117 list: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100118 user: entry(
Skyler Grey11236ba2022-08-08 21:13:33 +0100119 (interaction.options.getMember("user") as GuildMember).user.id,
120 renderUser((interaction.options.getMember("user") as GuildMember).user)
Skyler Grey75ea9172022-08-06 10:22:23 +0100121 ),
Skyler Grey11236ba2022-08-08 21:13:33 +0100122 warnedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
pineafan73a7c4a2022-07-24 10:38:04 +0100123 reason: reason ? `\n> ${reason}` : "No reason provided"
pineafan1dc15722022-03-14 21:27:34 +0000124 },
125 hidden: {
126 guild: interaction.guild.id
127 }
pineafan63fc5e22022-08-04 22:04:10 +0100128 };
129 await client.database.history.create(
Skyler Grey75ea9172022-08-06 10:22:23 +0100130 "warn",
131 interaction.guild.id,
pineafan4edb7762022-06-26 19:21:04 +0100132 (interaction.options.getMember("user") as GuildMember).user,
Skyler Grey75ea9172022-08-06 10:22:23 +0100133 interaction.user,
134 reason
pineafan63fc5e22022-08-04 22:04:10 +0100135 );
pineafan4edb7762022-06-26 19:21:04 +0100136 log(data);
Skyler Grey75ea9172022-08-06 10:22:23 +0100137 const failed = !dmd && notify;
pineafan5d1908e2022-02-28 21:34:47 +0000138 if (!failed) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100139 await interaction.editReply({
140 embeds: [
141 new EmojiEmbed()
142 .setEmoji("PUNISH.WARN.GREEN")
143 .setTitle("Warn")
144 .setDescription(
145 "The user was warned" +
146 (confirmation.components.appeal.response
147 ? ` and an appeal ticket was opened in <#${confirmation.components.appeal.response}>`
148 : "")
149 )
150 .setStatus("Success")
151 ],
152 components: []
153 });
pineafan5d1908e2022-02-28 21:34:47 +0000154 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100155 const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
Skyler Grey75ea9172022-08-06 10:22:23 +0100156 .permissionsIn(interaction.channel as Discord.TextChannel)
157 .has("VIEW_CHANNEL");
158 const m = (await interaction.editReply({
159 embeds: [
160 new EmojiEmbed()
161 .setEmoji("PUNISH.WARN.RED")
162 .setTitle("Warn")
Skyler Grey11236ba2022-08-08 21:13:33 +0100163 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
Skyler Grey75ea9172022-08-06 10:22:23 +0100164 .setStatus("Danger")
165 ],
166 components: [
pineafan5d1908e2022-02-28 21:34:47 +0000167 new MessageActionRow().addComponents([
Skyler Grey11236ba2022-08-08 21:13:33 +0100168 new Discord.MessageButton().setCustomId("log").setLabel("Ignore and log").setStyle("SECONDARY"),
pineafan5d1908e2022-02-28 21:34:47 +0000169 new Discord.MessageButton()
170 .setCustomId("here")
171 .setLabel("Warn here")
pineafanc1c18792022-08-03 21:41:36 +0100172 .setStyle(canSeeChannel ? "PRIMARY" : "SECONDARY")
173 .setDisabled(!canSeeChannel),
174 new Discord.MessageButton()
175 .setCustomId("ticket")
176 .setLabel("Create ticket")
177 .setStyle(canSeeChannel ? "SECONDARY" : "PRIMARY")
pineafan5d1908e2022-02-28 21:34:47 +0000178 ])
pineafan02ba0232022-07-24 22:16:15 +0100179 ]
Skyler Grey75ea9172022-08-06 10:22:23 +0100180 })) as Discord.Message;
pineafan5d1908e2022-02-28 21:34:47 +0000181 let component;
182 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100183 component = await m.awaitMessageComponent({
184 filter: (m) => m.user.id === interaction.user.id,
185 time: 300000
186 });
pineafan5d1908e2022-02-28 21:34:47 +0000187 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100188 return await interaction.editReply({
189 embeds: [
190 new EmojiEmbed()
191 .setEmoji("PUNISH.WARN.GREEN")
192 .setTitle("Warn")
193 .setDescription("No changes were made")
194 .setStatus("Success")
195 ],
196 components: []
197 });
198 }
199 if (component.customId === "here") {
200 await interaction.channel.send({
201 embeds: [
202 new EmojiEmbed()
203 .setEmoji("PUNISH.WARN.RED")
204 .setTitle("Warn")
Skyler Grey11236ba2022-08-08 21:13:33 +0100205 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
Skyler Grey75ea9172022-08-06 10:22:23 +0100206 .setStatus("Danger")
207 ],
Skyler Grey11236ba2022-08-08 21:13:33 +0100208 content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
Skyler Grey75ea9172022-08-06 10:22:23 +0100209 allowedMentions: {
Skyler Grey11236ba2022-08-08 21:13:33 +0100210 users: [(interaction.options.getMember("user") as GuildMember).id]
Skyler Grey75ea9172022-08-06 10:22:23 +0100211 }
212 });
213 return await interaction.editReply({
214 embeds: [
215 new EmojiEmbed()
216 .setEmoji("PUNISH.WARN.GREEN")
217 .setTitle("Warn")
218 .setDescription(
219 "The user was warned" +
220 (confirmation.response
221 ? ` and an appeal ticket was opened in <#${confirmation.response}>`
222 : "")
223 )
224 .setStatus("Success")
225 ],
226 components: []
227 });
228 } else if (component.customId === "log") {
229 await interaction.editReply({
230 embeds: [
231 new EmojiEmbed()
232 .setEmoji("PUNISH.WARN.GREEN")
233 .setTitle("Warn")
234 .setDescription("The warn was logged")
235 .setStatus("Success")
236 ],
237 components: []
238 });
239 } else if (component.customId === "ticket") {
240 const ticketChannel = await create(
241 interaction.guild,
242 interaction.options.getUser("user"),
243 interaction.user,
244 reason,
245 "Warn Notification"
246 );
247 if (ticketChannel === null) {
248 return await interaction.editReply({
249 embeds: [
250 new EmojiEmbed()
251 .setEmoji("PUNISH.WARN.RED")
252 .setTitle("Warn")
253 .setDescription("A ticket could not be created")
254 .setStatus("Danger")
255 ],
256 components: []
257 });
258 }
259 await interaction.editReply({
260 embeds: [
261 new EmojiEmbed()
262 .setEmoji("PUNISH.WARN.GREEN")
263 .setTitle("Warn")
Skyler Grey11236ba2022-08-08 21:13:33 +0100264 .setDescription(`A ticket was created in <#${ticketChannel}>`)
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 .setStatus("Success")
266 ],
267 components: []
268 });
269 }
270 }
271 } else {
272 await interaction.editReply({
273 embeds: [
274 new EmojiEmbed()
pineafan63fc5e22022-08-04 22:04:10 +0100275 .setEmoji("PUNISH.WARN.GREEN")
276 .setTitle("Warn")
pineafan5d1908e2022-02-28 21:34:47 +0000277 .setDescription("No changes were made")
278 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100279 ],
280 components: []
281 });
pineafan8b4b17f2022-02-27 20:42:52 +0000282 }
pineafan63fc5e22022-08-04 22:04:10 +0100283};
pineafan4f164f32022-02-26 22:07:12 +0000284
pineafanbd02b4a2022-08-05 22:01:38 +0100285const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100286 const member = interaction.member as GuildMember;
287 const me = interaction.guild.me!;
288 const apply = interaction.options.getMember("user") as GuildMember;
pineafan3a02ea32022-08-11 21:35:04 +0100289 if (member === null || me === null || apply === null) throw new Error("That member is not in the server");
pineafan63fc5e22022-08-04 22:04:10 +0100290 const memberPos = member.roles ? member.roles.highest.position : 0;
pineafan63fc5e22022-08-04 22:04:10 +0100291 const applyPos = apply.roles ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000292 // Do not allow warning bots
pineafan3a02ea32022-08-11 21:35:04 +0100293 if (member.user.bot) throw new Error("I cannot warn bots");
pineafan8b4b17f2022-02-27 20:42:52 +0000294 // Allow the owner to warn anyone
pineafan63fc5e22022-08-04 22:04:10 +0100295 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000296 // Check if the user has moderate_members permission
pineafan3a02ea32022-08-11 21:35:04 +0100297 if (!member.permissions.has("MODERATE_MEMBERS"))
298 throw new Error("You do not have the *Moderate Members* permission");
pineafan8b4b17f2022-02-27 20:42:52 +0000299 // Check if the user is below on the role list
pineafan3a02ea32022-08-11 21:35:04 +0100300 if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member");
pineafan8b4b17f2022-02-27 20:42:52 +0000301 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100302 return true;
303};
pineafan4f164f32022-02-26 22:07:12 +0000304
Skyler Grey75ea9172022-08-06 10:22:23 +0100305export { command, callback, check };