blob: 8f67477d2cc31b5242f934d55b527069ea9c608c [file] [log] [blame]
TheCodedProf21c08592022-09-13 14:14:43 -04001import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
PineaFan100df682023-01-02 13:26:08 +00002import type { 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";
PineaFan0d06edc2023-01-17 22:10:31 +00008import { LinkWarningFooter } from "../../utils/defaults.js";
pineafan4f164f32022-02-26 22:07:12 +00009
10const command = (builder: SlashCommandSubcommandBuilder) =>
11 builder
pineafan63fc5e22022-08-04 22:04:10 +010012 .setName("warn")
13 .setDescription("Warns a user")
Skyler Grey11236ba2022-08-08 21:13:33 +010014 .addUserOption((option) => option.setName("user").setDescription("The user to warn").setRequired(true));
pineafan4f164f32022-02-26 22:07:12 +000015
pineafan3a02ea32022-08-11 21:35:04 +010016const callback = async (interaction: CommandInteraction): Promise<unknown> => {
PineaFana00db1b2023-01-02 15:32:54 +000017 if (interaction.guild === null) return;
pineafan63fc5e22022-08-04 22:04:10 +010018 const { log, NucleusColors, renderUser, entry } = client.logger;
pineafan8b4b17f2022-02-27 20:42:52 +000019 // TODO:[Modals] Replace this with a modal
PineaFan100df682023-01-02 13:26:08 +000020 let reason: string | null = null;
pineafan02ba0232022-07-24 22:16:15 +010021 let notify = true;
22 let createAppealTicket = false;
pineafan73a7c4a2022-07-24 10:38:04 +010023 let confirmation;
Skyler Greyad002172022-08-16 18:48:26 +010024 let timedOut = false;
25 let success = false;
pineafan62ce1922022-08-25 20:34:45 +010026 do {
pineafan73a7c4a2022-07-24 10:38:04 +010027 confirmation = await new confirmationMessage(interaction)
28 .setEmoji("PUNISH.WARN.RED")
29 .setTitle("Warn")
Skyler Grey75ea9172022-08-06 10:22:23 +010030 .setDescription(
31 keyValueList({
PineaFan100df682023-01-02 13:26:08 +000032 user: renderUser(interaction.options.getUser("user")!),
Skyler Greyad002172022-08-16 18:48:26 +010033 reason: reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*"
Skyler Grey75ea9172022-08-06 10:22:23 +010034 }) +
Skyler Grey11236ba2022-08-08 21:13:33 +010035 `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)),
PineaFan100df682023-01-02 13:26:08 +000042 async () => await create(interaction.guild!, interaction.options.getUser("user")!, interaction.user, reason),
43 "An appeal ticket will be created",
PineaFana34d04b2023-01-03 22:05:42 +000044 null,
Skyler Grey75ea9172022-08-06 10:22:23 +010045 "CONTROL.TICKET",
46 createAppealTicket
47 )
48 .addCustomBoolean(
49 "notify",
50 "Notify user",
51 false,
52 null,
PineaFan100df682023-01-02 13:26:08 +000053 "The user will be sent a DM",
PineaFana34d04b2023-01-03 22:05:42 +000054 null,
Skyler Grey75ea9172022-08-06 10:22:23 +010055 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
56 notify
57 )
pineafan73a7c4a2022-07-24 10:38:04 +010058 .addReasonButton(reason ?? "")
PineaFan1dee28f2023-01-16 22:09:07 +000059 .setFailedMessage("No changes were made", "Success", "PUNISH.WARN.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +010060 .send(reason !== null);
61 reason = reason ?? "";
Skyler Greyad002172022-08-16 18:48:26 +010062 if (confirmation.cancelled) timedOut = true;
pineafan62ce1922022-08-25 20:34:45 +010063 else if (confirmation.success !== undefined) success = true;
Skyler Greyad002172022-08-16 18:48:26 +010064 else if (confirmation.newReason) reason = confirmation.newReason;
65 else if (confirmation.components) {
PineaFan100df682023-01-02 13:26:08 +000066 notify = confirmation.components["notify"]!.active;
67 createAppealTicket = confirmation.components["appeal"]!.active;
pineafan02ba0232022-07-24 22:16:15 +010068 }
pineafan62ce1922022-08-25 20:34:45 +010069 } while (!timedOut && !success)
PineaFane6ba7882023-01-18 20:41:16 +000070 if (timedOut || !success) return;
PineaFana00db1b2023-01-02 15:32:54 +000071 let dmSent = false;
72 const config = await client.database.guilds.read(interaction.guild.id);
73 try {
74 if (notify) {
PineaFan538d3752023-01-12 21:48:23 +000075 if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") }
PineaFana00db1b2023-01-02 15:32:54 +000076 const messageData: {
77 embeds: EmojiEmbed[];
78 components: ActionRowBuilder<ButtonBuilder>[];
79 } = {
80 embeds: [
81 new EmojiEmbed()
82 .setEmoji("PUNISH.WARN.RED")
83 .setTitle("Warned")
84 .setDescription(
85 `You have been warned in ${interaction.guild.name}` +
PineaFan538d3752023-01-12 21:48:23 +000086 (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") +
PineaFana00db1b2023-01-02 15:32:54 +000087 "\n\n" +
88 (createAppealTicket
89 ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
90 : "")
91 )
92 .setStatus("Danger")
93 ],
94 components: []
95 };
96 if (config.moderation.warn.text && config.moderation.warn.link) {
97 messageData.embeds[0]!.setFooter(LinkWarningFooter)
98 messageData.components.push(new ActionRowBuilder<Discord.ButtonBuilder>()
99 .addComponents(new ButtonBuilder()
100 .setStyle(ButtonStyle.Link)
101 .setLabel(config.moderation.warn.text)
PineaFan9b2ac4d2023-01-18 14:41:07 +0000102 .setURL(config.moderation.warn.link.replaceAll("{id}", (interaction.options.getMember("user") as GuildMember).id))
PineaFana00db1b2023-01-02 15:32:54 +0000103 )
104 )
pineafan8b4b17f2022-02-27 20:42:52 +0000105 }
PineaFana00db1b2023-01-02 15:32:54 +0000106 await (interaction.options.getMember("user") as GuildMember).send(messageData);
107 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100108 }
PineaFana00db1b2023-01-02 15:32:54 +0000109 } catch (e) {
110 dmSent = false;
111 }
112 const data = {
113 meta: {
114 type: "memberWarn",
115 displayName: "Member warned",
116 calculateType: "guildMemberPunish",
117 color: NucleusColors.yellow,
118 emoji: "PUNISH.WARN.YELLOW",
119 timestamp: new Date().getTime()
120 },
121 list: {
122 user: entry(
123 (interaction.options.getMember("user") as GuildMember).user.id,
124 renderUser((interaction.options.getMember("user") as GuildMember).user)
125 ),
126 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
127 reason: reason ? `\n> ${reason}` : "*No reason provided*"
128 },
129 hidden: {
130 guild: interaction.guild.id
131 }
132 };
133 await client.database.history.create(
134 "warn",
135 interaction.guild.id,
136 (interaction.options.getMember("user") as GuildMember).user,
137 interaction.user,
138 reason
139 );
140 log(data);
141 const failed = !dmSent && notify;
142 if (!failed) {
143 await interaction.editReply({
144 embeds: [
145 new EmojiEmbed()
146 .setEmoji("PUNISH.WARN.GREEN")
147 .setTitle("Warn")
148 .setDescription(
149 "The user was warned" +
150 (createAppealTicket
151 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
152 : "")
153 )
154 .setStatus("Success")
155 ],
156 components: []
157 });
158 } else {
159 const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
160 .permissionsIn(interaction.channel as Discord.TextChannel)
161 .has("ViewChannel");
162 const m = (await interaction.editReply({
163 embeds: [
164 new EmojiEmbed()
165 .setEmoji("PUNISH.WARN.RED")
166 .setTitle("Warn")
167 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
168 .setStatus("Danger")
169 ],
170 components: [
171 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
172 new Discord.ButtonBuilder().setCustomId("log").setLabel("Ignore and log").setStyle(ButtonStyle.Secondary),
173 new Discord.ButtonBuilder()
174 .setCustomId("here")
175 .setLabel("Warn here")
176 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
177 .setDisabled(!canSeeChannel),
178 new Discord.ButtonBuilder()
179 .setCustomId("ticket")
180 .setLabel("Create ticket")
181 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
182 .setDisabled(createAppealTicket)
183 )
184 ]
185 })) as Discord.Message;
186 let component;
187 try {
188 component = await m.awaitMessageComponent({
TheCodedProf267563a2023-01-21 17:00:57 -0500189 filter: (i) => i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
PineaFana00db1b2023-01-02 15:32:54 +0000190 time: 300000
191 });
192 } catch (e) {
193 return await interaction.editReply({
194 embeds: [
195 new EmojiEmbed()
196 .setEmoji("PUNISH.WARN.GREEN")
197 .setTitle("Warn")
198 .setDescription("No changes were made")
199 .setStatus("Success")
200 ],
201 components: []
202 });
203 }
204 if (component.customId === "here") {
205 await interaction.channel!.send({
206 embeds: [
207 new EmojiEmbed()
208 .setEmoji("PUNISH.WARN.RED")
209 .setTitle("Warn")
210 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
211 .setStatus("Danger")
212 ],
213 content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
214 allowedMentions: {
215 users: [(interaction.options.getMember("user") as GuildMember).id]
216 }
217 });
218 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100219 embeds: [
220 new EmojiEmbed()
221 .setEmoji("PUNISH.WARN.GREEN")
222 .setTitle("Warn")
223 .setDescription(
224 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000225 (createAppealTicket
226 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100227 : "")
228 )
229 .setStatus("Success")
230 ],
231 components: []
232 });
PineaFana00db1b2023-01-02 15:32:54 +0000233 } else if (component.customId === "log") {
234 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100235 embeds: [
236 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000237 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100238 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000239 .setDescription("The warn was logged")
240 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100241 ],
PineaFana00db1b2023-01-02 15:32:54 +0000242 components: []
243 });
244 } else if (component.customId === "ticket") {
245 const ticketChannel = await create(
246 interaction.guild,
247 interaction.options.getUser("user")!,
248 interaction.user,
249 reason,
250 "Warn Notification"
251 );
252 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 return await interaction.editReply({
254 embeds: [
255 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100256 .setEmoji("PUNISH.WARN.RED")
257 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000258 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100259 .setStatus("Danger")
260 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100261 components: []
262 });
263 }
PineaFana00db1b2023-01-02 15:32:54 +0000264 await interaction.editReply({
265 embeds: [
266 new EmojiEmbed()
267 .setEmoji("PUNISH.WARN.GREEN")
268 .setTitle("Warn")
269 .setDescription(`A ticket was created in <#${ticketChannel}>`)
270 .setStatus("Success")
271 ],
272 components: []
273 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100274 }
pineafan8b4b17f2022-02-27 20:42:52 +0000275 }
pineafan63fc5e22022-08-04 22:04:10 +0100276};
pineafan4f164f32022-02-26 22:07:12 +0000277
pineafanbd02b4a2022-08-05 22:01:38 +0100278const check = (interaction: CommandInteraction) => {
PineaFana00db1b2023-01-02 15:32:54 +0000279 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100280 const member = interaction.member as GuildMember;
Skyler Greyad002172022-08-16 18:48:26 +0100281 const apply = interaction.options.getMember("user") as GuildMember | null;
PineaFan0d06edc2023-01-17 22:10:31 +0000282 if (apply === null) return "That member is not in the server";
Skyler Greyad002172022-08-16 18:48:26 +0100283 const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
284 const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000285 // Do not allow warning bots
PineaFan0d06edc2023-01-17 22:10:31 +0000286 if (member.user.bot) return "I cannot warn bots";
pineafan8b4b17f2022-02-27 20:42:52 +0000287 // Allow the owner to warn anyone
PineaFana00db1b2023-01-02 15:32:54 +0000288 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000289 // Check if the user has moderate_members permission
PineaFan100df682023-01-02 13:26:08 +0000290 if (!member.permissions.has("ModerateMembers"))
PineaFan0d06edc2023-01-17 22:10:31 +0000291 return "You do not have the *Moderate Members* permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000292 // Check if the user is below on the role list
PineaFan0d06edc2023-01-17 22:10:31 +0000293 if (!(memberPos > applyPos)) return "You do not have a role higher than that member";
pineafan8b4b17f2022-02-27 20:42:52 +0000294 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100295 return true;
296};
pineafan4f164f32022-02-26 22:07:12 +0000297
Skyler Grey75ea9172022-08-06 10:22:23 +0100298export { command, callback, check };