Moderation Update

This commit is contained in:
2025-10-09 06:13:48 -04:00
parent 2ae7202445
commit ff10bb3183
20 changed files with 2056 additions and 381 deletions

View File

@@ -0,0 +1,187 @@
const { SlashCommandBuilder, PermissionsBitField } = require('discord.js');
// Helper function to parse user from mention or ID
function parseUser(input, guild) {
// Check if it's a mention <@123456> or <@!123456>
const mentionMatch = input.match(/^<@!?(\d+)>$/);
if (mentionMatch) {
return guild.members.cache.get(mentionMatch[1])?.user;
}
// Check if it's a user ID
if (/^\d{15,20}$/.test(input)) {
return guild.members.cache.get(input)?.user;
}
// Try to find by username or global name
const member = guild.members.cache.find(m =>
(m.user.global_name && m.user.global_name.toLowerCase().includes(input.toLowerCase())) ||
m.user.username.toLowerCase().includes(input.toLowerCase()) ||
(m.user.global_name && m.user.global_name.toLowerCase() === input.toLowerCase()) ||
m.user.username.toLowerCase() === input.toLowerCase()
);
return member?.user;
}
// Helper function to log moderation actions
async function logModerationAction(guildId, action, targetUserId, targetUsername, moderatorUserId, moderatorUsername, reason, duration = null, endDate = null) {
try {
const logData = {
guildId,
action,
targetUserId,
targetUsername,
moderatorUserId,
moderatorUsername,
reason,
duration,
endDate
};
const response = await fetch(`${process.env.BACKEND_BASE || 'http://localhost:3001'}/internal/log-moderation`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(logData)
});
if (!response.ok) {
console.error('Failed to log moderation action:', response.statusText);
}
} catch (error) {
console.error('Error logging moderation action:', error);
}
}
module.exports = {
name: 'timeout',
description: 'Timeout a user in the server',
enabled: true,
builder: new SlashCommandBuilder()
.setName('timeout')
.setDescription('Timeout a user in the server')
.addStringOption(option =>
option.setName('user')
.setDescription('The user to timeout (mention or user ID)')
.setRequired(true))
.addIntegerOption(option =>
option.setName('duration')
.setDescription('Duration in minutes (max 40320 minutes = 28 days)')
.setRequired(true)
.setMinValue(1)
.setMaxValue(40320))
.addStringOption(option =>
option.setName('reason')
.setDescription('Reason for the timeout (minimum 3 words)')
.setRequired(true)),
async execute(interaction) {
// Check if user has moderate members permissions (required for timeout)
if (!interaction.member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) {
return await interaction.reply({
content: 'You do not have permission to timeout members.',
flags: 64
});
}
// Check if bot has moderate members permissions
if (!interaction.guild.members.me.permissions.has(PermissionsBitField.Flags.ModerateMembers)) {
return await interaction.reply({
content: 'I do not have permission to timeout members.',
flags: 64
});
}
const userInput = interaction.options.getString('user');
const duration = interaction.options.getInteger('duration');
const reason = interaction.options.getString('reason');
// Parse the user from the input
const user = parseUser(userInput, interaction.guild);
if (!user) {
return await interaction.reply({
content: 'Could not find that user. Please provide a valid user mention or user ID.',
flags: 64
});
}
// Validate reason has at least 3 words
const reasonWords = reason.trim().split(/\s+/);
if (reasonWords.length < 3) {
return await interaction.reply({
content: 'Reason must be at least 3 words long.',
flags: 64
});
}
// Cannot timeout yourself
if (user.id === interaction.user.id) {
return await interaction.reply({
content: 'You cannot timeout yourself.',
flags: 64
});
}
// Cannot timeout the bot
if (user.id === interaction.guild.members.me.id) {
return await interaction.reply({
content: 'I cannot timeout myself.',
flags: 64
});
}
// Check if user is in the server
const member = interaction.guild.members.cache.get(user.id);
if (!member) {
return await interaction.reply({
content: 'That user is not in this server.',
flags: 64
});
}
// Check role hierarchy
if (member.roles.highest.position >= interaction.member.roles.highest.position && interaction.user.id !== interaction.guild.ownerId) {
return await interaction.reply({
content: 'You cannot timeout a member with a higher or equal role.',
flags: 64
});
}
if (member.roles.highest.position >= interaction.guild.members.me.roles.highest.position) {
return await interaction.reply({
content: 'I cannot timeout a member with a higher or equal role.',
flags: 64
});
}
// Check if user is already timed out
if (member.communicationDisabledUntil) {
return await interaction.reply({
content: 'This user is already timed out.',
flags: 64
});
}
try {
const timeoutDuration = duration * 60 * 1000; // Convert minutes to milliseconds
const timeoutUntil = new Date(Date.now() + timeoutDuration);
await member.timeout(timeoutDuration, reason);
await interaction.reply({
content: `Successfully timed out ${user.global_name || user.username} for ${duration} minutes. Reason: ${reason}`,
flags: 64
});
// Log the action
const durationString = duration >= 1440 ? `${Math.floor(duration / 1440)}d ${Math.floor((duration % 1440) / 60)}h ${duration % 60}m` :
duration >= 60 ? `${Math.floor(duration / 60)}h ${duration % 60}m` : `${duration}m`;
await logModerationAction(interaction.guildId, 'timeout', user.id, user.global_name || user.username, interaction.user.id, interaction.user.global_name || interaction.user.username, reason, durationString, timeoutUntil);
} catch (error) {
console.error('Error timing out user:', error);
await interaction.reply({
content: 'Failed to timeout the user. Please try again.',
flags: 64
});
}
}
};