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>
57 lines
2.3 KiB
TypeScript
57 lines
2.3 KiB
TypeScript
import { ChannelType, ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
|
|
import { SlashCommand } from '../../utils/types';
|
|
import { context } from '../../config/context';
|
|
import { settingsStore } from '../../config/state';
|
|
|
|
const command: SlashCommand = {
|
|
guildOnly: true,
|
|
data: new SlashCommandBuilder()
|
|
.setName('gallery')
|
|
.setDescription('Screenshot-/Kunst-Galerie.')
|
|
.addSubcommand((sub) =>
|
|
sub
|
|
.setName('submit')
|
|
.setDescription('Reicht ein Bild für die Galerie ein.')
|
|
.addAttachmentOption((opt) => opt.setName('image').setDescription('Bild').setRequired(true))
|
|
.addStringOption((opt) => opt.setName('caption').setDescription('Beschreibung'))
|
|
)
|
|
.addSubcommand((sub) =>
|
|
sub
|
|
.setName('config')
|
|
.setDescription('Konfiguriert Galerie-Kanal und Künstler-Rolle.')
|
|
.addChannelOption((opt) => opt.setName('channel').setDescription('Galerie-Kanal').addChannelTypes(ChannelType.GuildText))
|
|
.addRoleOption((opt) => opt.setName('artist_role').setDescription('Rolle für Künstler'))
|
|
),
|
|
async execute(interaction: ChatInputCommandInteraction) {
|
|
if (!interaction.guildId) return;
|
|
const sub = interaction.options.getSubcommand();
|
|
|
|
if (sub === 'submit') {
|
|
const attachment = interaction.options.getAttachment('image', true);
|
|
const caption = interaction.options.getString('caption') ?? undefined;
|
|
await context.gallery.submit(interaction, attachment, caption);
|
|
return;
|
|
}
|
|
|
|
if (sub === 'config') {
|
|
if (!interaction.memberPermissions?.has(PermissionFlagsBits.ManageGuild)) {
|
|
await interaction.reply({ content: 'Du benötigst die Berechtigung "Server verwalten".', ephemeral: true });
|
|
return;
|
|
}
|
|
const channel = interaction.options.getChannel('channel');
|
|
const role = interaction.options.getRole('artist_role');
|
|
const current = settingsStore.get(interaction.guildId)?.galleryConfig || {};
|
|
await settingsStore.set(interaction.guildId, {
|
|
galleryConfig: {
|
|
channelId: channel?.id ?? current.channelId,
|
|
artistRoleId: role?.id ?? current.artistRoleId,
|
|
lastWinnerAt: current.lastWinnerAt
|
|
}
|
|
});
|
|
await interaction.reply({ content: 'Galerie-Konfiguration aktualisiert.', ephemeral: true });
|
|
}
|
|
}
|
|
};
|
|
|
|
export default command;
|