deploy #2
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "WelcomeConfig" ADD COLUMN "leaveAnnounceBan" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -254,6 +254,8 @@ model WelcomeConfig {
|
|||||||
leaveContent String?
|
leaveContent String?
|
||||||
leaveEmbed Json?
|
leaveEmbed Json?
|
||||||
leaveComponents Json?
|
leaveComponents Json?
|
||||||
|
/// When true, leave channel also posts a notice if the member was banned.
|
||||||
|
leaveAnnounceBan Boolean @default(false)
|
||||||
welcomeDmEnabled Boolean @default(false)
|
welcomeDmEnabled Boolean @default(false)
|
||||||
welcomeDmContent String?
|
welcomeDmContent String?
|
||||||
userAutoroleIds String[] @default([])
|
userAutoroleIds String[] @default([])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ChannelType, PermissionFlagsBits, type Guild, type GuildMember, type Message, type TextChannel } from 'discord.js';
|
import { ChannelType, PermissionFlagsBits, type Guild, type GuildMember, type Message, type TextChannel } from 'discord.js';
|
||||||
import type { BotContext } from '../../types.js';
|
import type { BotContext } from '../../types.js';
|
||||||
import { createCase, createWarning } from '../moderation/service.js';
|
import { createCase, createWarning, notifyModerationTargetDm } from '../moderation/service.js';
|
||||||
import { getGuildLocale } from '../../i18n.js';
|
import { getGuildLocale } from '../../i18n.js';
|
||||||
import {
|
import {
|
||||||
getAutoModConfig,
|
getAutoModConfig,
|
||||||
@@ -85,6 +85,14 @@ async function applyAutoModAction(
|
|||||||
|
|
||||||
if (action === 'KICK') {
|
if (action === 'KICK') {
|
||||||
if (me.permissions.has(PermissionFlagsBits.KickMembers)) {
|
if (me.permissions.has(PermissionFlagsBits.KickMembers)) {
|
||||||
|
const locale = await getGuildLocale(context.prisma, guild.id);
|
||||||
|
await notifyModerationTargetDm({
|
||||||
|
user: message.author,
|
||||||
|
guildName: guild.name,
|
||||||
|
reason: baseReason,
|
||||||
|
locale,
|
||||||
|
action: 'kick'
|
||||||
|
});
|
||||||
await message.member.kick(baseReason);
|
await message.member.kick(baseReason);
|
||||||
await createCase(context, {
|
await createCase(context, {
|
||||||
guildId: guild.id,
|
guildId: guild.id,
|
||||||
@@ -102,6 +110,14 @@ async function applyAutoModAction(
|
|||||||
|
|
||||||
if (action === 'BAN') {
|
if (action === 'BAN') {
|
||||||
if (me.permissions.has(PermissionFlagsBits.BanMembers)) {
|
if (me.permissions.has(PermissionFlagsBits.BanMembers)) {
|
||||||
|
const locale = await getGuildLocale(context.prisma, guild.id);
|
||||||
|
await notifyModerationTargetDm({
|
||||||
|
user: message.author,
|
||||||
|
guildName: guild.name,
|
||||||
|
reason: baseReason,
|
||||||
|
locale,
|
||||||
|
action: 'ban'
|
||||||
|
});
|
||||||
await guild.members.ban(message.author.id, { reason: baseReason });
|
await guild.members.ban(message.author.id, { reason: baseReason });
|
||||||
await createCase(context, {
|
await createCase(context, {
|
||||||
guildId: guild.id,
|
guildId: guild.id,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { TextChannel, type ButtonInteraction } from 'discord.js';
|
|||||||
import { type Locale, t, tf } from '@nexumi/shared';
|
import { type Locale, t, tf } from '@nexumi/shared';
|
||||||
import type { BotContext } from '../../types.js';
|
import type { BotContext } from '../../types.js';
|
||||||
import { assertBannableForConfirm } from '../../hierarchy.js';
|
import { assertBannableForConfirm } from '../../hierarchy.js';
|
||||||
import { createCase, scheduleTempBanExpire } from './service.js';
|
import { createCase, notifyModerationTargetDm, scheduleTempBanExpire } from './service.js';
|
||||||
import { tryParseDuration } from './duration.js';
|
import { tryParseDuration } from './duration.js';
|
||||||
|
|
||||||
type BanPayload = {
|
type BanPayload = {
|
||||||
@@ -55,6 +55,18 @@ export async function executeBan(
|
|||||||
|
|
||||||
await interaction.deferUpdate();
|
await interaction.deferUpdate();
|
||||||
|
|
||||||
|
const targetUser = await context.client.users.fetch(payload.userId).catch(() => null);
|
||||||
|
if (targetUser) {
|
||||||
|
await notifyModerationTargetDm({
|
||||||
|
user: targetUser,
|
||||||
|
guildName: guild.name,
|
||||||
|
reason: payload.reason,
|
||||||
|
locale,
|
||||||
|
action: 'ban',
|
||||||
|
duration: payload.duration
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await guild.members.ban(payload.userId, { reason: payload.reason });
|
await guild.members.ban(payload.userId, { reason: payload.reason });
|
||||||
const modCase = await createCase(context, {
|
const modCase = await createCase(context, {
|
||||||
guildId: guild.id,
|
guildId: guild.id,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { t, tf } from '@nexumi/shared';
|
|||||||
import type { SlashCommand } from '../../types.js';
|
import type { SlashCommand } from '../../types.js';
|
||||||
import { requirePermission } from '../../permissions.js';
|
import { requirePermission } from '../../permissions.js';
|
||||||
import { assertModerableMember } from '../../hierarchy.js';
|
import { assertModerableMember } from '../../hierarchy.js';
|
||||||
import { applyWarnEscalation, createCase, createWarning } from './service.js';
|
import { applyWarnEscalation, createCase, createWarning, notifyModerationTargetDm } from './service.js';
|
||||||
import { parseDuration, parseTimeoutDuration, tryParseDuration } from './duration.js';
|
import { parseDuration, parseTimeoutDuration, tryParseDuration } from './duration.js';
|
||||||
import { getGuildLocale } from '../../i18n.js';
|
import { getGuildLocale } from '../../i18n.js';
|
||||||
import { promptDestructiveConfirmation } from './confirmations.js';
|
import { promptDestructiveConfirmation } from './confirmations.js';
|
||||||
@@ -133,6 +133,13 @@ const kickCommand: SlashCommand = {
|
|||||||
const reason = reasonFrom(interaction, locale);
|
const reason = reasonFrom(interaction, locale);
|
||||||
const hierarchy = await assertModerableMember(interaction, user.id, locale, 'kick');
|
const hierarchy = await assertModerableMember(interaction, user.id, locale, 'kick');
|
||||||
if (!hierarchy.ok || !hierarchy.member) return;
|
if (!hierarchy.ok || !hierarchy.member) return;
|
||||||
|
await notifyModerationTargetDm({
|
||||||
|
user,
|
||||||
|
guildName: interaction.guild!.name,
|
||||||
|
reason,
|
||||||
|
locale,
|
||||||
|
action: 'kick'
|
||||||
|
});
|
||||||
await hierarchy.member.kick(reason);
|
await hierarchy.member.kick(reason);
|
||||||
const modCase = await createCase(context, {
|
const modCase = await createCase(context, {
|
||||||
guildId: interaction.guildId!,
|
guildId: interaction.guildId!,
|
||||||
|
|||||||
@@ -64,6 +64,37 @@ async function notifyWarnedUser(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function notifyModerationTargetDm(params: {
|
||||||
|
user: User;
|
||||||
|
guildName: string;
|
||||||
|
reason: string;
|
||||||
|
locale: Locale;
|
||||||
|
action: 'ban' | 'kick';
|
||||||
|
duration?: string | null;
|
||||||
|
}): Promise<void> {
|
||||||
|
const reason = params.reason || t(params.locale, 'moderation.reason.none');
|
||||||
|
const key =
|
||||||
|
params.action === 'kick'
|
||||||
|
? 'moderation.kick.dm'
|
||||||
|
: params.duration
|
||||||
|
? 'moderation.ban.dm.temporary'
|
||||||
|
: 'moderation.ban.dm';
|
||||||
|
try {
|
||||||
|
await params.user.send(
|
||||||
|
tf(params.locale, key, {
|
||||||
|
guild: params.guildName,
|
||||||
|
reason,
|
||||||
|
...(params.duration ? { duration: params.duration } : {})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(
|
||||||
|
{ error, userId: params.user.id, action: params.action },
|
||||||
|
'Failed to DM moderation target'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function createWarning(
|
export async function createWarning(
|
||||||
context: BotContext,
|
context: BotContext,
|
||||||
input: CreateWarningInput
|
input: CreateWarningInput
|
||||||
@@ -298,6 +329,13 @@ export async function applyWarnEscalation(
|
|||||||
String(warningCount)
|
String(warningCount)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await notifyModerationTargetDm({
|
||||||
|
user: member.user,
|
||||||
|
guildName: interaction.guild.name,
|
||||||
|
reason,
|
||||||
|
locale,
|
||||||
|
action: 'kick'
|
||||||
|
});
|
||||||
await member.kick(reason);
|
await member.kick(reason);
|
||||||
await createCase(context, {
|
await createCase(context, {
|
||||||
guildId,
|
guildId,
|
||||||
@@ -318,6 +356,13 @@ export async function applyWarnEscalation(
|
|||||||
String(warningCount)
|
String(warningCount)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await notifyModerationTargetDm({
|
||||||
|
user: member.user,
|
||||||
|
guildName: interaction.guild.name,
|
||||||
|
reason,
|
||||||
|
locale,
|
||||||
|
action: 'ban'
|
||||||
|
});
|
||||||
await interaction.guild.members.ban(userId, { reason });
|
await interaction.guild.members.ban(userId, { reason });
|
||||||
await createCase(context, {
|
await createCase(context, {
|
||||||
guildId,
|
guildId,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { detectAltAccount } from './alt-detection.js';
|
|||||||
import { env } from '../../env.js';
|
import { env } from '../../env.js';
|
||||||
import { logger } from '../../logger.js';
|
import { logger } from '../../logger.js';
|
||||||
import { ephemeral } from '../../interaction-reply.js';
|
import { ephemeral } from '../../interaction-reply.js';
|
||||||
|
import { notifyModerationTargetDm } from '../moderation/service.js';
|
||||||
|
|
||||||
export class VerificationPanelError extends Error {
|
export class VerificationPanelError extends Error {
|
||||||
constructor(public readonly code: 'not_configured' | 'missing_permission') {
|
constructor(public readonly code: 'not_configured' | 'missing_permission') {
|
||||||
@@ -57,16 +58,35 @@ export type CompleteVerificationOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function applyFailAction(
|
async function applyFailAction(
|
||||||
|
context: BotContext,
|
||||||
member: GuildMember,
|
member: GuildMember,
|
||||||
failAction: string,
|
failAction: string,
|
||||||
reason: string
|
reason: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const me = member.guild.members.me;
|
const me = member.guild.members.me;
|
||||||
|
if (failAction !== 'KICK' && failAction !== 'BAN') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const locale = await getGuildLocale(context.prisma, member.guild.id);
|
||||||
if (failAction === 'KICK' && me?.permissions.has(PermissionFlagsBits.KickMembers)) {
|
if (failAction === 'KICK' && me?.permissions.has(PermissionFlagsBits.KickMembers)) {
|
||||||
|
await notifyModerationTargetDm({
|
||||||
|
user: member.user,
|
||||||
|
guildName: member.guild.name,
|
||||||
|
reason,
|
||||||
|
locale,
|
||||||
|
action: 'kick'
|
||||||
|
});
|
||||||
await member.kick(reason);
|
await member.kick(reason);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (failAction === 'BAN' && me?.permissions.has(PermissionFlagsBits.BanMembers)) {
|
if (failAction === 'BAN' && me?.permissions.has(PermissionFlagsBits.BanMembers)) {
|
||||||
|
await notifyModerationTargetDm({
|
||||||
|
user: member.user,
|
||||||
|
guildName: member.guild.name,
|
||||||
|
reason,
|
||||||
|
locale,
|
||||||
|
action: 'ban'
|
||||||
|
});
|
||||||
await member.guild.members.ban(member.id, { reason });
|
await member.guild.members.ban(member.id, { reason });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +138,7 @@ export async function completeVerification(
|
|||||||
detail: `Account age ${ageDays}d (required ${config.minAccountAgeDays}d)`,
|
detail: `Account age ${ageDays}d (required ${config.minAccountAgeDays}d)`,
|
||||||
failAction: config.failAction
|
failAction: config.failAction
|
||||||
});
|
});
|
||||||
await applyFailAction(member, config.failAction, failReason);
|
await applyFailAction(context, member, config.failAction, failReason);
|
||||||
return { ok: false, reason: 'account_too_young' };
|
return { ok: false, reason: 'account_too_young' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +221,7 @@ export async function completeVerification(
|
|||||||
failAction: config.failAction,
|
failAction: config.failAction,
|
||||||
matchedUserId
|
matchedUserId
|
||||||
});
|
});
|
||||||
await applyFailAction(member, config.failAction, failReason);
|
await applyFailAction(context, member, config.failAction, failReason);
|
||||||
return { ok: false, reason: 'alt_detected' };
|
return { ok: false, reason: 'alt_detected' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { PermissionFlagsBits, type GuildMember, type Role } from 'discord.js';
|
import { PermissionFlagsBits, type GuildMember, type Role } from 'discord.js';
|
||||||
|
import { t, tf } from '@nexumi/shared';
|
||||||
import type { BotContext } from '../../types.js';
|
import type { BotContext } from '../../types.js';
|
||||||
|
import { getGuildLocale } from '../../i18n.js';
|
||||||
import { getWelcomeConfig, resolveWelcomePayload, resolveLeavePayload, renderWelcomeText } from './service.js';
|
import { getWelcomeConfig, resolveWelcomePayload, resolveLeavePayload, renderWelcomeText } from './service.js';
|
||||||
import { sendLeaveMessage, sendWelcomeMessage } from './renderer.js';
|
import { sendLeaveMessage, sendWelcomeMessage } from './renderer.js';
|
||||||
import { logger } from '../../logger.js';
|
import { logger } from '../../logger.js';
|
||||||
@@ -106,6 +108,33 @@ export async function handleMemberLeave(context: BotContext, member: GuildMember
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await sendLeaveMessage(channel, resolveLeavePayload(config), member, member.guild);
|
await sendLeaveMessage(channel, resolveLeavePayload(config), member, member.guild);
|
||||||
|
|
||||||
|
if (!config.leaveAnnounceBan || member.user.bot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ban = await member.guild.bans.fetch(member.id).catch(() => null);
|
||||||
|
if (!ban) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const locale = await getGuildLocale(context.prisma, member.guild.id);
|
||||||
|
const reason = ban.reason ?? t(locale, 'moderation.reason.none');
|
||||||
|
if ('send' in channel) {
|
||||||
|
await channel
|
||||||
|
.send(
|
||||||
|
tf(locale, 'welcome.leave.bannedNotice', {
|
||||||
|
user: member.user.tag,
|
||||||
|
reason
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
logger.warn(
|
||||||
|
{ error, guildId: member.guild.id, userId: member.id },
|
||||||
|
'Failed to send leave ban notice'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerWelcomeEvents(context: BotContext): void {
|
export function registerWelcomeEvents(context: BotContext): void {
|
||||||
|
|||||||
@@ -252,6 +252,22 @@ export function WelcomeForm({ guildId, initialValue, previewVars }: WelcomeFormP
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FieldAnchor>
|
</FieldAnchor>
|
||||||
|
<FieldAnchor id="field-leave-announce-ban">
|
||||||
|
<div className="flex items-center justify-between gap-4 rounded-md border border-border p-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>{t('modulePages.welcome.leaveAnnounceBanLabel')}</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t('modulePages.welcome.leaveAnnounceBanHint')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={value.leaveAnnounceBan}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setValue((prev) => ({ ...prev, leaveAnnounceBan: checked }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FieldAnchor>
|
||||||
<FieldAnchor id="field-leave-channel">
|
<FieldAnchor id="field-leave-channel">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t('modulePages.welcome.leaveChannelId')}</Label>
|
<Label>{t('modulePages.welcome.leaveChannelId')}</Label>
|
||||||
|
|||||||
@@ -309,6 +309,14 @@ export const SETTINGS_SEARCH_FIELDS: SearchIndexEntry[] = [
|
|||||||
href: 'welcome',
|
href: 'welcome',
|
||||||
hash: 'field-leave-enabled'
|
hash: 'field-leave-enabled'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'setting-leave-announce-ban',
|
||||||
|
kind: 'setting',
|
||||||
|
labelKey: 'modulePages.welcome.leaveAnnounceBanLabel',
|
||||||
|
href: 'welcome',
|
||||||
|
hash: 'field-leave-announce-ban',
|
||||||
|
keywords: ['ban', 'leave', 'moderation']
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'setting-leave-channel',
|
id: 'setting-leave-channel',
|
||||||
kind: 'setting',
|
kind: 'setting',
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function toDashboard(config: Awaited<ReturnType<typeof ensureWelcomeConfig>>): W
|
|||||||
leaveContent: config.leaveContent,
|
leaveContent: config.leaveContent,
|
||||||
leaveEmbed: parseEmbed(config.leaveEmbed),
|
leaveEmbed: parseEmbed(config.leaveEmbed),
|
||||||
leaveComponents: parseComponents(config.leaveComponents),
|
leaveComponents: parseComponents(config.leaveComponents),
|
||||||
|
leaveAnnounceBan: config.leaveAnnounceBan,
|
||||||
welcomeDmEnabled: config.welcomeDmEnabled,
|
welcomeDmEnabled: config.welcomeDmEnabled,
|
||||||
welcomeDmContent: config.welcomeDmContent,
|
welcomeDmContent: config.welcomeDmContent,
|
||||||
userAutoroleIds: config.userAutoroleIds,
|
userAutoroleIds: config.userAutoroleIds,
|
||||||
|
|||||||
@@ -549,6 +549,9 @@
|
|||||||
"leaveTitle": "Abschied",
|
"leaveTitle": "Abschied",
|
||||||
"leaveDescription": "Nachricht und Kanal, wenn ein Mitglied den Server verlässt.",
|
"leaveDescription": "Nachricht und Kanal, wenn ein Mitglied den Server verlässt.",
|
||||||
"leaveEnabledLabel": "Abschiedsnachricht aktiviert",
|
"leaveEnabledLabel": "Abschiedsnachricht aktiviert",
|
||||||
|
"leaveAnnounceBanLabel": "Ban im Abschieds-Kanal melden",
|
||||||
|
"leaveAnnounceBanHint":
|
||||||
|
"Zusätzlich zur Abschiedsnachricht anzeigen, wenn das Mitglied gebannt wurde (inkl. Grund).",
|
||||||
"leaveChannelId": "Abschieds-Kanal-ID",
|
"leaveChannelId": "Abschieds-Kanal-ID",
|
||||||
"leaveType": "Nachrichtentyp",
|
"leaveType": "Nachrichtentyp",
|
||||||
"leaveTypes": {
|
"leaveTypes": {
|
||||||
|
|||||||
@@ -549,6 +549,9 @@
|
|||||||
"leaveTitle": "Leave",
|
"leaveTitle": "Leave",
|
||||||
"leaveDescription": "Message and channel when a member leaves the server.",
|
"leaveDescription": "Message and channel when a member leaves the server.",
|
||||||
"leaveEnabledLabel": "Leave message enabled",
|
"leaveEnabledLabel": "Leave message enabled",
|
||||||
|
"leaveAnnounceBanLabel": "Announce bans in leave channel",
|
||||||
|
"leaveAnnounceBanHint":
|
||||||
|
"Also post in the leave channel when the member was banned (including reason).",
|
||||||
"leaveChannelId": "Leave channel ID",
|
"leaveChannelId": "Leave channel ID",
|
||||||
"leaveType": "Message type",
|
"leaveType": "Message type",
|
||||||
"leaveTypes": {
|
"leaveTypes": {
|
||||||
|
|||||||
@@ -634,6 +634,24 @@
|
|||||||
- [ ] AutoMod-WARN → ebenfalls DM + Case verknüpft
|
- [ ] AutoMod-WARN → ebenfalls DM + Case verknüpft
|
||||||
- [ ] Dashboard: Warn-Tabelle zeigt `#`-Spalte
|
- [ ] Dashboard: Warn-Tabelle zeigt `#`-Spalte
|
||||||
|
|
||||||
|
## Post-Phase – Ban/Kick-DM + Leave-Ban-Hinweis (Status: implementiert)
|
||||||
|
|
||||||
|
### Abgeschlossen (Code)
|
||||||
|
|
||||||
|
- DM vor Ban/Kick (Slash, Confirm, AutoMod, Eskalation, Verifizierung) mit Server + Grund (+ Dauer bei Temp-Ban)
|
||||||
|
- WelcomeConfig `leaveAnnounceBan` (Default aus); WebUI-Toggle unter Abschied
|
||||||
|
- Bei Leave + Toggle: Ban-Check via `guild.bans.fetch` → Zusatzzeile im Leave-Kanal
|
||||||
|
- Migration `20260725190000_leave_announce_ban`
|
||||||
|
|
||||||
|
### Manuell testen
|
||||||
|
|
||||||
|
- [ ] `/kick` → Ziel erhält DM; Moderator sieht Case-Erfolg
|
||||||
|
- [ ] `/ban` (Confirm) → DM vor Ban; Temp-Ban zeigt Dauer in der DM
|
||||||
|
- [ ] Leave-Kanal: normales Leave ohne Ban → nur Abschiedsnachricht
|
||||||
|
- [ ] Leave-Kanal: Ban mit Toggle an → Abschied + „wurde gebannt“ inkl. Grund
|
||||||
|
- [ ] Leave-Kanal: Ban mit Toggle aus → nur Abschiedsnachricht
|
||||||
|
- [ ] User mit geschlossenen DMs → Ban/Kick läuft trotzdem
|
||||||
|
|
||||||
## Post-Phase – AutoMod Standard-Wortliste DE/EN (Status: implementiert)
|
## Post-Phase – AutoMod Standard-Wortliste DE/EN (Status: implementiert)
|
||||||
|
|
||||||
### Abgeschlossen (Code)
|
### Abgeschlossen (Code)
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ export const WelcomeConfigDashboardSchema = z.object({
|
|||||||
leaveContent: z.string().max(2000).nullable().optional(),
|
leaveContent: z.string().max(2000).nullable().optional(),
|
||||||
leaveEmbed: DashboardEmbedSchema.nullable().optional(),
|
leaveEmbed: DashboardEmbedSchema.nullable().optional(),
|
||||||
leaveComponents: MessageComponentsV2Schema.nullable().optional(),
|
leaveComponents: MessageComponentsV2Schema.nullable().optional(),
|
||||||
|
leaveAnnounceBan: z.boolean().default(false),
|
||||||
welcomeDmEnabled: z.boolean(),
|
welcomeDmEnabled: z.boolean(),
|
||||||
welcomeDmContent: z.string().max(2000).nullable().optional(),
|
welcomeDmContent: z.string().max(2000).nullable().optional(),
|
||||||
userAutoroleIds: z.array(z.string()),
|
userAutoroleIds: z.array(z.string()),
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ const de: Dictionary = {
|
|||||||
'moderation.warning.notFound': 'Verwarnung nicht gefunden.',
|
'moderation.warning.notFound': 'Verwarnung nicht gefunden.',
|
||||||
'moderation.warning.dm':
|
'moderation.warning.dm':
|
||||||
'Du hast eine Verwarnung auf **{guild}** erhalten.\nGrund: {reason}\nVerwarnungen: {count} (Nr. #{warningNumber})',
|
'Du hast eine Verwarnung auf **{guild}** erhalten.\nGrund: {reason}\nVerwarnungen: {count} (Nr. #{warningNumber})',
|
||||||
|
'moderation.kick.dm': 'Du wurdest von **{guild}** gekickt.\nGrund: {reason}',
|
||||||
|
'moderation.ban.dm': 'Du wurdest von **{guild}** gebannt.\nGrund: {reason}',
|
||||||
|
'moderation.ban.dm.temporary':
|
||||||
|
'Du wurdest von **{guild}** temporär gebannt ({duration}).\nGrund: {reason}',
|
||||||
|
'welcome.leave.bannedNotice': '**{user}** wurde gebannt.\nGrund: {reason}',
|
||||||
'moderation.purge.done': '{deletedCount} Nachrichten gelöscht (Fall #{caseNumber}).',
|
'moderation.purge.done': '{deletedCount} Nachrichten gelöscht (Fall #{caseNumber}).',
|
||||||
'moderation.note.none': 'Keine Notizen vorhanden.',
|
'moderation.note.none': 'Keine Notizen vorhanden.',
|
||||||
'moderation.case.notFound': 'Fall nicht gefunden.',
|
'moderation.case.notFound': 'Fall nicht gefunden.',
|
||||||
@@ -758,6 +763,11 @@ const en: Dictionary = {
|
|||||||
'moderation.warning.notFound': 'Warning not found.',
|
'moderation.warning.notFound': 'Warning not found.',
|
||||||
'moderation.warning.dm':
|
'moderation.warning.dm':
|
||||||
'You received a warning on **{guild}**.\nReason: {reason}\nWarnings: {count} (No. #{warningNumber})',
|
'You received a warning on **{guild}**.\nReason: {reason}\nWarnings: {count} (No. #{warningNumber})',
|
||||||
|
'moderation.kick.dm': 'You were kicked from **{guild}**.\nReason: {reason}',
|
||||||
|
'moderation.ban.dm': 'You were banned from **{guild}**.\nReason: {reason}',
|
||||||
|
'moderation.ban.dm.temporary':
|
||||||
|
'You were temporarily banned from **{guild}** ({duration}).\nReason: {reason}',
|
||||||
|
'welcome.leave.bannedNotice': '**{user}** was banned.\nReason: {reason}',
|
||||||
'moderation.purge.done': 'Deleted {deletedCount} messages (case #{caseNumber}).',
|
'moderation.purge.done': 'Deleted {deletedCount} messages (case #{caseNumber}).',
|
||||||
'moderation.note.none': 'No notes.',
|
'moderation.note.none': 'No notes.',
|
||||||
'moderation.case.notFound': 'Case not found.',
|
'moderation.case.notFound': 'Case not found.',
|
||||||
|
|||||||
Reference in New Issue
Block a user