- Alle Embeds nutzen jetzt brandName/brandColor/brandColor2/brandFooter aus den Settings (Codemod über 20 Module) — Farbe & Name serverweit per Klick änderbar - Bot-Status (Presence): Typ (Spielt/Schaut/Hört/Status) + Text, sofort angewendet; Server-Monitor nutzt die Presence nur noch, wenn kein eigener Status gesetzt ist - Avatar-/Banner-Upload direkt aufs Bot-Profil (Base64, 8-MB-Limit, Discord-Rate-Limit sauber gemeldet) - Env-Verlagerung: GITEA_API_TOKEN (write-only Setting) und DISCORD_GUILD_ID jetzt auch über den Brand-Tab pflegbar — weniger Redeploys - Rollen-Menüs: Button-Farbe pro Eintrag wählbar (Grau/Blau/Grün/Rot) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
64 lines
2.8 KiB
JavaScript
64 lines
2.8 KiB
JavaScript
// /giveaway — Verlosung starten (Admin): 🎉-Button zum Teilnehmen, automatische Ziehung
|
|
import {
|
|
SlashCommandBuilder, PermissionFlagsBits, MessageFlags,
|
|
ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder,
|
|
} from 'discord.js';
|
|
import { saveGiveaway } from '../../db.js';
|
|
import { brandColor, brandFooter } from '../../runtime-settings.js';
|
|
|
|
/** "30m" / "2h" / "1d" → Millisekunden (null bei Unsinn) */
|
|
export function parseDuration(input) {
|
|
const m = String(input).trim().match(/^(\d+)\s*(m|h|d)$/i);
|
|
if (!m) return null;
|
|
const factor = { m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2].toLowerCase()];
|
|
const ms = Number(m[1]) * factor;
|
|
return ms >= 60_000 && ms <= 30 * 86_400_000 ? ms : null;
|
|
}
|
|
|
|
export const data = new SlashCommandBuilder()
|
|
.setName('giveaway')
|
|
.setDescription('Verlosung in diesem Kanal starten')
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
|
.addStringOption((o) => o.setName('preis').setDescription('Was gibt es zu gewinnen?').setRequired(true).setMaxLength(200))
|
|
.addStringOption((o) => o.setName('dauer').setDescription('z. B. 30m, 2h, 1d').setRequired(true))
|
|
.addIntegerOption((o) => o.setName('gewinner').setDescription('Anzahl Gewinner (Default 1)').setMinValue(1).setMaxValue(20));
|
|
|
|
export async function execute(interaction) {
|
|
const duration = parseDuration(interaction.options.getString('dauer'));
|
|
if (!duration) {
|
|
await interaction.reply({
|
|
content: '❌ Dauer bitte als `30m`, `2h` oder `1d` (1 Minute bis 30 Tage).',
|
|
flags: MessageFlags.Ephemeral,
|
|
});
|
|
return;
|
|
}
|
|
const prize = interaction.options.getString('preis');
|
|
const winners = interaction.options.getInteger('gewinner') ?? 1;
|
|
const endsAt = new Date(Date.now() + duration);
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(brandColor())
|
|
.setTitle(`🎉 Giveaway: ${prize}`)
|
|
.setDescription(
|
|
`Klick auf den Button zum Teilnehmen!\n\n` +
|
|
`🏆 **${winners}** Gewinner · ⏰ Ziehung <t:${Math.floor(endsAt.getTime() / 1000)}:R>`
|
|
)
|
|
.setFooter({ text: brandFooter('GIVEAWAY') })
|
|
.setTimestamp(endsAt);
|
|
|
|
const row = new ActionRowBuilder().addComponents(
|
|
new ButtonBuilder().setCustomId('giveaway_enter').setStyle(ButtonStyle.Primary).setLabel('Teilnehmen').setEmoji('🎉')
|
|
);
|
|
|
|
const message = await interaction.channel.send({ embeds: [embed], components: [row] });
|
|
saveGiveaway({
|
|
message_id: message.id,
|
|
channel_id: message.channelId,
|
|
prize,
|
|
winners,
|
|
// SQLite vergleicht mit datetime('now') im UTC-Format
|
|
ends_at: endsAt.toISOString().slice(0, 19).replace('T', ' '),
|
|
});
|
|
await interaction.reply({ content: '✅ Giveaway gestartet!', flags: MessageFlags.Ephemeral });
|
|
}
|