import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, MessageFlags } from 'discord.js'; import { Command } from '../../structures/Command.js'; // Parse time duration strings like "30s", "2h", "1d30m" function parseDuration(input: string): number | null { const regex = /(\d+)\s*([a-z]+)/gi; let match; let totalMs = 0; while ((match = regex.exec(input)) !== null) { const value = parseInt(match[1]); const unit = match[2].toLowerCase(); const unitMs: Record = { 's': 1000, 'sec': 1000, 'sek': 1000, 'sekunden': 1000, 'm': 60000, 'min': 60000, 'minuten': 60000, 'h': 3600000, 'hour': 3600000, 'std': 3600000, 'stunden': 3600000, 'd': 86400000, 'day': 86400000, 't': 86400000, 'tage': 86400000, 'w': 604800000, 'week': 604800000, 'wochen': 604800000, 'mo': 2592000000, 'month': 2592000000, 'monat': 2592000000, 'monate': 2592000000, 'y': 31536000000, 'year': 31536000000, 'j': 31536000000, 'jahr': 31536000000, 'jahre': 31536000000 }; if (unitMs[unit]) { totalMs += value * unitMs[unit]; } } return totalMs > 0 ? totalMs : null; } // Parse date-time strings like "25.12.2024 14:30" or "tomorrow 3pm" function parseDateTime(input: string): Date | null { const now = new Date(); // German date format: DD.MM.YYYY or DD.MM.YYYY HH:MM const germanDate = /^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{1,2}))?$/.exec(input); if (germanDate) { const day = parseInt(germanDate[1]); const month = parseInt(germanDate[2]) - 1; // Month is 0-indexed const year = parseInt(germanDate[3]); const hour = germanDate[4] ? parseInt(germanDate[4]) : 12; const minute = germanDate[5] ? parseInt(germanDate[5]) : 0; const date = new Date(year, month, day, hour, minute); if (isNaN(date.getTime())) return null; return date; } // ISO format: YYYY-MM-DD or YYYY-MM-DD HH:MM const isoDate = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s](\d{1,2}):(\d{1,2}))?$/.exec(input); if (isoDate) { const year = parseInt(isoDate[1]); const month = parseInt(isoDate[2]) - 1; const day = parseInt(isoDate[3]); const hour = isoDate[4] ? parseInt(isoDate[4]) : 12; const minute = isoDate[5] ? parseInt(isoDate[5]) : 0; const date = new Date(year, month, day, hour, minute); if (isNaN(date.getTime())) return null; return date; } // Tomorrow/Nächsten Tag const lowerInput = input.toLowerCase(); if (lowerInput.includes('morgen') || lowerInput.includes('tomorrow')) { const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); // Try to extract time const timeMatch = /(\d{1,2}):(\d{1,2})/.exec(input); if (timeMatch) { tomorrow.setHours(parseInt(timeMatch[1]), parseInt(timeMatch[2]), 0, 0); } else { tomorrow.setHours(9, 0, 0, 0); // Default 9 AM } return tomorrow; } return null; } const command: Command = { data: new SlashCommandBuilder() .setName('timer') .setDescription('Erinnerungen und Timer verwalten.') .addSubcommand(subcommand => subcommand .setName('add') .setDescription('Fügt einen neuen Timer hinzu.') .addStringOption(option => option.setName('zeit') .setDescription('Zeit (z.B. "30min", "2h", "25.12.2024 14:30", "morgen")') .setRequired(true)) .addStringOption(option => option.setName('nachricht') .setDescription('Die Erinnerungs-Nachricht') .setRequired(true))) .addSubcommand(subcommand => subcommand .setName('remove') .setDescription('Entfernt einen Timer.') .addIntegerOption(option => option.setName('id') .setDescription('Die Timer-ID (siehe /timer list)') .setRequired(true))) .addSubcommand(subcommand => subcommand .setName('list') .setDescription('Zeigt alle Timer dieses Kanals an.')) .addSubcommand(subcommand => subcommand .setName('listall') .setDescription('Zeigt alle Timer des Servers an (Nur Moderatoren).')) .addSubcommand(subcommand => subcommand .setName('help') .setDescription('Zeigt Hilfe zu den Timer-Befehlen an.')), category: 'Public', async execute(interaction: any) { const subcommand = interaction.options.getSubcommand(); const guildId = interaction.guildId; const channelId = interaction.channelId; const userId = interaction.user.id; const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.ModerateMembers); const DB = interaction.client.DB; if (subcommand === 'help') { const embed = new EmbedBuilder() .setTitle('⏰ Timer Hilfe') .setColor(0x3498db) .setDescription('Setze Erinnerungen für dich oder andere.') .addFields( { name: '`/timer add `', value: 'Fügt einen neuen Timer hinzu.\nJeder kann Timer setzen.', inline: false }, { name: '`/timer remove `', value: 'Entfernt einen Timer (Mod).\nDie ID bekommst du über `/timer list`.', inline: false }, { name: '`/timer list`', value: 'Zeigt alle Timer in diesem Kanal an.', inline: false }, { name: '`/timer listall`', value: 'Zeigt alle Timer auf dem Server an (Mod).', inline: false }, { name: 'Zeitformate', value: 'Dauer: `30s`, `2h`, `1d30m`, `3wochen`, `1jahr`\nDatum: `25.12.2024 14:30` oder `morgen 15:00`', inline: false }, { name: 'Beispiele', value: '`/timer add 30min Meeting erinnern`\n`/timer add morgen 9 Uhr Aufstehen`\n`/timer add 25.12.2024 18:00 Weihnachtsessen`', inline: false } ) .setTimestamp(); await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); return; } if (subcommand === 'add') { const timeInput = interaction.options.getString('zeit')!; const message = interaction.options.getString('nachricht')!; const now = new Date(); let targetTime: Date | null = null; // Try duration first (e.g., "30min", "2h", "1d30m") const duration = parseDuration(timeInput); if (duration !== null) { targetTime = new Date(now.getTime() + duration); } else { // Try date-time parsing targetTime = parseDateTime(timeInput); } if (!targetTime || targetTime <= now) { await interaction.reply({ content: '❌ Ungültige Zeitangabe. Verwende z.B.: "30min", "2h30m", "25.12.2024 14:30" oder "morgen 15:00"', flags: [MessageFlags.Ephemeral] }); return; } // Insert reminder const result = DB.run( 'INSERT INTO reminders (guild_id, channel_id, user_id, reminder_text, target_time) VALUES (?, ?, ?, ?, ?)', guildId, channelId, userId, message, targetTime.toISOString() ); const timeDiff = targetTime.getTime() - now.getTime(); const minutes = Math.floor(timeDiff / 60000); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); let timeStr = ''; if (days > 0) timeStr += `${days}d `; if (hours % 24 > 0) timeStr += `${hours % 24}h `; if (minutes % 60 > 0) timeStr += `${minutes % 60}min`; await interaction.reply({ content: `✅ Erinnerung gesetzt (ID: ${result.lastInsertRowid}) für **${timeStr.trim()}**`, flags: [MessageFlags.Ephemeral] }); return; } if (subcommand === 'remove') { if (!isAdmin) { await interaction.reply({ content: '❌ Keine Berechtigung. Moderatoren erforderlich.', flags: [MessageFlags.Ephemeral] }); return; } const timerId = interaction.options.getInteger('id')!; const result = DB.run('DELETE FROM reminders WHERE id = ? AND guild_id = ?', timerId, guildId); if (result.changes === 0) { await interaction.reply({ content: `❌ Timer mit ID ${timerId} nicht gefunden.`, flags: [MessageFlags.Ephemeral] }); return; } await interaction.reply({ content: `✅ Timer mit ID ${timerId} entfernt.`, flags: [MessageFlags.Ephemeral] }); return; } if (subcommand === 'list') { const reminders: any[] = DB.all( 'SELECT * FROM reminders WHERE guild_id = ? AND channel_id = ? ORDER BY target_time ASC', guildId, channelId ); if (reminders.length === 0) { await interaction.reply({ content: 'Keine Timer in diesem Kanal.', flags: [MessageFlags.Ephemeral] }); return; } const embed = new EmbedBuilder() .setTitle('⏰ Timer in diesem Kanal') .setColor(0x3498db) .setTimestamp(); const now = new Date(); const chunks: string[] = []; const chunkSize = 10; for (let i = 0; i < reminders.length; i += chunkSize) { const chunk = reminders.slice(i, i + chunkSize).map((r: any) => { const targetTime = new Date(r.target_time); const diffMs = targetTime.getTime() - now.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); let timeLeft = ''; if (diffDays > 0) timeLeft = `${diffDays}d ${diffHours % 24}h`; else if (diffHours > 0) timeLeft = `${diffHours}h ${diffMins % 60}min`; else if (diffMins > 0) timeLeft = `${diffMins}min`; else timeLeft = '< 1min'; const dateStr = targetTime.toLocaleString('de-DE'); return `• **ID ${r.id}**: ${r.reminder_text}\n 🕐 ${dateStr} (in ${timeLeft})`; }).join('\n\n'); chunks.push(chunk); } embed.setDescription(chunks[0]); await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); for (let i = 1; i < chunks.length; i++) { const nextEmbed = new EmbedBuilder() .setColor(0x3498db) .setDescription(chunks[i]); await interaction.followUp({ embeds: [nextEmbed], flags: [MessageFlags.Ephemeral] }); } return; } if (subcommand === 'listall') { if (!isAdmin) { await interaction.reply({ content: '❌ Keine Berechtigung. Moderatoren erforderlich.', flags: [MessageFlags.Ephemeral] }); return; } const reminders: any[] = DB.all( 'SELECT * FROM reminders WHERE guild_id = ? ORDER BY target_time ASC', guildId ); if (reminders.length === 0) { await interaction.reply({ content: 'Keine Timer auf dem Server.', flags: [MessageFlags.Ephemeral] }); return; } const embed = new EmbedBuilder() .setTitle('⏰ Alle Timer auf dem Server') .setColor(0xe74c3c) .setTimestamp(); const now = new Date(); const chunks: string[] = []; const chunkSize = 10; for (let i = 0; i < reminders.length; i += chunkSize) { const chunk = reminders.slice(i, i + chunkSize).map((r: any) => { const targetTime = new Date(r.target_time); const diffMs = targetTime.getTime() - now.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); let timeLeft = ''; if (diffDays > 0) timeLeft = `${diffDays}d ${diffHours % 24}h`; else if (diffHours > 0) timeLeft = `${diffHours}h ${diffMins % 60}min`; else if (diffMins > 0) timeLeft = `${diffMins}min`; else timeLeft = '< 1min'; const dateStr = targetTime.toLocaleString('de-DE'); return `• **ID ${r.id}**: ${r.reminder_text}\n 📍 Kanal: ${r.channel_id}\n 🕐 ${dateStr} (in ${timeLeft})`; }).join('\n\n'); chunks.push(chunk); } embed.setDescription(chunks[0]); await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); for (let i = 1; i < chunks.length; i++) { const nextEmbed = new EmbedBuilder() .setColor(0xe74c3c) .setDescription(chunks[i]); await interaction.followUp({ embeds: [nextEmbed], flags: [MessageFlags.Ephemeral] }); } } }, }; export default command;