add badges, weekly plans, polls, suggestions, mod-case system, branding, partner system, alt-account detection, gallery, growth tracking
Two feature batches plus a deploy fix: Moderation & engagement: persistent ModCase history (retrofits ban/kick/ mute/timeout/tempban to record cases), /warn, /watch watchlist with automatic alerts on deletions/tickets/automod hits, /case file lookup, achievement badges (/badges, /profile) with message/streak/ticket/ birthday/event/booster triggers, /weekplan RSVP boards, /poll with anonymous mode and scheduled auto-close, /suggest with vote/comment/ decide flow. Branding & growth: /branding (per-guild embed color/logo/footer/bot name/theme, applied across logging/ticket/register/embed-builder embeds and the dashboard sidebar/accent color at runtime), two new /setup server-type presets (Roleplay, Creator), /rules generate (template-based rule sets), /partner apply/list/config with invite validation and showcase posting, alt-account suspicion scoring on join (account age, avatar, name pattern, join bursts, ban-list similarity), /gallery submit with voting and weekly-winner scheduler, invite-attributed join/leave tracking with a new Wachstum dashboard page, and an expanded Admin dashboard (guild list, feature-usage counters). Also fixes production: the Docker container never ran `prisma migrate deploy`, so schema changes never reached the live database. The compose command now applies pending migrations on every start. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
76
src/commands/admin/case.ts
Normal file
76
src/commands/admin/case.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { EmbedBuilder, SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { SlashCommand } from '../../utils/types';
|
||||
import { context } from '../../config/context';
|
||||
|
||||
const TYPE_LABELS: Record<string, string> = {
|
||||
warn: 'Warnungen',
|
||||
mute: 'Mutes',
|
||||
timeout: 'Timeouts',
|
||||
kick: 'Kicks',
|
||||
ban: 'Bans',
|
||||
tempban: 'Tempbans',
|
||||
note: 'Notizen',
|
||||
watchlist_add: 'Watchlist (hinzugefügt)',
|
||||
watchlist_remove: 'Watchlist (entfernt)'
|
||||
};
|
||||
|
||||
const command: SlashCommand = {
|
||||
guildOnly: true,
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('case')
|
||||
.setDescription('Zeigt oder ergänzt die Mod-Akte eines Nutzers.')
|
||||
.addSubcommand((sub) =>
|
||||
sub
|
||||
.setName('view')
|
||||
.setDescription('Zeigt die Mod-Akte eines Nutzers.')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer').setRequired(true))
|
||||
)
|
||||
.addSubcommand((sub) =>
|
||||
sub
|
||||
.setName('note')
|
||||
.setDescription('Fügt eine interne Notiz zur Akte hinzu.')
|
||||
.addUserOption((opt) => opt.setName('user').setDescription('Nutzer').setRequired(true))
|
||||
.addStringOption((opt) => opt.setName('body').setDescription('Notiz').setRequired(true))
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (!interaction.guild) return;
|
||||
const sub = interaction.options.getSubcommand();
|
||||
const user = interaction.options.getUser('user', true);
|
||||
|
||||
if (sub === 'note') {
|
||||
const body = interaction.options.getString('body', true);
|
||||
await context.modCases.addNote(interaction.guild.id, user.id, interaction.user.id, interaction.user.tag, body);
|
||||
await interaction.reply({ content: `Notiz zur Akte von ${user.tag} hinzugefügt.`, ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await context.modCases.getCase(interaction.guild.id, user.id);
|
||||
const watched = context.watchlist.isWatched(interaction.guild.id, user.id);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`Mod-Akte: ${user.tag}`)
|
||||
.setColor(watched ? 0xeab308 : 0x7289da)
|
||||
.addFields(
|
||||
...Object.entries(TYPE_LABELS).map(([type, label]) => ({ name: label, value: String(data.counts[type] || 0), inline: true })),
|
||||
{ name: 'Tickets erstellt', value: String(data.ticketCount), inline: true },
|
||||
{ name: 'Automod-Verstöße', value: String(data.automodStrikes), inline: true },
|
||||
{ name: 'Beobachtungsliste', value: watched ? 'Ja' : 'Nein', inline: true }
|
||||
);
|
||||
|
||||
const recent = data.cases.slice(0, 10);
|
||||
if (recent.length) {
|
||||
embed.addFields({
|
||||
name: 'Letzte Einträge',
|
||||
value: recent
|
||||
.map((c) => `**${TYPE_LABELS[c.type] || c.type}** — ${c.reason || 'Kein Grund'} (${c.moderatorTag}, ${c.createdAt.toLocaleDateString('de-DE')})`)
|
||||
.join('\n')
|
||||
.slice(0, 1024)
|
||||
});
|
||||
}
|
||||
|
||||
await interaction.reply({ embeds: [embed], ephemeral: true });
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
||||
Reference in New Issue
Block a user