AutoMod/bot/src/bot/commands/moderation/kick.ts
2023-09-09 15:24:32 +02:00

261 lines
9.2 KiB
TypeScript

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;