Files
keiko/src/commands/ban.ts
T
hikari 76e559876b
Node.js CI / CI (pull_request) Failing after 15s
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 54s
feat: rewrite as moderation bot (Keiko)
Replaces the old AI companion bot with a full Discord moderation system.

Commands: warn, mute, unmute, kick, softban, ban, unban, prune
Logging: member join/leave, activity (messages/threads/voice), mod actions
Audit log: captures manual bans, kicks, timeouts, and unbans
Sanctions: posts to Hikari sanction API for all applicable actions

All commands are ephemeral and use Components v2. Permission and role
hierarchy checks are enforced on every applicable command.
2026-03-24 20:15:41 -07:00

158 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable max-lines-per-function -- Command handlers have many validation and action steps */
/* eslint-disable max-statements -- Command handlers have many validation and action steps */
/* eslint-disable complexity -- Command handlers have multiple validation branches */
import {
InteractionContextType,
PermissionFlagsBits,
SlashCommandBuilder,
} from "discord.js";
import { logModerationAction } from "../modules/logModAction.js";
import { sendSanction } from "../modules/sendSanction.js";
import { errorReply, successReply } from "../utils/components.js";
import { logger } from "../utils/logger.js";
import type { Command } from "../interfaces/command.js";
const banCommand: Command = {
data: new SlashCommandBuilder().
setName("ban").
setDescription("Ban a member from the server.").
setDefaultMemberPermissions(PermissionFlagsBits.BanMembers).
setContexts([ InteractionContextType.Guild ]).
addUserOption((option) => {
return option.
setName("user").
setDescription("The member to ban.").
setRequired(true);
}).
addStringOption((option) => {
return option.
setName("reason").
setDescription("The reason for the ban.").
setRequired(true).
setMaxLength(512);
}).
addIntegerOption((option) => {
return option.
setName("days").
setDescription(
"Days of message history to delete (07). Defaults to 0.",
).
setRequired(false).
setMinValue(0).
setMaxValue(7);
}),
execute: async(interaction) => {
await interaction.deferReply({ ephemeral: true });
const hasPermission = interaction.memberPermissions?.has(
PermissionFlagsBits.BanMembers,
);
if (hasPermission !== true) {
await interaction.editReply(
errorReply(
"Insufficient Permissions",
"You do not have permission to use this command.",
),
);
return;
}
const target = interaction.options.getUser("user", true);
const reason = interaction.options.getString("reason", true);
const days = interaction.options.getInteger("days") ?? 0;
if (target.bot) {
await interaction.editReply(
errorReply("Invalid Target", "You cannot ban a bot."),
);
return;
}
if (target.id === interaction.user.id) {
await interaction.editReply(
errorReply("Invalid Target", "You cannot ban yourself."),
);
return;
}
const member = interaction.guild?.members.cache.get(target.id);
if (member) {
const selfMember = interaction.guild?.members.cache.get(
interaction.user.id,
);
if (
selfMember
&& member.roles.highest.position >= selfMember.roles.highest.position
) {
await interaction.editReply(
errorReply(
"Insufficient Permissions",
"You cannot ban someone with an equal or higher role.",
),
);
return;
}
try {
await target.send(
`You have been banned from **${interaction.guild?.name ?? "the server"}**.\n**Reason:** ${reason}`,
);
} catch {
// DMs may be closed; continue without failing the command.
}
}
try {
await interaction.guild?.bans.create(target.id, {
deleteMessageSeconds: days * 86_400,
reason: reason,
});
} catch {
await interaction.editReply(
errorReply(
"Action Failed",
"Failed to ban the member. Check my permissions and role hierarchy.",
),
);
return;
}
const sanctionNumber = await sendSanction({
reason: reason,
type: "ban",
username: target.username,
uuid: target.id,
});
await logModerationAction(interaction.client, {
action: "Member Banned",
emoji: "🔨",
moderatorTag: interaction.user.username,
reason: reason,
sanctionNumber: sanctionNumber,
source: "Command",
targetId: target.id,
targetTag: target.username,
});
await interaction.editReply(
successReply(
"Member Banned",
`**User**: ${target.username} (\`${target.id}\`)\n**Days Deleted**: ${days.toString()}\n**Reason**: ${reason}`,
),
);
},
};
void logger.log("debug", "Ban command loaded.");
export { banCommand };