blob: 020a962b474049391f90a8545d1680e7c142abaf [file] [log] [blame]
pineafan1e462ab2023-03-07 21:34:06 +00001import Discord, {
2 CommandInteraction,
3 GuildMember,
4 ActionRowBuilder,
5 ButtonBuilder,
6 ButtonStyle,
7 ButtonInteraction
8} from "discord.js";
TheCodedProff86ba092023-01-27 17:10:07 -05009import type { SlashCommandSubcommandBuilder } from "discord.js";
pineafan8b4b17f2022-02-27 20:42:52 +000010import confirmationMessage from "../../utils/confirmationMessage.js";
pineafan4edb7762022-06-26 19:21:04 +010011import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +000012import keyValueList from "../../utils/generateKeyValueList.js";
Skyler Grey11236ba2022-08-08 21:13:33 +010013import { create, areTicketsEnabled } from "../../actions/createModActionTicket.js";
pineafan63fc5e22022-08-04 22:04:10 +010014import client from "../../utils/client.js";
TheCodedProf94ff6de2023-02-22 17:47:26 -050015import getEmojiByName from "../../utils/getEmojiByName.js";
PineaFan0d06edc2023-01-17 22:10:31 +000016import { LinkWarningFooter } from "../../utils/defaults.js";
pineafan4f164f32022-02-26 22:07:12 +000017
18const command = (builder: SlashCommandSubcommandBuilder) =>
19 builder
pineafan63fc5e22022-08-04 22:04:10 +010020 .setName("warn")
21 .setDescription("Warns a user")
Skyler Grey11236ba2022-08-08 21:13:33 +010022 .addUserOption((option) => option.setName("user").setDescription("The user to warn").setRequired(true));
pineafan4f164f32022-02-26 22:07:12 +000023
pineafan1e462ab2023-03-07 21:34:06 +000024const callback = async (
25 interaction: CommandInteraction | ButtonInteraction,
26 member?: GuildMember
27): Promise<unknown> => {
pineafan6de4da52023-03-07 20:43:44 +000028 if (!interaction.guild) return;
pineafan63fc5e22022-08-04 22:04:10 +010029 const { log, NucleusColors, renderUser, entry } = client.logger;
pineafan6de4da52023-03-07 20:43:44 +000030 if (!interaction.isButton()) member = interaction.options.getMember("user") as GuildMember;
31 if (!member) return;
pineafan8b4b17f2022-02-27 20:42:52 +000032 // TODO:[Modals] Replace this with a modal
PineaFan100df682023-01-02 13:26:08 +000033 let reason: string | null = null;
pineafan02ba0232022-07-24 22:16:15 +010034 let notify = true;
35 let createAppealTicket = false;
pineafan73a7c4a2022-07-24 10:38:04 +010036 let confirmation;
Skyler Greyad002172022-08-16 18:48:26 +010037 let timedOut = false;
38 let success = false;
pineafan62ce1922022-08-25 20:34:45 +010039 do {
pineafan73a7c4a2022-07-24 10:38:04 +010040 confirmation = await new confirmationMessage(interaction)
41 .setEmoji("PUNISH.WARN.RED")
42 .setTitle("Warn")
Skyler Grey75ea9172022-08-06 10:22:23 +010043 .setDescription(
44 keyValueList({
pineafan6de4da52023-03-07 20:43:44 +000045 user: renderUser(member.user),
Skyler Greyad002172022-08-16 18:48:26 +010046 reason: reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*"
pineafan6de4da52023-03-07 20:43:44 +000047 }) + `Are you sure you want to warn <@!${member.id}>?`
Skyler Grey75ea9172022-08-06 10:22:23 +010048 )
pineafan73a7c4a2022-07-24 10:38:04 +010049 .setColor("Danger")
50 .addCustomBoolean(
Skyler Grey75ea9172022-08-06 10:22:23 +010051 "appeal",
52 "Create appeal ticket",
PineaFana00db1b2023-01-02 15:32:54 +000053 !(await areTicketsEnabled(interaction.guild.id)),
pineafan1e462ab2023-03-07 21:34:06 +000054 async () => await create(interaction.guild!, member!.user, interaction.user, reason),
PineaFan100df682023-01-02 13:26:08 +000055 "An appeal ticket will be created",
PineaFana34d04b2023-01-03 22:05:42 +000056 null,
Skyler Grey75ea9172022-08-06 10:22:23 +010057 "CONTROL.TICKET",
58 createAppealTicket
59 )
60 .addCustomBoolean(
61 "notify",
62 "Notify user",
63 false,
64 null,
PineaFan100df682023-01-02 13:26:08 +000065 "The user will be sent a DM",
PineaFana34d04b2023-01-03 22:05:42 +000066 null,
Skyler Grey75ea9172022-08-06 10:22:23 +010067 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
68 notify
69 )
pineafan73a7c4a2022-07-24 10:38:04 +010070 .addReasonButton(reason ?? "")
PineaFan1dee28f2023-01-16 22:09:07 +000071 .setFailedMessage("No changes were made", "Success", "PUNISH.WARN.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +010072 .send(reason !== null);
73 reason = reason ?? "";
Skyler Greyad002172022-08-16 18:48:26 +010074 if (confirmation.cancelled) timedOut = true;
pineafan62ce1922022-08-25 20:34:45 +010075 else if (confirmation.success !== undefined) success = true;
Skyler Greyad002172022-08-16 18:48:26 +010076 else if (confirmation.newReason) reason = confirmation.newReason;
77 else if (confirmation.components) {
PineaFan100df682023-01-02 13:26:08 +000078 notify = confirmation.components["notify"]!.active;
79 createAppealTicket = confirmation.components["appeal"]!.active;
pineafan02ba0232022-07-24 22:16:15 +010080 }
Skyler Greyda16adf2023-03-05 10:22:12 +000081 } while (!timedOut && !success);
PineaFane6ba7882023-01-18 20:41:16 +000082 if (timedOut || !success) return;
PineaFana00db1b2023-01-02 15:32:54 +000083 let dmSent = false;
84 const config = await client.database.guilds.read(interaction.guild.id);
85 try {
86 if (notify) {
PineaFana6e37932023-09-25 22:09:08 +010087 let formattedReason: string | null = null;
Skyler Greyda16adf2023-03-05 10:22:12 +000088 if (reason) {
PineaFana6e37932023-09-25 22:09:08 +010089 formattedReason = reason
Skyler Greyda16adf2023-03-05 10:22:12 +000090 .split("\n")
91 .map((line) => "> " + line)
92 .join("\n");
93 }
PineaFana00db1b2023-01-02 15:32:54 +000094 const messageData: {
95 embeds: EmojiEmbed[];
96 components: ActionRowBuilder<ButtonBuilder>[];
97 } = {
98 embeds: [
99 new EmojiEmbed()
100 .setEmoji("PUNISH.WARN.RED")
101 .setTitle("Warned")
102 .setDescription(
103 `You have been warned in ${interaction.guild.name}` +
PineaFana6e37932023-09-25 22:09:08 +0100104 (formattedReason ? ` for:\n${formattedReason}` : ".\n*No reason was provided*") +
PineaFana00db1b2023-01-02 15:32:54 +0000105 "\n\n" +
106 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000107 ? `You can appeal this in the ticket created in <#${
108 confirmation.components!["appeal"]!.response
109 }>`
PineaFana00db1b2023-01-02 15:32:54 +0000110 : "")
111 )
112 .setStatus("Danger")
113 ],
114 components: []
115 };
116 if (config.moderation.warn.text && config.moderation.warn.link) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000117 messageData.embeds[0]!.setFooter(LinkWarningFooter);
118 messageData.components.push(
119 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
120 new ButtonBuilder()
PineaFana00db1b2023-01-02 15:32:54 +0000121 .setStyle(ButtonStyle.Link)
122 .setLabel(config.moderation.warn.text)
pineafan1e462ab2023-03-07 21:34:06 +0000123 .setURL(config.moderation.warn.link.replaceAll("{id}", member.id))
Skyler Greyda16adf2023-03-05 10:22:12 +0000124 )
125 );
pineafan8b4b17f2022-02-27 20:42:52 +0000126 }
pineafan6de4da52023-03-07 20:43:44 +0000127 await member.send(messageData);
PineaFana00db1b2023-01-02 15:32:54 +0000128 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100129 }
PineaFana00db1b2023-01-02 15:32:54 +0000130 } catch (e) {
131 dmSent = false;
132 }
133 const data = {
134 meta: {
135 type: "memberWarn",
136 displayName: "Member warned",
137 calculateType: "guildMemberPunish",
138 color: NucleusColors.yellow,
139 emoji: "PUNISH.WARN.YELLOW",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500140 timestamp: Date.now()
PineaFana00db1b2023-01-02 15:32:54 +0000141 },
142 list: {
pineafan1e462ab2023-03-07 21:34:06 +0000143 user: entry(member.user.id, renderUser(member.user)),
PineaFana00db1b2023-01-02 15:32:54 +0000144 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
TheCodedProf94ff6de2023-02-22 17:47:26 -0500145 reason: reason ? reason : "*No reason provided*"
146 },
147 separate: {
Skyler Greyda16adf2023-03-05 10:22:12 +0000148 end:
149 getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) +
150 ` The user was ${notify ? "" : "not "}notified`
PineaFana00db1b2023-01-02 15:32:54 +0000151 },
152 hidden: {
153 guild: interaction.guild.id
154 }
155 };
pineafan1e462ab2023-03-07 21:34:06 +0000156 await client.database.history.create("warn", interaction.guild.id, member.user, interaction.user, reason);
Skyler Greyf4f21c42023-03-08 14:36:29 +0000157 await log(data);
PineaFana00db1b2023-01-02 15:32:54 +0000158 const failed = !dmSent && notify;
159 if (!failed) {
160 await interaction.editReply({
161 embeds: [
162 new EmojiEmbed()
163 .setEmoji("PUNISH.WARN.GREEN")
164 .setTitle("Warn")
165 .setDescription(
166 "The user was warned" +
167 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000168 ? ` and an appeal ticket was opened in <#${
169 confirmation.components!["appeal"]!.response
170 }>`
PineaFana00db1b2023-01-02 15:32:54 +0000171 : "")
172 )
173 .setStatus("Success")
174 ],
175 components: []
176 });
177 } else {
pineafan1e462ab2023-03-07 21:34:06 +0000178 const canSeeChannel = member.permissionsIn(interaction.channel as Discord.TextChannel).has("ViewChannel");
PineaFana00db1b2023-01-02 15:32:54 +0000179 const m = (await interaction.editReply({
180 embeds: [
181 new EmojiEmbed()
182 .setEmoji("PUNISH.WARN.RED")
183 .setTitle("Warn")
184 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
185 .setStatus("Danger")
186 ],
187 components: [
188 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
Skyler Greyda16adf2023-03-05 10:22:12 +0000189 new Discord.ButtonBuilder()
190 .setCustomId("log")
191 .setLabel("Ignore and log")
192 .setStyle(ButtonStyle.Secondary),
PineaFana00db1b2023-01-02 15:32:54 +0000193 new Discord.ButtonBuilder()
194 .setCustomId("here")
195 .setLabel("Warn here")
196 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
197 .setDisabled(!canSeeChannel),
198 new Discord.ButtonBuilder()
199 .setCustomId("ticket")
200 .setLabel("Create ticket")
201 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
202 .setDisabled(createAppealTicket)
203 )
204 ]
205 })) as Discord.Message;
206 let component;
207 try {
208 component = await m.awaitMessageComponent({
Skyler Greyda16adf2023-03-05 10:22:12 +0000209 filter: (i) =>
210 i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
PineaFana00db1b2023-01-02 15:32:54 +0000211 time: 300000
212 });
213 } catch (e) {
214 return await interaction.editReply({
215 embeds: [
216 new EmojiEmbed()
217 .setEmoji("PUNISH.WARN.GREEN")
218 .setTitle("Warn")
219 .setDescription("No changes were made")
220 .setStatus("Success")
221 ],
222 components: []
223 });
224 }
225 if (component.customId === "here") {
226 await interaction.channel!.send({
227 embeds: [
228 new EmojiEmbed()
229 .setEmoji("PUNISH.WARN.RED")
230 .setTitle("Warn")
231 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
232 .setStatus("Danger")
233 ],
pineafan6de4da52023-03-07 20:43:44 +0000234 content: `<@!${member.id}>`,
PineaFana00db1b2023-01-02 15:32:54 +0000235 allowedMentions: {
pineafan6de4da52023-03-07 20:43:44 +0000236 users: [member.id]
PineaFana00db1b2023-01-02 15:32:54 +0000237 }
238 });
239 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100240 embeds: [
241 new EmojiEmbed()
242 .setEmoji("PUNISH.WARN.GREEN")
243 .setTitle("Warn")
244 .setDescription(
245 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000246 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000247 ? ` and an appeal ticket was opened in <#${
248 confirmation.components!["appeal"]!.response
249 }>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100250 : "")
251 )
252 .setStatus("Success")
253 ],
254 components: []
255 });
PineaFana00db1b2023-01-02 15:32:54 +0000256 } else if (component.customId === "log") {
257 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100258 embeds: [
259 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000260 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100261 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000262 .setDescription("The warn was logged")
263 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100264 ],
PineaFana00db1b2023-01-02 15:32:54 +0000265 components: []
266 });
267 } else if (component.customId === "ticket") {
268 const ticketChannel = await create(
269 interaction.guild,
pineafan6de4da52023-03-07 20:43:44 +0000270 member.user,
PineaFana00db1b2023-01-02 15:32:54 +0000271 interaction.user,
272 reason,
273 "Warn Notification"
274 );
275 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100276 return await interaction.editReply({
277 embeds: [
278 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100279 .setEmoji("PUNISH.WARN.RED")
280 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000281 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100282 .setStatus("Danger")
283 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100284 components: []
285 });
286 }
PineaFana00db1b2023-01-02 15:32:54 +0000287 await interaction.editReply({
288 embeds: [
289 new EmojiEmbed()
290 .setEmoji("PUNISH.WARN.GREEN")
291 .setTitle("Warn")
292 .setDescription(`A ticket was created in <#${ticketChannel}>`)
293 .setStatus("Success")
294 ],
295 components: []
296 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100297 }
pineafan8b4b17f2022-02-27 20:42:52 +0000298 }
pineafan63fc5e22022-08-04 22:04:10 +0100299};
pineafan4f164f32022-02-26 22:07:12 +0000300
pineafan6de4da52023-03-07 20:43:44 +0000301const check = (interaction: CommandInteraction | ButtonInteraction, partial: boolean = false, target?: GuildMember) => {
PineaFana00db1b2023-01-02 15:32:54 +0000302 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100303 const member = interaction.member as GuildMember;
TheCodedProfa9e3e032023-04-22 17:32:19 -0400304 if (!member.permissions.has("ManageMessages")) return "You do not have the *Manage Messages* permission";
Skyler Greyda16adf2023-03-05 10:22:12 +0000305 if (partial) return true;
pineafan6de4da52023-03-07 20:43:44 +0000306 let apply: GuildMember;
307 if (interaction.isButton()) {
308 apply = target!;
309 } else {
310 apply = interaction.options.getMember("user") as GuildMember;
311 }
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 };