blob: 1c5223f52d058c9241833a87c39c39faa3fa6d27 [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",
Skyler Grey75ea9172022-08-06 10:22:23 +010044 "CONTROL.TICKET",
45 createAppealTicket
46 )
47 .addCustomBoolean(
48 "notify",
49 "Notify user",
50 false,
51 null,
PineaFan100df682023-01-02 13:26:08 +000052 "The user will be sent a DM",
Skyler Grey75ea9172022-08-06 10:22:23 +010053 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
54 notify
55 )
pineafan73a7c4a2022-07-24 10:38:04 +010056 .addReasonButton(reason ?? "")
pineafan63fc5e22022-08-04 22:04:10 +010057 .send(reason !== null);
58 reason = reason ?? "";
Skyler Greyad002172022-08-16 18:48:26 +010059 if (confirmation.cancelled) timedOut = true;
pineafan62ce1922022-08-25 20:34:45 +010060 else if (confirmation.success !== undefined) success = true;
Skyler Greyad002172022-08-16 18:48:26 +010061 else if (confirmation.newReason) reason = confirmation.newReason;
62 else if (confirmation.components) {
PineaFan100df682023-01-02 13:26:08 +000063 notify = confirmation.components["notify"]!.active;
64 createAppealTicket = confirmation.components["appeal"]!.active;
pineafan02ba0232022-07-24 22:16:15 +010065 }
pineafan62ce1922022-08-25 20:34:45 +010066 } while (!timedOut && !success)
Skyler Greyad002172022-08-16 18:48:26 +010067 if (timedOut) return;
PineaFana00db1b2023-01-02 15:32:54 +000068 if (!confirmation.success) {
69 await interaction.editReply({
70 embeds: [
71 new EmojiEmbed()
72 .setEmoji("PUNISH.WARN.GREEN")
73 .setTitle("Warn")
74 .setDescription("No changes were made")
75 .setStatus("Success")
76 ],
77 components: []
78 });
79 return;
80 }
81 let dmSent = false;
82 const config = await client.database.guilds.read(interaction.guild.id);
83 try {
84 if (notify) {
85 const messageData: {
86 embeds: EmojiEmbed[];
87 components: ActionRowBuilder<ButtonBuilder>[];
88 } = {
89 embeds: [
90 new EmojiEmbed()
91 .setEmoji("PUNISH.WARN.RED")
92 .setTitle("Warned")
93 .setDescription(
94 `You have been warned in ${interaction.guild.name}` +
95 (reason ? ` for:\n> ${reason}` : ".\n*No reason was provided*") +
96 "\n\n" +
97 (createAppealTicket
98 ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
99 : "")
100 )
101 .setStatus("Danger")
102 ],
103 components: []
104 };
105 if (config.moderation.warn.text && config.moderation.warn.link) {
106 messageData.embeds[0]!.setFooter(LinkWarningFooter)
107 messageData.components.push(new ActionRowBuilder<Discord.ButtonBuilder>()
108 .addComponents(new ButtonBuilder()
109 .setStyle(ButtonStyle.Link)
110 .setLabel(config.moderation.warn.text)
111 .setURL(config.moderation.warn.link)
112 )
113 )
pineafan8b4b17f2022-02-27 20:42:52 +0000114 }
PineaFana00db1b2023-01-02 15:32:54 +0000115 await (interaction.options.getMember("user") as GuildMember).send(messageData);
116 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100117 }
PineaFana00db1b2023-01-02 15:32:54 +0000118 } catch (e) {
119 dmSent = false;
120 }
121 const data = {
122 meta: {
123 type: "memberWarn",
124 displayName: "Member warned",
125 calculateType: "guildMemberPunish",
126 color: NucleusColors.yellow,
127 emoji: "PUNISH.WARN.YELLOW",
128 timestamp: new Date().getTime()
129 },
130 list: {
131 user: entry(
132 (interaction.options.getMember("user") as GuildMember).user.id,
133 renderUser((interaction.options.getMember("user") as GuildMember).user)
134 ),
135 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
136 reason: reason ? `\n> ${reason}` : "*No reason provided*"
137 },
138 hidden: {
139 guild: interaction.guild.id
140 }
141 };
142 await client.database.history.create(
143 "warn",
144 interaction.guild.id,
145 (interaction.options.getMember("user") as GuildMember).user,
146 interaction.user,
147 reason
148 );
149 log(data);
150 const failed = !dmSent && notify;
151 if (!failed) {
152 await interaction.editReply({
153 embeds: [
154 new EmojiEmbed()
155 .setEmoji("PUNISH.WARN.GREEN")
156 .setTitle("Warn")
157 .setDescription(
158 "The user was warned" +
159 (createAppealTicket
160 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
161 : "")
162 )
163 .setStatus("Success")
164 ],
165 components: []
166 });
167 } else {
168 const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
169 .permissionsIn(interaction.channel as Discord.TextChannel)
170 .has("ViewChannel");
171 const m = (await interaction.editReply({
172 embeds: [
173 new EmojiEmbed()
174 .setEmoji("PUNISH.WARN.RED")
175 .setTitle("Warn")
176 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
177 .setStatus("Danger")
178 ],
179 components: [
180 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
181 new Discord.ButtonBuilder().setCustomId("log").setLabel("Ignore and log").setStyle(ButtonStyle.Secondary),
182 new Discord.ButtonBuilder()
183 .setCustomId("here")
184 .setLabel("Warn here")
185 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
186 .setDisabled(!canSeeChannel),
187 new Discord.ButtonBuilder()
188 .setCustomId("ticket")
189 .setLabel("Create ticket")
190 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
191 .setDisabled(createAppealTicket)
192 )
193 ]
194 })) as Discord.Message;
195 let component;
196 try {
197 component = await m.awaitMessageComponent({
198 filter: (m) => m.user.id === interaction.user.id,
199 time: 300000
200 });
201 } catch (e) {
202 return await interaction.editReply({
203 embeds: [
204 new EmojiEmbed()
205 .setEmoji("PUNISH.WARN.GREEN")
206 .setTitle("Warn")
207 .setDescription("No changes were made")
208 .setStatus("Success")
209 ],
210 components: []
211 });
212 }
213 if (component.customId === "here") {
214 await interaction.channel!.send({
215 embeds: [
216 new EmojiEmbed()
217 .setEmoji("PUNISH.WARN.RED")
218 .setTitle("Warn")
219 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
220 .setStatus("Danger")
221 ],
222 content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
223 allowedMentions: {
224 users: [(interaction.options.getMember("user") as GuildMember).id]
225 }
226 });
227 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100228 embeds: [
229 new EmojiEmbed()
230 .setEmoji("PUNISH.WARN.GREEN")
231 .setTitle("Warn")
232 .setDescription(
233 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000234 (createAppealTicket
235 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100236 : "")
237 )
238 .setStatus("Success")
239 ],
240 components: []
241 });
PineaFana00db1b2023-01-02 15:32:54 +0000242 } else if (component.customId === "log") {
243 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100244 embeds: [
245 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000246 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100247 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000248 .setDescription("The warn was logged")
249 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100250 ],
PineaFana00db1b2023-01-02 15:32:54 +0000251 components: []
252 });
253 } else if (component.customId === "ticket") {
254 const ticketChannel = await create(
255 interaction.guild,
256 interaction.options.getUser("user")!,
257 interaction.user,
258 reason,
259 "Warn Notification"
260 );
261 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100262 return await interaction.editReply({
263 embeds: [
264 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 .setEmoji("PUNISH.WARN.RED")
266 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000267 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100268 .setStatus("Danger")
269 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100270 components: []
271 });
272 }
PineaFana00db1b2023-01-02 15:32:54 +0000273 await interaction.editReply({
274 embeds: [
275 new EmojiEmbed()
276 .setEmoji("PUNISH.WARN.GREEN")
277 .setTitle("Warn")
278 .setDescription(`A ticket was created in <#${ticketChannel}>`)
279 .setStatus("Success")
280 ],
281 components: []
282 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100283 }
pineafan8b4b17f2022-02-27 20:42:52 +0000284 }
pineafan63fc5e22022-08-04 22:04:10 +0100285};
pineafan4f164f32022-02-26 22:07:12 +0000286
pineafanbd02b4a2022-08-05 22:01:38 +0100287const check = (interaction: CommandInteraction) => {
PineaFana00db1b2023-01-02 15:32:54 +0000288 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100289 const member = interaction.member as GuildMember;
Skyler Greyad002172022-08-16 18:48:26 +0100290 const apply = interaction.options.getMember("user") as GuildMember | null;
291 if (apply === null) throw new Error("That member is not in the server");
292 const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
293 const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000294 // Do not allow warning bots
pineafan3a02ea32022-08-11 21:35:04 +0100295 if (member.user.bot) throw new Error("I cannot warn bots");
pineafan8b4b17f2022-02-27 20:42:52 +0000296 // Allow the owner to warn anyone
PineaFana00db1b2023-01-02 15:32:54 +0000297 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000298 // Check if the user has moderate_members permission
PineaFan100df682023-01-02 13:26:08 +0000299 if (!member.permissions.has("ModerateMembers"))
pineafan3a02ea32022-08-11 21:35:04 +0100300 throw new Error("You do not have the *Moderate Members* permission");
pineafan8b4b17f2022-02-27 20:42:52 +0000301 // Check if the user is below on the role list
pineafan3a02ea32022-08-11 21:35:04 +0100302 if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member");
pineafan8b4b17f2022-02-27 20:42:52 +0000303 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100304 return true;
305};
pineafan4f164f32022-02-26 22:07:12 +0000306
Skyler Grey75ea9172022-08-06 10:22:23 +0100307export { command, callback, check };