import { User } from "revolt.js"; import { SendableEmbed } from "revolt-api"; import { ulid } from "ulid"; import { client } from "../../../"; import Infraction from "automod/dist/types/antispam/Infraction"; import InfractionType from "automod/dist/types/antispam/InfractionType"; import CommandCategory from "../../../struct/commands/CommandCategory"; import SimpleCommand from "../../../struct/commands/SimpleCommand"; import logger from "../../logger"; import { fetchUsername, logModAction } from "../../modules/mod_logs"; import { dedupeArray, embed, EmbedColor, generateInfractionDMEmbed, getDmChannel, getMembers, isModerator, NO_MANAGER_MSG, parseUser, parseUserOrId, sanitizeMessageContent, storeInfraction, yesNoMessage, } from "../../util"; export default { name: "kick", aliases: ["yeet", "vent"], description: "Kick a member from the server", syntax: "/kick @username [reason?]", removeEmptyArgs: true, category: CommandCategory.Moderation, run: async (message, args, serverConfig) => { if (!(await isModerator(message))) return message.reply(NO_MANAGER_MSG); if (!message.serverContext.havePermission("KickMembers")) { return await message.reply( `Sorry, I do not have \`KickMembers\` permission.` ); } const userInput = !message.replyIds?.length ? args.shift() || "" : undefined; if (!userInput && !message.replyIds?.length) return message.reply({ embeds: [ embed( `Please specify one or more users by replying to their message while running this command or ` + `by specifying a comma-separated list of usernames.`, "No target user specified", EmbedColor.SoftError ), ], }); let reason = args .join(" ") ?.replace(new RegExp("`", "g"), "'") ?.replace(new RegExp("\n", "g"), " "); if (reason.length > 500) return message.reply({ embeds: [ embed( "Kick reason may not be longer than 500 characters.", null, EmbedColor.SoftError ), ], }); const embeds: SendableEmbed[] = []; const handledUsers: string[] = []; const targetUsers: User | { id: string }[] = []; const targetInput = dedupeArray( message.replyIds?.length ? ( await Promise.allSettled( message.replyIds.map((msg) => message.channel?.fetchMessage(msg) ) ) ) .filter((m) => m.status == "fulfilled") .map((m) => (m as any).value.authorId) : userInput!.split(",") ); for (const userStr of targetInput) { try { let user = await parseUserOrId(userStr); if (!user) { embeds.push( embed( `I can't resolve \`${sanitizeMessageContent( userStr ).trim()}\` to a user.`, null, EmbedColor.SoftError ) ); continue; } // Silently ignore duplicates if (handledUsers.includes(user.id)) continue; handledUsers.push(user.id); if (user.id == message.authorId) { embeds.push( embed( "You might want to avoid kicking yourself...", null, EmbedColor.Warning ) ); continue; } if (user.id == client.user!.id) { embeds.push( embed( "I won't allow you to get rid of me this easily :trol:", null, EmbedColor.Warning ) ); continue; } targetUsers.push(user); } catch (e) { console.error(e); embeds.push( embed( `Failed to kick target \`${sanitizeMessageContent( userStr ).trim()}\`: ${e}`, `Failed to kick: An error has occurred`, EmbedColor.Error ) ); } } if (message.replyIds?.length && targetUsers.length) { let res = await yesNoMessage( message.channel!, message.authorId!, `This will kick the author${targetUsers.length > 1 ? 's' : ''} ` + `of the message${message.replyIds.length > 1 ? 's' : ''} you replied to.\n` + `The following user${targetUsers.length > 1 ? 's' : ''} will be affected: ` + `${targetUsers.map(u => `<@${u.id}>`).join(', ')}.\n` + `Are you sure?`, 'Confirm action' ); if (!res) return; } const members = getMembers(message.serverContext.id); for (const user of targetUsers) { try { const member = members.find((m) => m.id.user == user.id) || await message.serverContext.fetchMember(user.id); let infId = ulid(); let infraction: Infraction = { _id: infId, createdBy: message.authorId!, date: Date.now(), reason: reason || "No reason provided", server: message.serverContext.id, type: InfractionType.Manual, user: user.id, actionType: "kick", }; if (serverConfig?.dmOnKick) { try { const embed = generateInfractionDMEmbed( message.serverContext, serverConfig, infraction, message ); const dmChannel = await getDmChannel(user); if ( dmChannel.havePermission("SendMessage") || dmChannel.havePermission("SendEmbeds") ) { await dmChannel.sendMessage({ embeds: [embed] }); } else logger.warn("Missing permission to DM user."); } catch (e) { console.error(e); } } let [{ userWarnCount }] = await Promise.all([ storeInfraction(infraction), logModAction( "kick", message.serverContext, message.member!, user.id, reason, infraction._id ), member.kick(), ]); embeds.push({ title: `User kicked`, icon_url: user instanceof User ? user.avatarURL : undefined, colour: EmbedColor.Success, description: `This is ${ userWarnCount == 1 ? "**the first infraction**" : `infraction number **${userWarnCount}**` }` + ` for ${await fetchUsername(user.id)}.\n` + `**User ID:** \`${user.id}\`\n` + `**Infraction ID:** \`${infraction._id}\`\n` + `**Reason:** \`${infraction.reason}\``, }); } catch (e) { embeds.push( embed( `Failed to kick user ${await fetchUsername( user.id )}: ${e}`, "Failed to kick user", EmbedColor.Error ) ); } } let firstMsg = true; while (embeds.length > 0) { const targetEmbeds = embeds.splice(0, 10); if (firstMsg) { await message.reply( { embeds: targetEmbeds, content: "Operation completed." }, false ); } else { await message.channel?.sendMessage({ embeds: targetEmbeds }); } firstMsg = false; } }, } as SimpleCommand;