blob: 5d1bd9420763c0f5bed023f5fee0f6965b420974 [file] [log] [blame]
TheCodedProf21c08592022-09-13 14:14:43 -04001import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
TheCodedProff86ba092023-01-27 17:10:07 -05002import type { SlashCommandSubcommandBuilder } from "discord.js";
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";
TheCodedProf94ff6de2023-02-22 17:47:26 -05008import getEmojiByName from "../../utils/getEmojiByName.js";
PineaFan0d06edc2023-01-17 22:10:31 +00009import { LinkWarningFooter } from "../../utils/defaults.js";
pineafan4f164f32022-02-26 22:07:12 +000010
11const command = (builder: SlashCommandSubcommandBuilder) =>
12 builder
pineafan63fc5e22022-08-04 22:04:10 +010013 .setName("warn")
14 .setDescription("Warns a user")
Skyler Grey11236ba2022-08-08 21:13:33 +010015 .addUserOption((option) => option.setName("user").setDescription("The user to warn").setRequired(true));
pineafan4f164f32022-02-26 22:07:12 +000016
pineafan3a02ea32022-08-11 21:35:04 +010017const callback = async (interaction: CommandInteraction): Promise<unknown> => {
PineaFana00db1b2023-01-02 15:32:54 +000018 if (interaction.guild === null) return;
pineafan63fc5e22022-08-04 22:04:10 +010019 const { log, NucleusColors, renderUser, entry } = client.logger;
pineafan8b4b17f2022-02-27 20:42:52 +000020 // TODO:[Modals] Replace this with a modal
PineaFan100df682023-01-02 13:26:08 +000021 let reason: string | null = null;
pineafan02ba0232022-07-24 22:16:15 +010022 let notify = true;
23 let createAppealTicket = false;
pineafan73a7c4a2022-07-24 10:38:04 +010024 let confirmation;
Skyler Greyad002172022-08-16 18:48:26 +010025 let timedOut = false;
26 let success = false;
pineafan62ce1922022-08-25 20:34:45 +010027 do {
pineafan73a7c4a2022-07-24 10:38:04 +010028 confirmation = await new confirmationMessage(interaction)
29 .setEmoji("PUNISH.WARN.RED")
30 .setTitle("Warn")
Skyler Grey75ea9172022-08-06 10:22:23 +010031 .setDescription(
32 keyValueList({
PineaFan100df682023-01-02 13:26:08 +000033 user: renderUser(interaction.options.getUser("user")!),
Skyler Greyad002172022-08-16 18:48:26 +010034 reason: reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*"
Skyler Greyda16adf2023-03-05 10:22:12 +000035 }) + `Are you sure you want to warn <@!${(interaction.options.getMember("user") as GuildMember).id}>?`
Skyler Grey75ea9172022-08-06 10:22:23 +010036 )
pineafan73a7c4a2022-07-24 10:38:04 +010037 .setColor("Danger")
38 .addCustomBoolean(
Skyler Grey75ea9172022-08-06 10:22:23 +010039 "appeal",
40 "Create appeal ticket",
PineaFana00db1b2023-01-02 15:32:54 +000041 !(await areTicketsEnabled(interaction.guild.id)),
Skyler Greyda16adf2023-03-05 10:22:12 +000042 async () =>
43 await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
PineaFan100df682023-01-02 13:26:08 +000044 "An appeal ticket will be created",
PineaFana34d04b2023-01-03 22:05:42 +000045 null,
Skyler Grey75ea9172022-08-06 10:22:23 +010046 "CONTROL.TICKET",
47 createAppealTicket
48 )
49 .addCustomBoolean(
50 "notify",
51 "Notify user",
52 false,
53 null,
PineaFan100df682023-01-02 13:26:08 +000054 "The user will be sent a DM",
PineaFana34d04b2023-01-03 22:05:42 +000055 null,
Skyler Grey75ea9172022-08-06 10:22:23 +010056 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
57 notify
58 )
pineafan73a7c4a2022-07-24 10:38:04 +010059 .addReasonButton(reason ?? "")
PineaFan1dee28f2023-01-16 22:09:07 +000060 .setFailedMessage("No changes were made", "Success", "PUNISH.WARN.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +010061 .send(reason !== null);
62 reason = reason ?? "";
Skyler Greyad002172022-08-16 18:48:26 +010063 if (confirmation.cancelled) timedOut = true;
pineafan62ce1922022-08-25 20:34:45 +010064 else if (confirmation.success !== undefined) success = true;
Skyler Greyad002172022-08-16 18:48:26 +010065 else if (confirmation.newReason) reason = confirmation.newReason;
66 else if (confirmation.components) {
PineaFan100df682023-01-02 13:26:08 +000067 notify = confirmation.components["notify"]!.active;
68 createAppealTicket = confirmation.components["appeal"]!.active;
pineafan02ba0232022-07-24 22:16:15 +010069 }
Skyler Greyda16adf2023-03-05 10:22:12 +000070 } while (!timedOut && !success);
PineaFane6ba7882023-01-18 20:41:16 +000071 if (timedOut || !success) return;
PineaFana00db1b2023-01-02 15:32:54 +000072 let dmSent = false;
73 const config = await client.database.guilds.read(interaction.guild.id);
74 try {
75 if (notify) {
Skyler Greyda16adf2023-03-05 10:22:12 +000076 if (reason) {
77 reason = reason
78 .split("\n")
79 .map((line) => "> " + line)
80 .join("\n");
81 }
PineaFana00db1b2023-01-02 15:32:54 +000082 const messageData: {
83 embeds: EmojiEmbed[];
84 components: ActionRowBuilder<ButtonBuilder>[];
85 } = {
86 embeds: [
87 new EmojiEmbed()
88 .setEmoji("PUNISH.WARN.RED")
89 .setTitle("Warned")
90 .setDescription(
91 `You have been warned in ${interaction.guild.name}` +
PineaFan538d3752023-01-12 21:48:23 +000092 (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") +
PineaFana00db1b2023-01-02 15:32:54 +000093 "\n\n" +
94 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +000095 ? `You can appeal this in the ticket created in <#${
96 confirmation.components!["appeal"]!.response
97 }>`
PineaFana00db1b2023-01-02 15:32:54 +000098 : "")
99 )
100 .setStatus("Danger")
101 ],
102 components: []
103 };
104 if (config.moderation.warn.text && config.moderation.warn.link) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000105 messageData.embeds[0]!.setFooter(LinkWarningFooter);
106 messageData.components.push(
107 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
108 new ButtonBuilder()
PineaFana00db1b2023-01-02 15:32:54 +0000109 .setStyle(ButtonStyle.Link)
110 .setLabel(config.moderation.warn.text)
Skyler Greyda16adf2023-03-05 10:22:12 +0000111 .setURL(
112 config.moderation.warn.link.replaceAll(
113 "{id}",
114 (interaction.options.getMember("user") as GuildMember).id
115 )
116 )
117 )
118 );
pineafan8b4b17f2022-02-27 20:42:52 +0000119 }
PineaFana00db1b2023-01-02 15:32:54 +0000120 await (interaction.options.getMember("user") as GuildMember).send(messageData);
121 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100122 }
PineaFana00db1b2023-01-02 15:32:54 +0000123 } catch (e) {
124 dmSent = false;
125 }
126 const data = {
127 meta: {
128 type: "memberWarn",
129 displayName: "Member warned",
130 calculateType: "guildMemberPunish",
131 color: NucleusColors.yellow,
132 emoji: "PUNISH.WARN.YELLOW",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500133 timestamp: Date.now()
PineaFana00db1b2023-01-02 15:32:54 +0000134 },
135 list: {
136 user: entry(
137 (interaction.options.getMember("user") as GuildMember).user.id,
138 renderUser((interaction.options.getMember("user") as GuildMember).user)
139 ),
140 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
TheCodedProf94ff6de2023-02-22 17:47:26 -0500141 reason: reason ? reason : "*No reason provided*"
142 },
143 separate: {
Skyler Greyda16adf2023-03-05 10:22:12 +0000144 end:
145 getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) +
146 ` The user was ${notify ? "" : "not "}notified`
PineaFana00db1b2023-01-02 15:32:54 +0000147 },
148 hidden: {
149 guild: interaction.guild.id
150 }
151 };
152 await client.database.history.create(
153 "warn",
154 interaction.guild.id,
155 (interaction.options.getMember("user") as GuildMember).user,
156 interaction.user,
157 reason
158 );
159 log(data);
160 const failed = !dmSent && notify;
161 if (!failed) {
162 await interaction.editReply({
163 embeds: [
164 new EmojiEmbed()
165 .setEmoji("PUNISH.WARN.GREEN")
166 .setTitle("Warn")
167 .setDescription(
168 "The user was warned" +
169 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000170 ? ` and an appeal ticket was opened in <#${
171 confirmation.components!["appeal"]!.response
172 }>`
PineaFana00db1b2023-01-02 15:32:54 +0000173 : "")
174 )
175 .setStatus("Success")
176 ],
177 components: []
178 });
179 } else {
180 const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
181 .permissionsIn(interaction.channel as Discord.TextChannel)
182 .has("ViewChannel");
183 const m = (await interaction.editReply({
184 embeds: [
185 new EmojiEmbed()
186 .setEmoji("PUNISH.WARN.RED")
187 .setTitle("Warn")
188 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
189 .setStatus("Danger")
190 ],
191 components: [
192 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
Skyler Greyda16adf2023-03-05 10:22:12 +0000193 new Discord.ButtonBuilder()
194 .setCustomId("log")
195 .setLabel("Ignore and log")
196 .setStyle(ButtonStyle.Secondary),
PineaFana00db1b2023-01-02 15:32:54 +0000197 new Discord.ButtonBuilder()
198 .setCustomId("here")
199 .setLabel("Warn here")
200 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
201 .setDisabled(!canSeeChannel),
202 new Discord.ButtonBuilder()
203 .setCustomId("ticket")
204 .setLabel("Create ticket")
205 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
206 .setDisabled(createAppealTicket)
207 )
208 ]
209 })) as Discord.Message;
210 let component;
211 try {
212 component = await m.awaitMessageComponent({
Skyler Greyda16adf2023-03-05 10:22:12 +0000213 filter: (i) =>
214 i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
PineaFana00db1b2023-01-02 15:32:54 +0000215 time: 300000
216 });
217 } catch (e) {
218 return await interaction.editReply({
219 embeds: [
220 new EmojiEmbed()
221 .setEmoji("PUNISH.WARN.GREEN")
222 .setTitle("Warn")
223 .setDescription("No changes were made")
224 .setStatus("Success")
225 ],
226 components: []
227 });
228 }
229 if (component.customId === "here") {
230 await interaction.channel!.send({
231 embeds: [
232 new EmojiEmbed()
233 .setEmoji("PUNISH.WARN.RED")
234 .setTitle("Warn")
235 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
236 .setStatus("Danger")
237 ],
238 content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
239 allowedMentions: {
240 users: [(interaction.options.getMember("user") as GuildMember).id]
241 }
242 });
243 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100244 embeds: [
245 new EmojiEmbed()
246 .setEmoji("PUNISH.WARN.GREEN")
247 .setTitle("Warn")
248 .setDescription(
249 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000250 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000251 ? ` and an appeal ticket was opened in <#${
252 confirmation.components!["appeal"]!.response
253 }>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100254 : "")
255 )
256 .setStatus("Success")
257 ],
258 components: []
259 });
PineaFana00db1b2023-01-02 15:32:54 +0000260 } else if (component.customId === "log") {
261 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100262 embeds: [
263 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000264 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000266 .setDescription("The warn was logged")
267 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100268 ],
PineaFana00db1b2023-01-02 15:32:54 +0000269 components: []
270 });
271 } else if (component.customId === "ticket") {
272 const ticketChannel = await create(
273 interaction.guild,
274 interaction.options.getUser("user")!,
275 interaction.user,
276 reason,
277 "Warn Notification"
278 );
279 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100280 return await interaction.editReply({
281 embeds: [
282 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100283 .setEmoji("PUNISH.WARN.RED")
284 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000285 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100286 .setStatus("Danger")
287 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100288 components: []
289 });
290 }
PineaFana00db1b2023-01-02 15:32:54 +0000291 await interaction.editReply({
292 embeds: [
293 new EmojiEmbed()
294 .setEmoji("PUNISH.WARN.GREEN")
295 .setTitle("Warn")
296 .setDescription(`A ticket was created in <#${ticketChannel}>`)
297 .setStatus("Success")
298 ],
299 components: []
300 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100301 }
pineafan8b4b17f2022-02-27 20:42:52 +0000302 }
pineafan63fc5e22022-08-04 22:04:10 +0100303};
pineafan4f164f32022-02-26 22:07:12 +0000304
TheCodedProff86ba092023-01-27 17:10:07 -0500305const check = (interaction: CommandInteraction, partial: boolean = false) => {
PineaFana00db1b2023-01-02 15:32:54 +0000306 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100307 const member = interaction.member as GuildMember;
Skyler Greyda16adf2023-03-05 10:22:12 +0000308 if (!member.permissions.has("ModerateMembers")) return "You do not have the *Moderate Members* permission";
309 if (partial) return true;
Skyler Greyad002172022-08-16 18:48:26 +0100310 const apply = interaction.options.getMember("user") as GuildMember | null;
PineaFan0d06edc2023-01-17 22:10:31 +0000311 if (apply === null) return "That member is not in the server";
Skyler Greyad002172022-08-16 18:48:26 +0100312 const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
313 const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000314 // Do not allow warning bots
PineaFan0d06edc2023-01-17 22:10:31 +0000315 if (member.user.bot) return "I cannot warn bots";
pineafan8b4b17f2022-02-27 20:42:52 +0000316 // Allow the owner to warn anyone
PineaFana00db1b2023-01-02 15:32:54 +0000317 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000318 // Check if the user has moderate_members permission
pineafan8b4b17f2022-02-27 20:42:52 +0000319 // Check if the user is below on the role list
TheCodedProf0941da42023-02-18 20:28:04 -0500320 if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
pineafan8b4b17f2022-02-27 20:42:52 +0000321 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100322 return true;
323};
pineafan4f164f32022-02-26 22:07:12 +0000324
Skyler Grey75ea9172022-08-06 10:22:23 +0100325export { command, callback, check };