blob: d920bb03bd8137731ec23acb140195fe0e19064c [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) {
PineaFan538d3752023-01-12 21:48:23 +000087 if (reason) { reason = reason.split("\n").map((line) => "> " + line).join("\n") }
PineaFana00db1b2023-01-02 15:32:54 +000088 const messageData: {
89 embeds: EmojiEmbed[];
90 components: ActionRowBuilder<ButtonBuilder>[];
91 } = {
92 embeds: [
93 new EmojiEmbed()
94 .setEmoji("PUNISH.WARN.RED")
95 .setTitle("Warned")
96 .setDescription(
97 `You have been warned in ${interaction.guild.name}` +
PineaFan538d3752023-01-12 21:48:23 +000098 (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") +
PineaFana00db1b2023-01-02 15:32:54 +000099 "\n\n" +
100 (createAppealTicket
101 ? `You can appeal this in the ticket created in <#${confirmation.components!["appeal"]!.response}>`
102 : "")
103 )
104 .setStatus("Danger")
105 ],
106 components: []
107 };
108 if (config.moderation.warn.text && config.moderation.warn.link) {
109 messageData.embeds[0]!.setFooter(LinkWarningFooter)
110 messageData.components.push(new ActionRowBuilder<Discord.ButtonBuilder>()
111 .addComponents(new ButtonBuilder()
112 .setStyle(ButtonStyle.Link)
113 .setLabel(config.moderation.warn.text)
114 .setURL(config.moderation.warn.link)
115 )
116 )
pineafan8b4b17f2022-02-27 20:42:52 +0000117 }
PineaFana00db1b2023-01-02 15:32:54 +0000118 await (interaction.options.getMember("user") as GuildMember).send(messageData);
119 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100120 }
PineaFana00db1b2023-01-02 15:32:54 +0000121 } catch (e) {
122 dmSent = false;
123 }
124 const data = {
125 meta: {
126 type: "memberWarn",
127 displayName: "Member warned",
128 calculateType: "guildMemberPunish",
129 color: NucleusColors.yellow,
130 emoji: "PUNISH.WARN.YELLOW",
131 timestamp: new Date().getTime()
132 },
133 list: {
134 user: entry(
135 (interaction.options.getMember("user") as GuildMember).user.id,
136 renderUser((interaction.options.getMember("user") as GuildMember).user)
137 ),
138 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
139 reason: reason ? `\n> ${reason}` : "*No reason provided*"
140 },
141 hidden: {
142 guild: interaction.guild.id
143 }
144 };
145 await client.database.history.create(
146 "warn",
147 interaction.guild.id,
148 (interaction.options.getMember("user") as GuildMember).user,
149 interaction.user,
150 reason
151 );
152 log(data);
153 const failed = !dmSent && notify;
154 if (!failed) {
155 await interaction.editReply({
156 embeds: [
157 new EmojiEmbed()
158 .setEmoji("PUNISH.WARN.GREEN")
159 .setTitle("Warn")
160 .setDescription(
161 "The user was warned" +
162 (createAppealTicket
163 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
164 : "")
165 )
166 .setStatus("Success")
167 ],
168 components: []
169 });
170 } else {
171 const canSeeChannel = (interaction.options.getMember("user") as GuildMember)
172 .permissionsIn(interaction.channel as Discord.TextChannel)
173 .has("ViewChannel");
174 const m = (await interaction.editReply({
175 embeds: [
176 new EmojiEmbed()
177 .setEmoji("PUNISH.WARN.RED")
178 .setTitle("Warn")
179 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
180 .setStatus("Danger")
181 ],
182 components: [
183 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
184 new Discord.ButtonBuilder().setCustomId("log").setLabel("Ignore and log").setStyle(ButtonStyle.Secondary),
185 new Discord.ButtonBuilder()
186 .setCustomId("here")
187 .setLabel("Warn here")
188 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
189 .setDisabled(!canSeeChannel),
190 new Discord.ButtonBuilder()
191 .setCustomId("ticket")
192 .setLabel("Create ticket")
193 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
194 .setDisabled(createAppealTicket)
195 )
196 ]
197 })) as Discord.Message;
198 let component;
199 try {
200 component = await m.awaitMessageComponent({
201 filter: (m) => m.user.id === interaction.user.id,
202 time: 300000
203 });
204 } catch (e) {
205 return await interaction.editReply({
206 embeds: [
207 new EmojiEmbed()
208 .setEmoji("PUNISH.WARN.GREEN")
209 .setTitle("Warn")
210 .setDescription("No changes were made")
211 .setStatus("Success")
212 ],
213 components: []
214 });
215 }
216 if (component.customId === "here") {
217 await interaction.channel!.send({
218 embeds: [
219 new EmojiEmbed()
220 .setEmoji("PUNISH.WARN.RED")
221 .setTitle("Warn")
222 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
223 .setStatus("Danger")
224 ],
225 content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
226 allowedMentions: {
227 users: [(interaction.options.getMember("user") as GuildMember).id]
228 }
229 });
230 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100231 embeds: [
232 new EmojiEmbed()
233 .setEmoji("PUNISH.WARN.GREEN")
234 .setTitle("Warn")
235 .setDescription(
236 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000237 (createAppealTicket
238 ? ` and an appeal ticket was opened in <#${confirmation.components!["appeal"]!.response}>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 : "")
240 )
241 .setStatus("Success")
242 ],
243 components: []
244 });
PineaFana00db1b2023-01-02 15:32:54 +0000245 } else if (component.customId === "log") {
246 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100247 embeds: [
248 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000249 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100250 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000251 .setDescription("The warn was logged")
252 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 ],
PineaFana00db1b2023-01-02 15:32:54 +0000254 components: []
255 });
256 } else if (component.customId === "ticket") {
257 const ticketChannel = await create(
258 interaction.guild,
259 interaction.options.getUser("user")!,
260 interaction.user,
261 reason,
262 "Warn Notification"
263 );
264 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 return await interaction.editReply({
266 embeds: [
267 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100268 .setEmoji("PUNISH.WARN.RED")
269 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000270 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100271 .setStatus("Danger")
272 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100273 components: []
274 });
275 }
PineaFana00db1b2023-01-02 15:32:54 +0000276 await interaction.editReply({
277 embeds: [
278 new EmojiEmbed()
279 .setEmoji("PUNISH.WARN.GREEN")
280 .setTitle("Warn")
281 .setDescription(`A ticket was created in <#${ticketChannel}>`)
282 .setStatus("Success")
283 ],
284 components: []
285 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100286 }
pineafan8b4b17f2022-02-27 20:42:52 +0000287 }
pineafan63fc5e22022-08-04 22:04:10 +0100288};
pineafan4f164f32022-02-26 22:07:12 +0000289
pineafanbd02b4a2022-08-05 22:01:38 +0100290const check = (interaction: CommandInteraction) => {
PineaFana00db1b2023-01-02 15:32:54 +0000291 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100292 const member = interaction.member as GuildMember;
Skyler Greyad002172022-08-16 18:48:26 +0100293 const apply = interaction.options.getMember("user") as GuildMember | null;
294 if (apply === null) throw new Error("That member is not in the server");
295 const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
296 const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000297 // Do not allow warning bots
pineafan3a02ea32022-08-11 21:35:04 +0100298 if (member.user.bot) throw new Error("I cannot warn bots");
pineafan8b4b17f2022-02-27 20:42:52 +0000299 // Allow the owner to warn anyone
PineaFana00db1b2023-01-02 15:32:54 +0000300 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000301 // Check if the user has moderate_members permission
PineaFan100df682023-01-02 13:26:08 +0000302 if (!member.permissions.has("ModerateMembers"))
pineafan3a02ea32022-08-11 21:35:04 +0100303 throw new Error("You do not have the *Moderate Members* permission");
pineafan8b4b17f2022-02-27 20:42:52 +0000304 // Check if the user is below on the role list
pineafan3a02ea32022-08-11 21:35:04 +0100305 if (!(memberPos > applyPos)) throw new Error("You do not have a role higher than that member");
pineafan8b4b17f2022-02-27 20:42:52 +0000306 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100307 return true;
308};
pineafan4f164f32022-02-26 22:07:12 +0000309
Skyler Grey75ea9172022-08-06 10:22:23 +0100310export { command, callback, check };