blob: 8408303fa9361951309a1199537194dd559ff5d8 [file] [log] [blame]
pineafan6de4da52023-03-07 20:43:44 +00001import Discord, { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction } from "discord.js";
TheCodedProff86ba092023-01-27 17:10:07 -05002import type { SlashCommandSubcommandBuilder } from "discord.js";
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";
TheCodedProf94ff6de2023-02-22 17:47:26 -05008import getEmojiByName from "../../utils/getEmojiByName.js";
PineaFan0d06edc2023-01-17 22:10:31 +00009import { LinkWarningFooter } from "../../utils/defaults.js";
pineafan4f164f32022-02-26 22:07:12 +000010
11const command = (builder: SlashCommandSubcommandBuilder) =>
12 builder
pineafan63fc5e22022-08-04 22:04:10 +010013 .setName("warn")
14 .setDescription("Warns a user")
Skyler Grey11236ba2022-08-08 21:13:33 +010015 .addUserOption((option) => option.setName("user").setDescription("The user to warn").setRequired(true));
pineafan4f164f32022-02-26 22:07:12 +000016
pineafan6de4da52023-03-07 20:43:44 +000017const callback = async (interaction: CommandInteraction | ButtonInteraction, member?: GuildMember): Promise<unknown> => {
18 if (!interaction.guild) return;
pineafan63fc5e22022-08-04 22:04:10 +010019 const { log, NucleusColors, renderUser, entry } = client.logger;
pineafan6de4da52023-03-07 20:43:44 +000020 if (!interaction.isButton()) member = interaction.options.getMember("user") as GuildMember;
21 if (!member) return;
pineafan8b4b17f2022-02-27 20:42:52 +000022 // TODO:[Modals] Replace this with a modal
PineaFan100df682023-01-02 13:26:08 +000023 let reason: string | null = null;
pineafan02ba0232022-07-24 22:16:15 +010024 let notify = true;
25 let createAppealTicket = false;
pineafan73a7c4a2022-07-24 10:38:04 +010026 let confirmation;
Skyler Greyad002172022-08-16 18:48:26 +010027 let timedOut = false;
28 let success = false;
pineafan62ce1922022-08-25 20:34:45 +010029 do {
pineafan73a7c4a2022-07-24 10:38:04 +010030 confirmation = await new confirmationMessage(interaction)
31 .setEmoji("PUNISH.WARN.RED")
32 .setTitle("Warn")
Skyler Grey75ea9172022-08-06 10:22:23 +010033 .setDescription(
34 keyValueList({
pineafan6de4da52023-03-07 20:43:44 +000035 user: renderUser(member.user),
Skyler Greyad002172022-08-16 18:48:26 +010036 reason: reason ? "\n> " + reason.replaceAll("\n", "\n> ") : "*No reason provided*"
pineafan6de4da52023-03-07 20:43:44 +000037 }) + `Are you sure you want to warn <@!${member.id}>?`
Skyler Grey75ea9172022-08-06 10:22:23 +010038 )
pineafan73a7c4a2022-07-24 10:38:04 +010039 .setColor("Danger")
40 .addCustomBoolean(
Skyler Grey75ea9172022-08-06 10:22:23 +010041 "appeal",
42 "Create appeal ticket",
PineaFana00db1b2023-01-02 15:32:54 +000043 !(await areTicketsEnabled(interaction.guild.id)),
Skyler Greyda16adf2023-03-05 10:22:12 +000044 async () =>
pineafan6de4da52023-03-07 20:43:44 +000045 await create(interaction.guild!, member!.user, interaction.user, reason),
PineaFan100df682023-01-02 13:26:08 +000046 "An appeal ticket will be created",
PineaFana34d04b2023-01-03 22:05:42 +000047 null,
Skyler Grey75ea9172022-08-06 10:22:23 +010048 "CONTROL.TICKET",
49 createAppealTicket
50 )
51 .addCustomBoolean(
52 "notify",
53 "Notify user",
54 false,
55 null,
PineaFan100df682023-01-02 13:26:08 +000056 "The user will be sent a DM",
PineaFana34d04b2023-01-03 22:05:42 +000057 null,
Skyler Grey75ea9172022-08-06 10:22:23 +010058 "ICONS.NOTIFY." + (notify ? "ON" : "OFF"),
59 notify
60 )
pineafan73a7c4a2022-07-24 10:38:04 +010061 .addReasonButton(reason ?? "")
PineaFan1dee28f2023-01-16 22:09:07 +000062 .setFailedMessage("No changes were made", "Success", "PUNISH.WARN.GREEN")
pineafan63fc5e22022-08-04 22:04:10 +010063 .send(reason !== null);
64 reason = reason ?? "";
Skyler Greyad002172022-08-16 18:48:26 +010065 if (confirmation.cancelled) timedOut = true;
pineafan62ce1922022-08-25 20:34:45 +010066 else if (confirmation.success !== undefined) success = true;
Skyler Greyad002172022-08-16 18:48:26 +010067 else if (confirmation.newReason) reason = confirmation.newReason;
68 else if (confirmation.components) {
PineaFan100df682023-01-02 13:26:08 +000069 notify = confirmation.components["notify"]!.active;
70 createAppealTicket = confirmation.components["appeal"]!.active;
pineafan02ba0232022-07-24 22:16:15 +010071 }
Skyler Greyda16adf2023-03-05 10:22:12 +000072 } while (!timedOut && !success);
PineaFane6ba7882023-01-18 20:41:16 +000073 if (timedOut || !success) return;
PineaFana00db1b2023-01-02 15:32:54 +000074 let dmSent = false;
75 const config = await client.database.guilds.read(interaction.guild.id);
76 try {
77 if (notify) {
Skyler Greyda16adf2023-03-05 10:22:12 +000078 if (reason) {
79 reason = reason
80 .split("\n")
81 .map((line) => "> " + line)
82 .join("\n");
83 }
PineaFana00db1b2023-01-02 15:32:54 +000084 const messageData: {
85 embeds: EmojiEmbed[];
86 components: ActionRowBuilder<ButtonBuilder>[];
87 } = {
88 embeds: [
89 new EmojiEmbed()
90 .setEmoji("PUNISH.WARN.RED")
91 .setTitle("Warned")
92 .setDescription(
93 `You have been warned in ${interaction.guild.name}` +
PineaFan538d3752023-01-12 21:48:23 +000094 (reason ? ` for:\n${reason}` : ".\n*No reason was provided*") +
PineaFana00db1b2023-01-02 15:32:54 +000095 "\n\n" +
96 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +000097 ? `You can appeal this in the ticket created in <#${
98 confirmation.components!["appeal"]!.response
99 }>`
PineaFana00db1b2023-01-02 15:32:54 +0000100 : "")
101 )
102 .setStatus("Danger")
103 ],
104 components: []
105 };
106 if (config.moderation.warn.text && config.moderation.warn.link) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000107 messageData.embeds[0]!.setFooter(LinkWarningFooter);
108 messageData.components.push(
109 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
110 new ButtonBuilder()
PineaFana00db1b2023-01-02 15:32:54 +0000111 .setStyle(ButtonStyle.Link)
112 .setLabel(config.moderation.warn.text)
Skyler Greyda16adf2023-03-05 10:22:12 +0000113 .setURL(
114 config.moderation.warn.link.replaceAll(
115 "{id}",
pineafan6de4da52023-03-07 20:43:44 +0000116 member.id
Skyler Greyda16adf2023-03-05 10:22:12 +0000117 )
118 )
119 )
120 );
pineafan8b4b17f2022-02-27 20:42:52 +0000121 }
pineafan6de4da52023-03-07 20:43:44 +0000122 await member.send(messageData);
PineaFana00db1b2023-01-02 15:32:54 +0000123 dmSent = true;
Skyler Grey75ea9172022-08-06 10:22:23 +0100124 }
PineaFana00db1b2023-01-02 15:32:54 +0000125 } catch (e) {
126 dmSent = false;
127 }
128 const data = {
129 meta: {
130 type: "memberWarn",
131 displayName: "Member warned",
132 calculateType: "guildMemberPunish",
133 color: NucleusColors.yellow,
134 emoji: "PUNISH.WARN.YELLOW",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500135 timestamp: Date.now()
PineaFana00db1b2023-01-02 15:32:54 +0000136 },
137 list: {
138 user: entry(
pineafan6de4da52023-03-07 20:43:44 +0000139 member.user.id,
140 renderUser(member.user)
PineaFana00db1b2023-01-02 15:32:54 +0000141 ),
142 warnedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as Discord.User)),
TheCodedProf94ff6de2023-02-22 17:47:26 -0500143 reason: reason ? reason : "*No reason provided*"
144 },
145 separate: {
Skyler Greyda16adf2023-03-05 10:22:12 +0000146 end:
147 getEmojiByName("ICONS.NOTIFY." + (notify ? "ON" : "OFF")) +
148 ` The user was ${notify ? "" : "not "}notified`
PineaFana00db1b2023-01-02 15:32:54 +0000149 },
150 hidden: {
151 guild: interaction.guild.id
152 }
153 };
154 await client.database.history.create(
155 "warn",
156 interaction.guild.id,
pineafan6de4da52023-03-07 20:43:44 +0000157 member.user,
PineaFana00db1b2023-01-02 15:32:54 +0000158 interaction.user,
159 reason
160 );
161 log(data);
162 const failed = !dmSent && notify;
163 if (!failed) {
164 await interaction.editReply({
165 embeds: [
166 new EmojiEmbed()
167 .setEmoji("PUNISH.WARN.GREEN")
168 .setTitle("Warn")
169 .setDescription(
170 "The user was warned" +
171 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000172 ? ` and an appeal ticket was opened in <#${
173 confirmation.components!["appeal"]!.response
174 }>`
PineaFana00db1b2023-01-02 15:32:54 +0000175 : "")
176 )
177 .setStatus("Success")
178 ],
179 components: []
180 });
181 } else {
pineafan6de4da52023-03-07 20:43:44 +0000182 const canSeeChannel = member
PineaFana00db1b2023-01-02 15:32:54 +0000183 .permissionsIn(interaction.channel as Discord.TextChannel)
184 .has("ViewChannel");
185 const m = (await interaction.editReply({
186 embeds: [
187 new EmojiEmbed()
188 .setEmoji("PUNISH.WARN.RED")
189 .setTitle("Warn")
190 .setDescription("The user's DMs are not open\n\nWhat would you like to do?")
191 .setStatus("Danger")
192 ],
193 components: [
194 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
Skyler Greyda16adf2023-03-05 10:22:12 +0000195 new Discord.ButtonBuilder()
196 .setCustomId("log")
197 .setLabel("Ignore and log")
198 .setStyle(ButtonStyle.Secondary),
PineaFana00db1b2023-01-02 15:32:54 +0000199 new Discord.ButtonBuilder()
200 .setCustomId("here")
201 .setLabel("Warn here")
202 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
203 .setDisabled(!canSeeChannel),
204 new Discord.ButtonBuilder()
205 .setCustomId("ticket")
206 .setLabel("Create ticket")
207 .setStyle(canSeeChannel ? ButtonStyle.Primary : ButtonStyle.Secondary)
208 .setDisabled(createAppealTicket)
209 )
210 ]
211 })) as Discord.Message;
212 let component;
213 try {
214 component = await m.awaitMessageComponent({
Skyler Greyda16adf2023-03-05 10:22:12 +0000215 filter: (i) =>
216 i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.id === m.id,
PineaFana00db1b2023-01-02 15:32:54 +0000217 time: 300000
218 });
219 } catch (e) {
220 return await interaction.editReply({
221 embeds: [
222 new EmojiEmbed()
223 .setEmoji("PUNISH.WARN.GREEN")
224 .setTitle("Warn")
225 .setDescription("No changes were made")
226 .setStatus("Success")
227 ],
228 components: []
229 });
230 }
231 if (component.customId === "here") {
232 await interaction.channel!.send({
233 embeds: [
234 new EmojiEmbed()
235 .setEmoji("PUNISH.WARN.RED")
236 .setTitle("Warn")
237 .setDescription("You have been warned" + (reason ? ` for:\n> ${reason}` : "."))
238 .setStatus("Danger")
239 ],
pineafan6de4da52023-03-07 20:43:44 +0000240 content: `<@!${member.id}>`,
PineaFana00db1b2023-01-02 15:32:54 +0000241 allowedMentions: {
pineafan6de4da52023-03-07 20:43:44 +0000242 users: [member.id]
PineaFana00db1b2023-01-02 15:32:54 +0000243 }
244 });
245 return await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100246 embeds: [
247 new EmojiEmbed()
248 .setEmoji("PUNISH.WARN.GREEN")
249 .setTitle("Warn")
250 .setDescription(
251 "The user was warned" +
PineaFan100df682023-01-02 13:26:08 +0000252 (createAppealTicket
Skyler Greyda16adf2023-03-05 10:22:12 +0000253 ? ` and an appeal ticket was opened in <#${
254 confirmation.components!["appeal"]!.response
255 }>`
Skyler Grey75ea9172022-08-06 10:22:23 +0100256 : "")
257 )
258 .setStatus("Success")
259 ],
260 components: []
261 });
PineaFana00db1b2023-01-02 15:32:54 +0000262 } else if (component.customId === "log") {
263 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100264 embeds: [
265 new EmojiEmbed()
PineaFana00db1b2023-01-02 15:32:54 +0000266 .setEmoji("PUNISH.WARN.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +0100267 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000268 .setDescription("The warn was logged")
269 .setStatus("Success")
Skyler Grey75ea9172022-08-06 10:22:23 +0100270 ],
PineaFana00db1b2023-01-02 15:32:54 +0000271 components: []
272 });
273 } else if (component.customId === "ticket") {
274 const ticketChannel = await create(
275 interaction.guild,
pineafan6de4da52023-03-07 20:43:44 +0000276 member.user,
PineaFana00db1b2023-01-02 15:32:54 +0000277 interaction.user,
278 reason,
279 "Warn Notification"
280 );
281 if (ticketChannel === null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100282 return await interaction.editReply({
283 embeds: [
284 new EmojiEmbed()
Skyler Grey75ea9172022-08-06 10:22:23 +0100285 .setEmoji("PUNISH.WARN.RED")
286 .setTitle("Warn")
PineaFana00db1b2023-01-02 15:32:54 +0000287 .setDescription("A ticket could not be created")
Skyler Grey75ea9172022-08-06 10:22:23 +0100288 .setStatus("Danger")
289 ],
Skyler Grey75ea9172022-08-06 10:22:23 +0100290 components: []
291 });
292 }
PineaFana00db1b2023-01-02 15:32:54 +0000293 await interaction.editReply({
294 embeds: [
295 new EmojiEmbed()
296 .setEmoji("PUNISH.WARN.GREEN")
297 .setTitle("Warn")
298 .setDescription(`A ticket was created in <#${ticketChannel}>`)
299 .setStatus("Success")
300 ],
301 components: []
302 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100303 }
pineafan8b4b17f2022-02-27 20:42:52 +0000304 }
pineafan63fc5e22022-08-04 22:04:10 +0100305};
pineafan4f164f32022-02-26 22:07:12 +0000306
pineafan6de4da52023-03-07 20:43:44 +0000307const check = (interaction: CommandInteraction | ButtonInteraction, partial: boolean = false, target?: GuildMember) => {
PineaFana00db1b2023-01-02 15:32:54 +0000308 if (!interaction.guild) return;
Skyler Grey75ea9172022-08-06 10:22:23 +0100309 const member = interaction.member as GuildMember;
Skyler Greyda16adf2023-03-05 10:22:12 +0000310 if (!member.permissions.has("ModerateMembers")) return "You do not have the *Moderate Members* permission";
311 if (partial) return true;
pineafan6de4da52023-03-07 20:43:44 +0000312 let apply: GuildMember;
313 if (interaction.isButton()) {
314 apply = target!;
315 } else {
316 apply = interaction.options.getMember("user") as GuildMember;
317 }
Skyler Greyad002172022-08-16 18:48:26 +0100318 const memberPos = member.roles.cache.size ? member.roles.highest.position : 0;
319 const applyPos = apply.roles.cache.size ? apply.roles.highest.position : 0;
pineafan8b4b17f2022-02-27 20:42:52 +0000320 // Do not allow warning bots
PineaFan0d06edc2023-01-17 22:10:31 +0000321 if (member.user.bot) return "I cannot warn bots";
pineafan8b4b17f2022-02-27 20:42:52 +0000322 // Allow the owner to warn anyone
PineaFana00db1b2023-01-02 15:32:54 +0000323 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000324 // Check if the user has moderate_members permission
pineafan8b4b17f2022-02-27 20:42:52 +0000325 // Check if the user is below on the role list
TheCodedProf0941da42023-02-18 20:28:04 -0500326 if (!(memberPos > applyPos)) return `You do not have a role higher than <@${apply.id}>`;
pineafan8b4b17f2022-02-27 20:42:52 +0000327 // Allow warn
pineafan63fc5e22022-08-04 22:04:10 +0100328 return true;
329};
pineafan4f164f32022-02-26 22:07:12 +0000330
Skyler Grey75ea9172022-08-06 10:22:23 +0100331export { command, callback, check };