blob: 390baa59564f0d24f05dec5fe952dd9b887dd586 [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";
PineaFan100df682023-01-02 13:26:08 +00008import { LinkWarningFooter } from "../../utils/defaultEmbeds.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 ?? "")
pineafan63fc5e22022-08-04 22:04:10 +010059 .send(reason !== null);
60 reason = reason ?? "";
Skyler Greyad002172022-08-16 18:48:26 +010061 if (confirmation.cancelled) timedOut = true;
pineafan62ce1922022-08-25 20:34:45 +010062 else if (confirmation.success !== undefined) success = true;
Skyler Greyad002172022-08-16 18:48:26 +010063 else if (confirmation.newReason) reason = confirmation.newReason;
64 else if (confirmation.components) {
PineaFan100df682023-01-02 13:26:08 +000065 notify = confirmation.components["notify"]!.active;
66 createAppealTicket = confirmation.components["appeal"]!.active;
pineafan02ba0232022-07-24 22:16:15 +010067 }
pineafan62ce1922022-08-25 20:34:45 +010068 } while (!timedOut && !success)
Skyler Greyad002172022-08-16 18:48:26 +010069 if (timedOut) return;
PineaFana00db1b2023-01-02 15:32:54 +000070 if (!confirmation.success) {
71 await interaction.editReply({
72 embeds: [
73 new EmojiEmbed()
74 .setEmoji("PUNISH.WARN.GREEN")
75 .setTitle("Warn")
76 .setDescription("No changes were made")
77 .setStatus("Success")
78 ],
79 components: []
80 });
81 return;
82 }
83 let dmSent = false;
84 const config = await client.database.guilds.read(interaction.guild.id);
85 try {
86 if (notify) {
87 const messageData: {
88 embeds: EmojiEmbed[];
89 components: ActionRowBuilder<ButtonBuilder>[];
90 } = {
91 embeds: [
92 new EmojiEmbed()
93 .setEmoji("PUNISH.WARN.RED")
94 .setTitle("Warned")
95 .setDescription(
96 `You have been warned in ${interaction.guild.name}` +
97 (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided*") +
98 "\n\n" +
99 (createAppealTicket
100 ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
101 : "")
102 )
103 .setStatus("Danger")
104 ],
105 components: []
106 };
107 if (config.moderation.warn.text && config.moderation.warn.link) {
108 messageData.embeds[0]!.setFooter(LinkWarningFooter)
109 messageData.components.push(new ActionRowBuilder<Discord.ButtonBuilder>()
110 .addComponents(new ButtonBuilder()
111 .setStyle(ButtonStyle.Link)
112 .setLabel(config.moderation.warn.text)
113 .setURL(config.moderation.warn.link)
114 )
115 )
pineafan8b4b17f2022-02-27 20:42:52 +0000116 }
PineaFana00db1b2023-01-02 15:32:54 +0000117 await (interaction.options.getMember("user") as GuildMember).send(messageData);
118 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100119 }
PineaFana00db1b2023-01-02 15:32:54 +0000120 } catch (e) {
121 dmSent = false;
122 }
123 const data = {
124 meta: {
125 type: "memberWarn",
126 displayName: "Member warned",
127 calculateType: "guildMemberPunish",
128 color: NucleusColors.yellow,
129 emoji: "PUNISH.WARN.YELLOW",
130 timestamp: new Date().getTime()
131 },
132 list: {
133 user: entry(
134 (interaction.options.getMember("user") as GuildMember).user.id,
135 renderUser((interaction.options.getMember("user") as GuildMember).user)
136 ),
137 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
138 reason: reason ? `\n> ${reason}` : "*No reason provided*"
139 },
140 hidden: {
141 guild: interaction.guild.id
142 }
143 };
144 await client.database.history.create(
145 "warn",
146 interaction.guild.id,
147 (interaction.options.getMember("user") as GuildMember).user,
148 interaction.user,
149 reason
150 );
151 log(data);
152 const failed = !dmSent && notify;
153 if (!failed) {
154 await interaction.editReply({
155 embeds: [
156 new EmojiEmbed()
157 .setEmoji("PUNISH.WARN.GREEN")
158 .setTitle("Warn")
159 .setDescription(
160 "The user was warned" +
161 (createAppealTicket
162 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
163 : "")
164 )
165 .setStatus("Success")
166 ],
167 components: []
168 });
169 } else {
170 const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
171 .permissionsIn(interaction.channel as Discord.TextChannel)
172 .has("ViewChannel");
173 const m = (await interaction.editReply({
174 embeds: [
175 new EmojiEmbed()
176 .setEmoji("PUNISH.WARN.RED")
177 .setTitle("Warn")
178 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
179 .setStatus("Danger")
180 ],
181 components: [
182 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
183 new Discord.ButtonBuilder().setCustomId("log").setLabel("Ignore and log").setStyle(ButtonStyle.Secondary),
184 new Discord.ButtonBuilder()
185 .setCustomId("here")
186 .setLabel("Warn here")
187 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
188 .setDisabled(!canSeeChannel),
189 new Discord.ButtonBuilder()
190 .setCustomId("ticket")
191 .setLabel("Create ticket")
192 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
193 .setDisabled(createAppealTicket)
194 )
195 ]
196 })) as Discord.Message;
197 let component;
198 try {
199 component = await m.awaitMessageComponent({
200 filter: (m) => m.user.id === interaction.user.id,
201 time: 300000
202 });
203 } catch (e) {
204 return await interaction.editReply({
205 embeds: [
206 new EmojiEmbed()
207 .setEmoji("PUNISH.WARN.GREEN")
208 .setTitle("Warn")
209 .setDescription("No changes were made")
210 .setStatus("Success")
211 ],
212 components: []
213 });
214 }
215 if (component.customId === "here") {
216 await interaction.channel!.send({
217 embeds: [
218 new EmojiEmbed()
219 .setEmoji("PUNISH.WARN.RED")
220 .setTitle("Warn")
221 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
222 .setStatus("Danger")
223 ],
224 content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
225 allowedMentions: {
226 users: [(interaction.options.getMember("user") as GuildMember).id]
227 }
228 });
229 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100230 embeds: [
231 new EmojiEmbed()
232 .setEmoji("PUNISH.WARN.GREEN")
233 .setTitle("Warn")
234 .setDescription(
235 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000236 (createAppealTicket
237 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100238 : "")
239 )
240 .setStatus("Success")
241 ],
242 components: []
243 });
PineaFana00db1b2023-01-02 15:32:54 +0000244 } else if (component.customId === "log") {
245 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100246 embeds: [
247 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000248 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100249 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000250 .setDescription("The warn was logged")
251 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100252 ],
PineaFana00db1b2023-01-02 15:32:54 +0000253 components: []
254 });
255 } else if (component.customId === "ticket") {
256 const ticketChannel = await create(
257 interaction.guild,
258 interaction.options.getUser("user")!,
259 interaction.user,
260 reason,
261 "Warn Notification"
262 );
263 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100264 return await interaction.editReply({
265 embeds: [
266 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100267 .setEmoji("PUNISH.WARN.RED")
268 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000269 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100270 .setStatus("Danger")
271 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100272 components: []
273 });
274 }
PineaFana00db1b2023-01-02 15:32:54 +0000275 await interaction.editReply({
276 embeds: [
277 new EmojiEmbed()
278 .setEmoji("PUNISH.WARN.GREEN")
279 .setTitle("Warn")
280 .setDescription(`A ticket was created in <#${ticketChannel}>`)
281 .setStatus("Success")
282 ],
283 components: []
284 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100285 }
pineafan8b4b17f2022-02-27 20:42:52 +0000286 }
pineafan63fc5e22022-08-04 22:04:10 +0100287};
pineafan4f164f32022-02-26 22:07:12 +0000288
pineafanbd02b4a2022-08-05 22:01:38 +0100289const check = (interaction: CommandInteraction) => {
PineaFana00db1b2023-01-02 15:32:54 +0000290 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100291 const member = interaction.member as GuildMember;
Skyler Greyad002172022-08-16 18:48:26 +0100292 const apply = interaction.options.getMember("user") as GuildMember | null;
293 if (apply === null) throw new Error("That member is not in the server");
294 const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
295 const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000296 // Do not allow warning bots
pineafan3a02ea32022-08-11 21:35:04 +0100297 if (member.user.bot) throw new Error("I cannot warn bots");
pineafan8b4b17f2022-02-27 20:42:52 +0000298 // Allow the owner to warn anyone
PineaFana00db1b2023-01-02 15:32:54 +0000299 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000300 // Check if the user has moderate_members permission
PineaFan100df682023-01-02 13:26:08 +0000301 if (!member.permissions.has("ModerateMembers"))
pineafan3a02ea32022-08-11 21:35:04 +0100302 throw new Error("You do not have the *Moderate Members* permission");
pineafan8b4b17f2022-02-27 20:42:52 +0000303 // Check if the user is below on the role list
pineafan3a02ea32022-08-11 21:35:04 +0100304 if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member");
pineafan8b4b17f2022-02-27 20:42:52 +0000305 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100306 return true;
307};
pineafan4f164f32022-02-26 22:07:12 +0000308
Skyler Grey75ea9172022-08-06 10:22:23 +0100309export { command, callback, check };