blob: 232219ba223c17115c193c71d772b7b2d4d297d2 [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) {
Skyler Greyda16adf2023-03-05 10:22:12 +000087 if (reason) {
88 reason = reason
89 .split("\n")
90 .map((line) => "> " + line)
91 .join("\n");
92 }
PineaFana00db1b2023-01-02 15:32:54 +000093 const messageData: {
94 embeds: EmojiEmbed[];
95 components: ActionRowBuilder<ButtonBuilder>[];
96 } = {
97 embeds: [
98 new EmojiEmbed()
99 .setEmoji("PUNISH.WARN.RED")
100 .setTitle("Warned")
101 .setDescription(
102 `You have been warned in ${interaction.guild.name}` +
PineaFan538d3752023-01-12 21:48:23 +0000103 (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") +
PineaFana00db1b2023-01-02 15:32:54 +0000104 "\n\n" +
105 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000106 ? `You can appeal this in the ticket created in <#${
107 confirmation.components!["appeal"]!.response
108 }>`
PineaFana00db1b2023-01-02 15:32:54 +0000109 : "")
110 )
111 .setStatus("Danger")
112 ],
113 components: []
114 };
115 if (config.moderation.warn.text && config.moderation.warn.link) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000116 messageData.embeds[0]!.setFooter(LinkWarningFooter);
117 messageData.components.push(
118 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
119 new ButtonBuilder()
PineaFana00db1b2023-01-02 15:32:54 +0000120 .setStyle(ButtonStyle.Link)
121 .setLabel(config.moderation.warn.text)
pineafan1e462ab2023-03-07 21:34:06 +0000122 .setURL(config.moderation.warn.link.replaceAll("{id}", member.id))
Skyler Greyda16adf2023-03-05 10:22:12 +0000123 )
124 );
pineafan8b4b17f2022-02-27 20:42:52 +0000125 }
pineafan6de4da52023-03-07 20:43:44 +0000126 await member.send(messageData);
PineaFana00db1b2023-01-02 15:32:54 +0000127 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100128 }
PineaFana00db1b2023-01-02 15:32:54 +0000129 } catch (e) {
130 dmSent = false;
131 }
132 const data = {
133 meta: {
134 type: "memberWarn",
135 displayName: "Member warned",
136 calculateType: "guildMemberPunish",
137 color: NucleusColors.yellow,
138 emoji: "PUNISH.WARN.YELLOW",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500139 timestamp: Date.now()
PineaFana00db1b2023-01-02 15:32:54 +0000140 },
141 list: {
pineafan1e462ab2023-03-07 21:34:06 +0000142 user: entry(member.user.id, renderUser(member.user)),
PineaFana00db1b2023-01-02 15:32:54 +0000143 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
TheCodedProf94ff6de2023-02-22 17:47:26 -0500144 reason: reason ? reason : "*No reason provided*"
145 },
146 separate: {
Skyler Greyda16adf2023-03-05 10:22:12 +0000147 end:
148 getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) +
149 ` The user was ${notify ? "" : "not "}notified`
PineaFana00db1b2023-01-02 15:32:54 +0000150 },
151 hidden: {
152 guild: interaction.guild.id
153 }
154 };
pineafan1e462ab2023-03-07 21:34:06 +0000155 await client.database.history.create("warn", interaction.guild.id, member.user, interaction.user, reason);
PineaFana00db1b2023-01-02 15:32:54 +0000156 log(data);
157 const failed = !dmSent && notify;
158 if (!failed) {
159 await interaction.editReply({
160 embeds: [
161 new EmojiEmbed()
162 .setEmoji("PUNISH.WARN.GREEN")
163 .setTitle("Warn")
164 .setDescription(
165 "The user was warned" +
166 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000167 ? ` and an appeal ticket was opened in <#${
168 confirmation.components!["appeal"]!.response
169 }>`
PineaFana00db1b2023-01-02 15:32:54 +0000170 : "")
171 )
172 .setStatus("Success")
173 ],
174 components: []
175 });
176 } else {
pineafan1e462ab2023-03-07 21:34:06 +0000177 const canSeeChannel = member.permissionsIn(interaction.channel as Discord.TextChannel).has("ViewChannel");
PineaFana00db1b2023-01-02 15:32:54 +0000178 const m = (await interaction.editReply({
179 embeds: [
180 new EmojiEmbed()
181 .setEmoji("PUNISH.WARN.RED")
182 .setTitle("Warn")
183 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
184 .setStatus("Danger")
185 ],
186 components: [
187 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
Skyler Greyda16adf2023-03-05 10:22:12 +0000188 new Discord.ButtonBuilder()
189 .setCustomId("log")
190 .setLabel("Ignore and log")
191 .setStyle(ButtonStyle.Secondary),
PineaFana00db1b2023-01-02 15:32:54 +0000192 new Discord.ButtonBuilder()
193 .setCustomId("here")
194 .setLabel("Warn here")
195 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
196 .setDisabled(!canSeeChannel),
197 new Discord.ButtonBuilder()
198 .setCustomId("ticket")
199 .setLabel("Create ticket")
200 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
201 .setDisabled(createAppealTicket)
202 )
203 ]
204 })) as Discord.Message;
205 let component;
206 try {
207 component = await m.awaitMessageComponent({
Skyler Greyda16adf2023-03-05 10:22:12 +0000208 filter: (i) =>
209 i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
PineaFana00db1b2023-01-02 15:32:54 +0000210 time: 300000
211 });
212 } catch (e) {
213 return await interaction.editReply({
214 embeds: [
215 new EmojiEmbed()
216 .setEmoji("PUNISH.WARN.GREEN")
217 .setTitle("Warn")
218 .setDescription("No changes were made")
219 .setStatus("Success")
220 ],
221 components: []
222 });
223 }
224 if (component.customId === "here") {
225 await interaction.channel!.send({
226 embeds: [
227 new EmojiEmbed()
228 .setEmoji("PUNISH.WARN.RED")
229 .setTitle("Warn")
230 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
231 .setStatus("Danger")
232 ],
pineafan6de4da52023-03-07 20:43:44 +0000233 content: `<@!${member.id}>`,
PineaFana00db1b2023-01-02 15:32:54 +0000234 allowedMentions: {
pineafan6de4da52023-03-07 20:43:44 +0000235 users: [member.id]
PineaFana00db1b2023-01-02 15:32:54 +0000236 }
237 });
238 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 embeds: [
240 new EmojiEmbed()
241 .setEmoji("PUNISH.WARN.GREEN")
242 .setTitle("Warn")
243 .setDescription(
244 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000245 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000246 ? ` and an appeal ticket was opened in <#${
247 confirmation.components!["appeal"]!.response
248 }>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100249 : "")
250 )
251 .setStatus("Success")
252 ],
253 components: []
254 });
PineaFana00db1b2023-01-02 15:32:54 +0000255 } else if (component.customId === "log") {
256 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100257 embeds: [
258 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000259 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100260 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000261 .setDescription("The warn was logged")
262 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100263 ],
PineaFana00db1b2023-01-02 15:32:54 +0000264 components: []
265 });
266 } else if (component.customId === "ticket") {
267 const ticketChannel = await create(
268 interaction.guild,
pineafan6de4da52023-03-07 20:43:44 +0000269 member.user,
PineaFana00db1b2023-01-02 15:32:54 +0000270 interaction.user,
271 reason,
272 "Warn Notification"
273 );
274 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100275 return await interaction.editReply({
276 embeds: [
277 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100278 .setEmoji("PUNISH.WARN.RED")
279 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000280 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100281 .setStatus("Danger")
282 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100283 components: []
284 });
285 }
PineaFana00db1b2023-01-02 15:32:54 +0000286 await interaction.editReply({
287 embeds: [
288 new EmojiEmbed()
289 .setEmoji("PUNISH.WARN.GREEN")
290 .setTitle("Warn")
291 .setDescription(`A ticket was created in <#${ticketChannel}>`)
292 .setStatus("Success")
293 ],
294 components: []
295 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100296 }
pineafan8b4b17f2022-02-27 20:42:52 +0000297 }
pineafan63fc5e22022-08-04 22:04:10 +0100298};
pineafan4f164f32022-02-26 22:07:12 +0000299
pineafan6de4da52023-03-07 20:43:44 +0000300const check = (interaction: CommandInteraction | ButtonInteraction, partial: boolean = false, target?: GuildMember) => {
PineaFana00db1b2023-01-02 15:32:54 +0000301 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100302 const member = interaction.member as GuildMember;
Skyler Greyda16adf2023-03-05 10:22:12 +0000303 if (!member.permissions.has("ModerateMembers")) return "You do not have the *Moderate Members* permission";
304 if (partial) return true;
pineafan6de4da52023-03-07 20:43:44 +0000305 let apply: GuildMember;
306 if (interaction.isButton()) {
307 apply = target!;
308 } else {
309 apply = interaction.options.getMember("user") as GuildMember;
310 }
Skyler Greyad002172022-08-16 18:48:26 +0100311 const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
312 const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000313 // Do not allow warning bots
PineaFan0d06edc2023-01-17 22:10:31 +0000314 if (member.user.bot) return "I cannot warn bots";
pineafan8b4b17f2022-02-27 20:42:52 +0000315 // Allow the owner to warn anyone
PineaFana00db1b2023-01-02 15:32:54 +0000316 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000317 // Check if the user has moderate_members permission
pineafan8b4b17f2022-02-27 20:42:52 +0000318 // Check if the user is below on the role list
TheCodedProf0941da42023-02-18 20:28:04 -0500319 if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
pineafan8b4b17f2022-02-27 20:42:52 +0000320 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100321 return true;
322};
pineafan4f164f32022-02-26 22:07:12 +0000323
Skyler Grey75ea9172022-08-06 10:22:23 +0100324export { command, callback, check };