blob: ea4f0842bdd34d33b0edba71b5ca88828aed7501 [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 Grey75ea9172022-08-06 10:22:23 +010035 }) +
Skyler Grey11236ba2022-08-08 21:13:33 +010036 `Are you sure you want to warn <@!${(interaction.options.getMember("user") as GuildMember).id}>?`
Skyler Grey75ea9172022-08-06 10:22:23 +010037 )
pineafan73a7c4a2022-07-24 10:38:04 +010038 .setColor("Danger")
39 .addCustomBoolean(
Skyler Grey75ea9172022-08-06 10:22:23 +010040 "appeal",
41 "Create appeal ticket",
PineaFana00db1b2023-01-02 15:32:54 +000042 !(await areTicketsEnabled(interaction.guild.id)),
PineaFan100df682023-01-02 13:26:08 +000043 async () => await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
44 "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 }
pineafan62ce1922022-08-25 20:34:45 +010070 } 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) {
PineaFan538d3752023-01-12 21:48:23 +000076 if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") }
PineaFana00db1b2023-01-02 15:32:54 +000077 const messageData: {
78 embeds: EmojiEmbed[];
79 components: ActionRowBuilder<ButtonBuilder>[];
80 } = {
81 embeds: [
82 new EmojiEmbed()
83 .setEmoji("PUNISH.WARN.RED")
84 .setTitle("Warned")
85 .setDescription(
86 `You have been warned in ${interaction.guild.name}` +
PineaFan538d3752023-01-12 21:48:23 +000087 (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") +
PineaFana00db1b2023-01-02 15:32:54 +000088 "\n\n" +
89 (createAppealTicket
90 ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
91 : "")
92 )
93 .setStatus("Danger")
94 ],
95 components: []
96 };
97 if (config.moderation.warn.text && config.moderation.warn.link) {
98 messageData.embeds[0]!.setFooter(LinkWarningFooter)
99 messageData.components.push(new ActionRowBuilder<Discord.ButtonBuilder>()
100 .addComponents(new ButtonBuilder()
101 .setStyle(ButtonStyle.Link)
102 .setLabel(config.moderation.warn.text)
PineaFan9b2ac4d2023-01-18 14:41:07 +0000103 .setURL(config.moderation.warn.link.replaceAll("{id}", (interaction.options.getMember("user") as GuildMember).id))
PineaFana00db1b2023-01-02 15:32:54 +0000104 )
105 )
pineafan8b4b17f2022-02-27 20:42:52 +0000106 }
PineaFana00db1b2023-01-02 15:32:54 +0000107 await (interaction.options.getMember("user") as GuildMember).send(messageData);
108 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100109 }
PineaFana00db1b2023-01-02 15:32:54 +0000110 } catch (e) {
111 dmSent = false;
112 }
113 const data = {
114 meta: {
115 type: "memberWarn",
116 displayName: "Member warned",
117 calculateType: "guildMemberPunish",
118 color: NucleusColors.yellow,
119 emoji: "PUNISH.WARN.YELLOW",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500120 timestamp: Date.now()
PineaFana00db1b2023-01-02 15:32:54 +0000121 },
122 list: {
123 user: entry(
124 (interaction.options.getMember("user") as GuildMember).user.id,
125 renderUser((interaction.options.getMember("user") as GuildMember).user)
126 ),
127 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
TheCodedProf94ff6de2023-02-22 17:47:26 -0500128 reason: reason ? reason : "*No reason provided*"
129 },
130 separate: {
131 end: getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) + ` The user was ${notify ? "" : "not "}notified`
PineaFana00db1b2023-01-02 15:32:54 +0000132 },
133 hidden: {
134 guild: interaction.guild.id
135 }
136 };
137 await client.database.history.create(
138 "warn",
139 interaction.guild.id,
140 (interaction.options.getMember("user") as GuildMember).user,
141 interaction.user,
142 reason
143 );
144 log(data);
145 const failed = !dmSent && notify;
146 if (!failed) {
147 await interaction.editReply({
148 embeds: [
149 new EmojiEmbed()
150 .setEmoji("PUNISH.WARN.GREEN")
151 .setTitle("Warn")
152 .setDescription(
153 "The user was warned" +
154 (createAppealTicket
155 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
156 : "")
157 )
158 .setStatus("Success")
159 ],
160 components: []
161 });
162 } else {
163 const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
164 .permissionsIn(interaction.channel as Discord.TextChannel)
165 .has("ViewChannel");
166 const m = (await interaction.editReply({
167 embeds: [
168 new EmojiEmbed()
169 .setEmoji("PUNISH.WARN.RED")
170 .setTitle("Warn")
171 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
172 .setStatus("Danger")
173 ],
174 components: [
175 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
176 new Discord.ButtonBuilder().setCustomId("log").setLabel("Ignore and log").setStyle(ButtonStyle.Secondary),
177 new Discord.ButtonBuilder()
178 .setCustomId("here")
179 .setLabel("Warn here")
180 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
181 .setDisabled(!canSeeChannel),
182 new Discord.ButtonBuilder()
183 .setCustomId("ticket")
184 .setLabel("Create ticket")
185 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
186 .setDisabled(createAppealTicket)
187 )
188 ]
189 })) as Discord.Message;
190 let component;
191 try {
192 component = await m.awaitMessageComponent({
TheCodedProf267563a2023-01-21 17:00:57 -0500193 filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
PineaFana00db1b2023-01-02 15:32:54 +0000194 time: 300000
195 });
196 } catch (e) {
197 return await interaction.editReply({
198 embeds: [
199 new EmojiEmbed()
200 .setEmoji("PUNISH.WARN.GREEN")
201 .setTitle("Warn")
202 .setDescription("No changes were made")
203 .setStatus("Success")
204 ],
205 components: []
206 });
207 }
208 if (component.customId === "here") {
209 await interaction.channel!.send({
210 embeds: [
211 new EmojiEmbed()
212 .setEmoji("PUNISH.WARN.RED")
213 .setTitle("Warn")
214 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
215 .setStatus("Danger")
216 ],
217 content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
218 allowedMentions: {
219 users: [(interaction.options.getMember("user") as GuildMember).id]
220 }
221 });
222 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100223 embeds: [
224 new EmojiEmbed()
225 .setEmoji("PUNISH.WARN.GREEN")
226 .setTitle("Warn")
227 .setDescription(
228 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000229 (createAppealTicket
230 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100231 : "")
232 )
233 .setStatus("Success")
234 ],
235 components: []
236 });
PineaFana00db1b2023-01-02 15:32:54 +0000237 } else if (component.customId === "log") {
238 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 embeds: [
240 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000241 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100242 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000243 .setDescription("The warn was logged")
244 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100245 ],
PineaFana00db1b2023-01-02 15:32:54 +0000246 components: []
247 });
248 } else if (component.customId === "ticket") {
249 const ticketChannel = await create(
250 interaction.guild,
251 interaction.options.getUser("user")!,
252 interaction.user,
253 reason,
254 "Warn Notification"
255 );
256 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100257 return await interaction.editReply({
258 embeds: [
259 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100260 .setEmoji("PUNISH.WARN.RED")
261 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000262 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100263 .setStatus("Danger")
264 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 components: []
266 });
267 }
PineaFana00db1b2023-01-02 15:32:54 +0000268 await interaction.editReply({
269 embeds: [
270 new EmojiEmbed()
271 .setEmoji("PUNISH.WARN.GREEN")
272 .setTitle("Warn")
273 .setDescription(`A ticket was created in <#${ticketChannel}>`)
274 .setStatus("Success")
275 ],
276 components: []
277 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100278 }
pineafan8b4b17f2022-02-27 20:42:52 +0000279 }
pineafan63fc5e22022-08-04 22:04:10 +0100280};
pineafan4f164f32022-02-26 22:07:12 +0000281
TheCodedProff86ba092023-01-27 17:10:07 -0500282const check = (interaction: CommandInteraction, partial: boolean = false) => {
PineaFana00db1b2023-01-02 15:32:54 +0000283 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100284 const member = interaction.member as GuildMember;
TheCodedProff86ba092023-01-27 17:10:07 -0500285 if (!member.permissions.has("ModerateMembers"))
286 return "You do not have the *Moderate Members* permission";
287 if(partial) return true;
Skyler Greyad002172022-08-16 18:48:26 +0100288 const apply = interaction.options.getMember("user") as GuildMember | null;
PineaFan0d06edc2023-01-17 22:10:31 +0000289 if (apply === null) return "That member is not in the server";
Skyler Greyad002172022-08-16 18:48:26 +0100290 const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
291 const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000292 // Do not allow warning bots
PineaFan0d06edc2023-01-17 22:10:31 +0000293 if (member.user.bot) return "I cannot warn bots";
pineafan8b4b17f2022-02-27 20:42:52 +0000294 // Allow the owner to warn anyone
PineaFana00db1b2023-01-02 15:32:54 +0000295 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000296 // Check if the user has moderate_members permission
pineafan8b4b17f2022-02-27 20:42:52 +0000297 // Check if the user is below on the role list
TheCodedProf0941da42023-02-18 20:28:04 -0500298 if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
pineafan8b4b17f2022-02-27 20:42:52 +0000299 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100300 return true;
301};
pineafan4f164f32022-02-26 22:07:12 +0000302
Skyler Grey75ea9172022-08-06 10:22:23 +0100303export { command, callback, check };