152 lines
6.1 KiB
JavaScript
152 lines
6.1 KiB
JavaScript
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
|
|
const { addWarning } = require('../../services/warnings');
|
|
const { getGuildSettings } = require('../../services/guildSettings');
|
|
const { recordModerationAction } = require('../../services/moderationRecords');
|
|
const { connectToDatabase } = require('../../db');
|
|
|
|
const MAX_REASON_LENGTH = 500;
|
|
|
|
module.exports = {
|
|
permissionGroup: 'moderation',
|
|
data: new SlashCommandBuilder()
|
|
.setName('warn')
|
|
.setDescription('Warn a member and track strikes.')
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers)
|
|
.addUserOption(option =>
|
|
option
|
|
.setName('target')
|
|
.setDescription('The member to warn')
|
|
.setRequired(true))
|
|
.addStringOption(option =>
|
|
option
|
|
.setName('reason')
|
|
.setDescription('The reason for the warning')
|
|
.setRequired(true)),
|
|
category: 'Moderation',
|
|
async execute(interaction) {
|
|
const target = interaction.options.getUser('target');
|
|
const reason = interaction.options.getString('reason');
|
|
|
|
if (!interaction.guildId) {
|
|
return interaction.reply({
|
|
content: 'Warnings can only be issued inside a server.',
|
|
ephemeral: true,
|
|
});
|
|
}
|
|
if (reason.length > MAX_REASON_LENGTH) {
|
|
return interaction.reply({
|
|
content: `Reason must be ${MAX_REASON_LENGTH} characters or fewer.`,
|
|
ephemeral: true,
|
|
});
|
|
}
|
|
|
|
const { warnings, strikeCount } = await addWarning({
|
|
guildId: interaction.guildId,
|
|
userId: target.id,
|
|
moderatorId: interaction.user.id,
|
|
reason,
|
|
});
|
|
|
|
const settings = await getGuildSettings(interaction.guildId);
|
|
const thresholds = settings.moderation?.strikeThresholds ?? {};
|
|
const muteThreshold = Number.isInteger(thresholds.mute) ? thresholds.mute : null;
|
|
const kickThreshold = Number.isInteger(thresholds.kick) ? thresholds.kick : null;
|
|
const banThreshold = Number.isInteger(thresholds.ban) ? thresholds.ban : null;
|
|
const muteDurationMinutes = settings.moderation?.muteDurationMinutes ?? 0;
|
|
const banDurationMs = settings.moderation?.banDurationMs ?? 0;
|
|
|
|
let actionTaken = null;
|
|
|
|
if (banThreshold && strikeCount === banThreshold) {
|
|
actionTaken = 'ban';
|
|
} else if (kickThreshold && strikeCount === kickThreshold) {
|
|
actionTaken = 'kick';
|
|
} else if (muteThreshold && strikeCount === muteThreshold) {
|
|
actionTaken = 'mute';
|
|
}
|
|
|
|
if (actionTaken === 'mute') {
|
|
try {
|
|
const member = await interaction.guild.members.fetch(target.id);
|
|
if (member.moderatable && muteDurationMinutes > 0) {
|
|
const durationMs = muteDurationMinutes * 60 * 1000;
|
|
await member.timeout(durationMs, 'Strike threshold reached');
|
|
await recordModerationAction({
|
|
guildId: interaction.guildId,
|
|
userId: target.id,
|
|
action: 'mute',
|
|
moderatorId: interaction.user.id,
|
|
reason: 'Strike threshold reached',
|
|
metadata: { strikeCount, durationMinutes: muteDurationMinutes },
|
|
stateUpdates: { mutedUntil: new Date(Date.now() + durationMs) },
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to auto-mute member:', error);
|
|
}
|
|
}
|
|
|
|
if (actionTaken === 'kick') {
|
|
try {
|
|
const member = await interaction.guild.members.fetch(target.id);
|
|
if (member.kickable) {
|
|
await member.kick('Strike threshold reached');
|
|
await recordModerationAction({
|
|
guildId: interaction.guildId,
|
|
userId: target.id,
|
|
action: 'kick',
|
|
moderatorId: interaction.user.id,
|
|
reason: 'Strike threshold reached',
|
|
metadata: { strikeCount },
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to auto-kick member:', error);
|
|
}
|
|
}
|
|
|
|
if (actionTaken === 'ban') {
|
|
try {
|
|
const banReason = 'Strike threshold reached';
|
|
const shouldTempBan = Number.isInteger(banDurationMs) && banDurationMs > 0;
|
|
|
|
await interaction.guild.bans.create(target.id, {
|
|
reason: banReason,
|
|
});
|
|
|
|
if (shouldTempBan) {
|
|
const dbClient = await connectToDatabase();
|
|
const db = dbClient.db('discord');
|
|
const tempBanCollection = db.collection('tempban');
|
|
const banUntil = Date.now() + banDurationMs;
|
|
await tempBanCollection.insertOne({
|
|
guildId: interaction.guildId,
|
|
userId: target.id,
|
|
banTime: banUntil,
|
|
reason: banReason,
|
|
});
|
|
}
|
|
|
|
await recordModerationAction({
|
|
guildId: interaction.guildId,
|
|
userId: target.id,
|
|
action: 'ban',
|
|
moderatorId: interaction.user.id,
|
|
reason: banReason,
|
|
metadata: { strikeCount, banDurationMs },
|
|
stateUpdates: {
|
|
isBanned: true,
|
|
bannedUntil: shouldTempBan ? new Date(Date.now() + banDurationMs) : null,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to auto-ban member:', error);
|
|
}
|
|
}
|
|
|
|
return interaction.reply({
|
|
content: `${target.tag} has been warned. Total strikes: **${warnings.length}**.`,
|
|
});
|
|
},
|
|
};
|