blob: 59c2f81ff1d066584fc07ec9e9d68e2f44e3135a [file] [log] [blame]
Skyler Grey75ea9172022-08-06 10:22:23 +01001import {
2 CommandInteraction,
3 GuildMember,
4 MessageActionRow,
5 MessageButton
6} from "discord.js";
pineafan63fc5e22022-08-04 22:04:10 +01007import humanizeDuration from "humanize-duration";
pineafan4f164f32022-02-26 22:07:12 +00008import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
pineafan8b4b17f2022-02-27 20:42:52 +00009import confirmationMessage from "../../utils/confirmationMessage.js";
pineafan4edb7762022-06-26 19:21:04 +010010import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +000011import keyValueList from "../../utils/generateKeyValueList.js";
pineafan6702cef2022-06-13 17:52:37 +010012import client from "../../utils/client.js";
pineafan4f164f32022-02-26 22:07:12 +000013
14const command = (builder: SlashCommandSubcommandBuilder) =>
15 builder
pineafan63fc5e22022-08-04 22:04:10 +010016 .setName("kick")
17 .setDescription("Kicks a user from the server")
Skyler Grey75ea9172022-08-06 10:22:23 +010018 .addUserOption((option) =>
19 option
20 .setName("user")
21 .setDescription("The user to kick")
22 .setRequired(true)
23 );
pineafan4f164f32022-02-26 22:07:12 +000024
Skyler Grey75ea9172022-08-06 10:22:23 +010025const callback = async (
26 interaction: CommandInteraction
27): Promise<void | unknown> => {
pineafan63fc5e22022-08-04 22:04:10 +010028 const { renderUser } = client.logger;
pineafan8b4b17f2022-02-27 20:42:52 +000029 // TODO:[Modals] Replace this with a modal
pineafan73a7c4a2022-07-24 10:38:04 +010030 let reason = null;
pineafan02ba0232022-07-24 22:16:15 +010031 let notify = true;
pineafan63fc5e22022-08-04 22:04:10 +010032 let confirmation;
pineafan73a7c4a2022-07-24 10:38:04 +010033 while (true) {
34 confirmation = await new confirmationMessage(interaction)
35 .setEmoji("PUNISH.KICK.RED")
36 .setTitle("Kick")
Skyler Grey75ea9172022-08-06 10:22:23 +010037 .setDescription(
38 keyValueList({
39 user: renderUser(interaction.options.getUser("user")),
40 reason: reason
41 ? "\n> " + (reason ?? "").replaceAll("\n", "\n> ")
42 : "*No reason provided*"
43 }) +
44 `The user **will${notify ? "" : " not"}** be notified\n\n` +
45 `Are you sure you want to kick <@!${
46 (interaction.options.getMember("user") as GuildMember)
47 .id
48 }>?`
49 )
pineafan73a7c4a2022-07-24 10:38:04 +010050 .setColor("Danger")
51 .addReasonButton(reason ?? "")
pineafan63fc5e22022-08-04 22:04:10 +010052 .send(reason !== null);
53 reason = reason ?? "";
54 if (confirmation.cancelled) return;
55 if (confirmation.success) break;
56 if (confirmation.newReason) reason = confirmation.newReason;
pineafan02ba0232022-07-24 22:16:15 +010057 if (confirmation.components) {
pineafan63fc5e22022-08-04 22:04:10 +010058 notify = confirmation.components.notify.active;
pineafan02ba0232022-07-24 22:16:15 +010059 }
pineafan73a7c4a2022-07-24 10:38:04 +010060 }
pineafan377794f2022-04-18 19:01:01 +010061 if (confirmation.success) {
pineafan63fc5e22022-08-04 22:04:10 +010062 let dmd = false;
pineafan5d1908e2022-02-28 21:34:47 +000063 let dm;
pineafan63fc5e22022-08-04 22:04:10 +010064 const config = await client.database.guilds.read(interaction.guild.id);
pineafan8b4b17f2022-02-27 20:42:52 +000065 try {
pineafan02ba0232022-07-24 22:16:15 +010066 if (notify) {
Skyler Grey75ea9172022-08-06 10:22:23 +010067 dm = await (
68 interaction.options.getMember("user") as GuildMember
69 ).send({
70 embeds: [
71 new EmojiEmbed()
72 .setEmoji("PUNISH.KICK.RED")
73 .setTitle("Kicked")
74 .setDescription(
75 `You have been kicked in ${interaction.guild.name}` +
76 (reason ? ` for:\n> ${reason}` : ".")
77 )
78 .setStatus("Danger")
pineafan377794f2022-04-18 19:01:01 +010079 ],
Skyler Grey75ea9172022-08-06 10:22:23 +010080 components: [
81 new MessageActionRow().addComponents(
82 config.moderation.kick.text
83 ? [
84 new MessageButton()
85 .setStyle("LINK")
86 .setLabel(config.moderation.kick.text)
87 .setURL(config.moderation.kick.link)
88 ]
89 : []
90 )
91 ]
pineafan63fc5e22022-08-04 22:04:10 +010092 });
93 dmd = true;
pineafan8b4b17f2022-02-27 20:42:52 +000094 }
Skyler Grey75ea9172022-08-06 10:22:23 +010095 } catch {
96 dmd = false;
97 }
pineafan8b4b17f2022-02-27 20:42:52 +000098 try {
Skyler Grey75ea9172022-08-06 10:22:23 +010099 (interaction.options.getMember("user") as GuildMember).kick(
100 reason ?? "No reason provided."
101 );
102 const member = interaction.options.getMember("user") as GuildMember;
103 await client.database.history.create(
104 "kick",
105 interaction.guild.id,
106 member.user,
107 interaction.user,
108 reason
109 );
110 const { log, NucleusColors, entry, renderUser, renderDelta } =
111 client.logger;
pineafan63fc5e22022-08-04 22:04:10 +0100112 const data = {
pineafane625d782022-05-09 18:04:32 +0100113 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100114 type: "memberKick",
115 displayName: "Member Kicked",
116 calculateType: "guildMemberPunish",
pineafane625d782022-05-09 18:04:32 +0100117 color: NucleusColors.red,
118 emoji: "PUNISH.KICK.RED",
119 timestamp: new Date().getTime()
120 },
121 list: {
pineafanda6e5342022-07-03 10:03:16 +0100122 memberId: entry(member.id, `\`${member.id}\``),
pineafane625d782022-05-09 18:04:32 +0100123 name: entry(member.id, renderUser(member.user)),
Skyler Grey75ea9172022-08-06 10:22:23 +0100124 joined: entry(
125 member.joinedAt,
126 renderDelta(member.joinedAt)
127 ),
128 kicked: entry(
129 new Date().getTime(),
130 renderDelta(new Date().getTime())
131 ),
132 kickedBy: entry(
133 interaction.user.id,
134 renderUser(interaction.user)
135 ),
136 reason: entry(
137 reason,
138 reason ? `\n> ${reason}` : "*No reason provided.*"
139 ),
140 timeInServer: entry(
141 new Date().getTime() - member.joinedTimestamp,
142 humanizeDuration(
143 new Date().getTime() - member.joinedTimestamp,
144 {
145 round: true
146 }
147 )
148 ),
149 accountCreated: entry(
150 member.user.createdAt,
151 renderDelta(member.user.createdAt)
152 ),
pineafan63fc5e22022-08-04 22:04:10 +0100153 serverMemberCount: member.guild.memberCount
pineafane625d782022-05-09 18:04:32 +0100154 },
155 hidden: {
156 guild: member.guild.id
157 }
pineafan63fc5e22022-08-04 22:04:10 +0100158 };
pineafan4edb7762022-06-26 19:21:04 +0100159 log(data);
pineafan8b4b17f2022-02-27 20:42:52 +0000160 } catch {
Skyler Grey75ea9172022-08-06 10:22:23 +0100161 await interaction.editReply({
162 embeds: [
163 new EmojiEmbed()
164 .setEmoji("PUNISH.KICK.RED")
165 .setTitle("Kick")
166 .setDescription(
167 "Something went wrong and the user was not kicked"
168 )
169 .setStatus("Danger")
170 ],
171 components: []
172 });
pineafan63fc5e22022-08-04 22:04:10 +0100173 if (dmd) await dm.delete();
174 return;
pineafan8b4b17f2022-02-27 20:42:52 +0000175 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100176 const failed = !dmd && notify;
177 await interaction.editReply({
178 embeds: [
179 new EmojiEmbed()
180 .setEmoji(`PUNISH.KICK.${failed ? "YELLOW" : "GREEN"}`)
181 .setTitle("Kick")
182 .setDescription(
183 "The member was kicked" +
184 (failed ? ", but could not be notified" : "")
185 )
186 .setStatus(failed ? "Warning" : "Success")
187 ],
188 components: []
189 });
pineafan8b4b17f2022-02-27 20:42:52 +0000190 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100191 await interaction.editReply({
192 embeds: [
193 new EmojiEmbed()
194 .setEmoji("PUNISH.KICK.GREEN")
195 .setTitle("Kick")
196 .setDescription("No changes were made")
197 .setStatus("Success")
198 ],
199 components: []
200 });
pineafan8b4b17f2022-02-27 20:42:52 +0000201 }
pineafan63fc5e22022-08-04 22:04:10 +0100202};
pineafan4f164f32022-02-26 22:07:12 +0000203
pineafanbd02b4a2022-08-05 22:01:38 +0100204const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100205 const member = interaction.member as GuildMember;
206 const me = interaction.guild.me!;
207 const apply = interaction.options.getMember("user") as GuildMember;
208 if (member === null || me === null || apply === null)
209 throw "That member is not in the server";
pineafan63fc5e22022-08-04 22:04:10 +0100210 const memberPos = member.roles ? member.roles.highest.position : 0;
211 const mePos = me.roles ? me.roles.highest.position : 0;
212 const applyPos = apply.roles ? apply.roles.highest.position : 0;
pineafanc1c18792022-08-03 21:41:36 +0100213 // Do not allow kicking the owner
Skyler Grey75ea9172022-08-06 10:22:23 +0100214 if (member.id === interaction.guild.ownerId)
215 throw "You cannot kick the owner of the server";
pineafan8b4b17f2022-02-27 20:42:52 +0000216 // Check if Nucleus can kick the member
Skyler Grey75ea9172022-08-06 10:22:23 +0100217 if (!(mePos > applyPos))
218 throw "I do not have a role higher than that member";
pineafan8b4b17f2022-02-27 20:42:52 +0000219 // Check if Nucleus has permission to kick
Skyler Grey75ea9172022-08-06 10:22:23 +0100220 if (!me.permissions.has("KICK_MEMBERS"))
221 throw "I do not have the *Kick Members* permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000222 // Do not allow kicking Nucleus
pineafan63fc5e22022-08-04 22:04:10 +0100223 if (member.id === interaction.guild.me.id) throw "I cannot kick myself";
pineafan8b4b17f2022-02-27 20:42:52 +0000224 // Allow the owner to kick anyone
pineafan63fc5e22022-08-04 22:04:10 +0100225 if (member.id === interaction.guild.ownerId) return true;
pineafan8b4b17f2022-02-27 20:42:52 +0000226 // Check if the user has kick_members permission
Skyler Grey75ea9172022-08-06 10:22:23 +0100227 if (!member.permissions.has("KICK_MEMBERS"))
228 throw "You do not have the *Kick Members* permission";
pineafan8b4b17f2022-02-27 20:42:52 +0000229 // Check if the user is below on the role list
Skyler Grey75ea9172022-08-06 10:22:23 +0100230 if (!(memberPos > applyPos))
231 throw "You do not have a role higher than that member";
pineafan8b4b17f2022-02-27 20:42:52 +0000232 // Allow kick
pineafan63fc5e22022-08-04 22:04:10 +0100233 return true;
234};
pineafan4f164f32022-02-26 22:07:12 +0000235
Skyler Grey75ea9172022-08-06 10:22:23 +0100236export { command, callback, check };