Files
d4rkbot/src/bot/commands/giveaway.js
T
D4rkst3randClaude Fable 5 f7b261c648 Branding-Tab: Name/Farben/Bot-Status/Avatar/Banner + Button-Farben für Rollen-Menüs
- 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>
2026-07-30 00:40:08 +02:00

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 });
}