Update README and moderation commands for localization and migration support
- Revised README to include database migration steps and a new section for database management. - Enhanced moderation command responses to utilize localized messages for unknown commands. - Refactored moderation commands to use centralized command data definitions, improving maintainability and consistency. - Added localization support for command descriptions and options in both German and English. - Versioned Prisma migrations for better tracking of database changes.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
ChannelType,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder,
|
||||
TextChannel,
|
||||
type ChatInputCommandInteraction,
|
||||
type GuildMember
|
||||
@@ -13,6 +12,21 @@ import { applyWarnEscalation, createCase } from './service.js';
|
||||
import { parseDuration } from './duration.js';
|
||||
import { getGuildLocale } from '../../i18n.js';
|
||||
import { promptDestructiveConfirmation } from './confirmations.js';
|
||||
import {
|
||||
banCommandData,
|
||||
caseCommandData,
|
||||
kickCommandData,
|
||||
lockCommandData,
|
||||
modNoteCommandData,
|
||||
nickCommandData,
|
||||
purgeCommandData,
|
||||
slowmodeCommandData,
|
||||
timeoutCommandData,
|
||||
unbanCommandData,
|
||||
unlockCommandData,
|
||||
untimeoutCommandData,
|
||||
warnCommandData
|
||||
} from './command-definitions.js';
|
||||
|
||||
function reasonFrom(i: ChatInputCommandInteraction, locale: 'de' | 'en'): string {
|
||||
return i.options.getString('reason') ?? t(locale, 'moderation.reason.none');
|
||||
@@ -38,12 +52,7 @@ async function ensureMod(
|
||||
}
|
||||
|
||||
const banCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ban')
|
||||
.setDescription('Ban a user')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Target user').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Reason'))
|
||||
.addStringOption((opt) => opt.setName('duration').setDescription('Optional temp-ban duration (e.g. 7d)')),
|
||||
data: banCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.BanMembers, locale))) return;
|
||||
@@ -62,11 +71,7 @@ const banCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const unbanCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('unban')
|
||||
.setDescription('Unban a user')
|
||||
.addStringOption((opt) => opt.setName('user_id').setDescription('Discord user id').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Reason')),
|
||||
data: unbanCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.BanMembers, locale))) return;
|
||||
@@ -86,11 +91,7 @@ const unbanCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const kickCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('kick')
|
||||
.setDescription('Kick a user')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Target user').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Reason')),
|
||||
data: kickCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.KickMembers, locale))) return;
|
||||
@@ -111,14 +112,7 @@ const kickCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const timeoutCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('timeout')
|
||||
.setDescription('Timeout a user')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Target user').setRequired(true))
|
||||
.addStringOption((opt) =>
|
||||
opt.setName('duration').setDescription('Duration (e.g. 15m)').setRequired(true)
|
||||
)
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Reason')),
|
||||
data: timeoutCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
|
||||
@@ -141,11 +135,7 @@ const timeoutCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const untimeoutCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('untimeout')
|
||||
.setDescription('Remove timeout from a user')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Target user').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('reason').setDescription('Reason')),
|
||||
data: untimeoutCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
|
||||
@@ -166,60 +156,7 @@ const untimeoutCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const warnCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('warn')
|
||||
.setDescription('Manage user warnings')
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('add')
|
||||
.setDescription('Add warning')
|
||||
.addUserOption((o) => o.setName('user').setDescription('Target').setRequired(true))
|
||||
.addStringOption((o) => o.setName('reason').setDescription('Reason').setRequired(true))
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('list')
|
||||
.setDescription('List warnings')
|
||||
.addUserOption((o) => o.setName('user').setDescription('Target').setRequired(true))
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('remove')
|
||||
.setDescription('Remove warning')
|
||||
.addStringOption((o) => o.setName('warning_id').setDescription('Warning ID').setRequired(true))
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('clear')
|
||||
.setDescription('Clear warnings for user')
|
||||
.addUserOption((o) => o.setName('user').setDescription('Target').setRequired(true))
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('escalation-set')
|
||||
.setDescription('Create or update escalation rule')
|
||||
.addIntegerOption((o) => o.setName('count').setDescription('Warning count threshold').setRequired(true).setMinValue(1).setMaxValue(50))
|
||||
.addStringOption((o) =>
|
||||
o
|
||||
.setName('action')
|
||||
.setDescription('Escalation action')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: 'timeout', value: 'TIMEOUT' },
|
||||
{ name: 'kick', value: 'KICK' },
|
||||
{ name: 'ban', value: 'BAN' }
|
||||
)
|
||||
)
|
||||
.addStringOption((o) => o.setName('duration').setDescription('Timeout duration (required for TIMEOUT)'))
|
||||
.addStringOption((o) => o.setName('reason').setDescription('Optional escalation reason'))
|
||||
)
|
||||
.addSubcommand((s) => s.setName('escalation-list').setDescription('List escalation rules'))
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('escalation-remove')
|
||||
.setDescription('Remove escalation rule')
|
||||
.addIntegerOption((o) => o.setName('count').setDescription('Warning count threshold').setRequired(true).setMinValue(1).setMaxValue(50))
|
||||
),
|
||||
data: warnCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
|
||||
@@ -308,6 +245,7 @@ const warnCommand: SlashCommand = {
|
||||
guildId: interaction.guildId!,
|
||||
targetUserId: user.id,
|
||||
moderatorId: interaction.user.id,
|
||||
...auditFrom(interaction),
|
||||
action: 'WARN_ADD',
|
||||
reason
|
||||
});
|
||||
@@ -367,6 +305,7 @@ const warnCommand: SlashCommand = {
|
||||
guildId: interaction.guildId!,
|
||||
targetUserId: warning.userId,
|
||||
moderatorId: interaction.user.id,
|
||||
...auditFrom(interaction),
|
||||
action: 'WARN_REMOVE',
|
||||
reason: `Warning ${warningId} removed`
|
||||
});
|
||||
@@ -389,21 +328,7 @@ const warnCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const purgeCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('purge')
|
||||
.setDescription('Bulk delete messages')
|
||||
.addIntegerOption((o) =>
|
||||
o.setName('amount').setDescription('2-100').setRequired(true).setMinValue(2).setMaxValue(100)
|
||||
)
|
||||
.addUserOption((o) => o.setName('user').setDescription('Only purge this user messages'))
|
||||
.addBooleanOption((o) => o.setName('bots').setDescription('Only purge bot messages'))
|
||||
.addBooleanOption((o) => o.setName('links').setDescription('Only purge messages containing links'))
|
||||
.addBooleanOption((o) =>
|
||||
o.setName('attachments').setDescription('Only purge messages with attachments')
|
||||
)
|
||||
.addStringOption((o) =>
|
||||
o.setName('regex').setDescription('Only purge messages matching regex')
|
||||
),
|
||||
data: purgeCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ManageMessages, locale))) return;
|
||||
@@ -443,10 +368,7 @@ const purgeCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const slowmodeCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('slowmode')
|
||||
.setDescription('Set channel slowmode')
|
||||
.addIntegerOption((o) => o.setName('seconds').setDescription('0-21600').setRequired(true).setMinValue(0).setMaxValue(21600)),
|
||||
data: slowmodeCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ManageChannels, locale))) return;
|
||||
@@ -467,7 +389,7 @@ const slowmodeCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const lockCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder().setName('lock').setDescription('Lock current text channel'),
|
||||
data: lockCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ManageChannels, locale))) return;
|
||||
@@ -489,7 +411,7 @@ const lockCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const unlockCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder().setName('unlock').setDescription('Unlock current text channel'),
|
||||
data: unlockCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ManageChannels, locale))) return;
|
||||
@@ -511,22 +433,7 @@ const unlockCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const nickCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('nick')
|
||||
.setDescription('Manage nicknames')
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('set')
|
||||
.setDescription('Set nickname')
|
||||
.addUserOption((o) => o.setName('user').setDescription('Target').setRequired(true))
|
||||
.addStringOption((o) => o.setName('nickname').setDescription('New nickname').setRequired(true))
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('reset')
|
||||
.setDescription('Reset nickname')
|
||||
.addUserOption((o) => o.setName('user').setDescription('Target').setRequired(true))
|
||||
),
|
||||
data: nickCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ManageNicknames, locale))) return;
|
||||
@@ -540,6 +447,7 @@ const nickCommand: SlashCommand = {
|
||||
guildId: interaction.guildId!,
|
||||
targetUserId: user.id,
|
||||
moderatorId: interaction.user.id,
|
||||
...auditFrom(interaction),
|
||||
action: 'NICK_SET',
|
||||
reason: nickname
|
||||
});
|
||||
@@ -549,6 +457,7 @@ const nickCommand: SlashCommand = {
|
||||
guildId: interaction.guildId!,
|
||||
targetUserId: user.id,
|
||||
moderatorId: interaction.user.id,
|
||||
...auditFrom(interaction),
|
||||
action: 'NICK_RESET',
|
||||
reason: 'Nickname reset'
|
||||
});
|
||||
@@ -558,22 +467,7 @@ const nickCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const caseCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('case')
|
||||
.setDescription('Manage moderation cases')
|
||||
.addSubcommand((s) =>
|
||||
s.setName('view').setDescription('View case').addIntegerOption((o) => o.setName('id').setDescription('Case number').setRequired(true))
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('edit')
|
||||
.setDescription('Edit case reason')
|
||||
.addIntegerOption((o) => o.setName('id').setDescription('Case number').setRequired(true))
|
||||
.addStringOption((o) => o.setName('reason').setDescription('New reason').setRequired(true))
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
s.setName('delete').setDescription('Soft-delete case').addIntegerOption((o) => o.setName('id').setDescription('Case number').setRequired(true))
|
||||
),
|
||||
data: caseCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
|
||||
@@ -606,22 +500,7 @@ const caseCommand: SlashCommand = {
|
||||
};
|
||||
|
||||
const modNoteCommand: SlashCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('modnote')
|
||||
.setDescription('Manage mod notes')
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('add')
|
||||
.setDescription('Add note')
|
||||
.addUserOption((o) => o.setName('user').setDescription('Target').setRequired(true))
|
||||
.addStringOption((o) => o.setName('note').setDescription('Note').setRequired(true))
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
s
|
||||
.setName('list')
|
||||
.setDescription('List notes')
|
||||
.addUserOption((o) => o.setName('user').setDescription('Target').setRequired(true))
|
||||
),
|
||||
data: modNoteCommandData,
|
||||
async execute(interaction, context) {
|
||||
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
||||
if (!(await ensureMod(interaction, PermissionFlagsBits.ModerateMembers, locale))) return;
|
||||
@@ -642,6 +521,7 @@ const modNoteCommand: SlashCommand = {
|
||||
guildId: interaction.guildId!,
|
||||
targetUserId: user.id,
|
||||
moderatorId: interaction.user.id,
|
||||
...auditFrom(interaction),
|
||||
action: 'MODNOTE_ADD',
|
||||
reason: note
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user