Add: Timer and Trigger modules with help subcommands
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 6s

- Add /timer command: add, remove, list, listall, help
- Add /trigger command: add, remove, list, help
- Add /admin help and /owner help subcommands
- Add /twitch help subcommand
- Add ReminderManager for timer notifications
- Add reminders table to database
- Update README.md and AGENTS-BOT.md documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 18:53:33 +01:00
parent f78636447b
commit 5c6188ea13
10 changed files with 716 additions and 33 deletions

View File

@@ -116,6 +116,10 @@ const command: Command = {
option.setName('duration_value')
.setDescription('Dauer in Sekunden')
.setMinValue(0)))
.addSubcommand(subcommand =>
subcommand
.setName('help')
.setDescription('Zeigt Hilfe zu den Admin-Befehlen an.'))
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
category: 'Admin',
async execute(interaction: any) {
@@ -124,6 +128,28 @@ const command: Command = {
const guild = interaction.guild;
const guildId = interaction.guildId;
if (subcommand === 'help') {
const embed = new EmbedBuilder()
.setTitle('🛡️ Admin Hilfe')
.setColor(0xe74c3c)
.setDescription('Moderations-Tools für Server-Verwaltung.')
.addFields(
{ name: '`/admin kick <nutzer> [grund]`', value: 'Kickt einen Nutzer vom Server.', inline: false },
{ name: '`/admin warn <nutzer> <grund>`', value: 'Verwarnt einen Nutzer. Bei X Warnungen wird automatisch eine Aktion ausgeführt.', inline: false },
{ name: '`/admin unwarn <nutzer>`', value: 'Entfernt die letzte Warnung eines Nutzers.', inline: false },
{ name: '`/admin mute <nutzer> <dauer> <einheit> [grund]`', value: 'Muted einen Nutzer für eine bestimmte Zeit.\nEinheiten: sec, min, hour, day, week, month, year, perm', inline: false },
{ name: '`/admin unmute <nutzer>`', value: 'Entmutet einen Nutzer.', inline: false },
{ name: '`/admin ban <nutzer> [grund] [delete_days]`', value: 'Bannt einen Nutzer vom Server.\ndelete_days: Nachrichten der letzten X Tage löschen (0-7).', inline: false },
{ name: '`/admin unban <id>`', value: 'Entbannt einen Nutzer (via ID oder @username).', inline: false },
{ name: '`/admin config <setting> [wert]`', value: 'Konfiguriert das Warn-System.\nSettings: warn_threshold, warn_action, warn_mute_duration, show', inline: false },
{ name: 'Warn-System', value: 'Automatische Aktionen bei X Warnungen:\n`/admin config warn_action kick|mute|ban`', inline: false }
)
.setTimestamp();
await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] });
return;
}
// Get or create guild settings
let settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId);
if (!settings) {

View File

@@ -18,6 +18,10 @@ const command: Command = {
subcommand
.setName('servers')
.setDescription('Listet alle Server'))
.addSubcommand(subcommand =>
subcommand
.setName('help')
.setDescription('Zeigt Hilfe zu den Owner-Befehlen an.'))
.setDMPermission(true),
category: 'Owner',
async execute(interaction: any, client: any) {
@@ -30,6 +34,22 @@ const command: Command = {
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'help') {
const embed = new EmbedBuilder()
.setTitle('🔑 Owner Hilfe')
.setColor(0xf1c40f)
.setDescription('Bot-Einstellungen und Statistiken.')
.addFields(
{ name: '`/owner deploy`', value: 'Aktualisiert alle Discord-Befehle.\nNach Code-Änderungen ausführen.', inline: false },
{ name: '`/owner stats`', value: 'Zeigt Bot-Statistiken: Uptime, Latency, Server- und Nutzerzahl.', inline: false },
{ name: '`/owner servers`', value: 'Listet alle Server, auf denen der Bot aktiv ist.', inline: false }
)
.setTimestamp();
await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] });
return;
}
if (subcommand === 'deploy') {
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });

View File

@@ -0,0 +1,347 @@
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<string, number> = {
'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 <zeit> <nachricht>`', value: 'Fügt einen neuen Timer hinzu.\nJeder kann Timer setzen.', inline: false },
{ name: '`/timer remove <id>`', 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 r.*, c.name as channel_name FROM reminders r LEFT JOIN channels c ON r.channel_id = c.id WHERE r.guild_id = ? ORDER BY r.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;

View File

@@ -0,0 +1,131 @@
import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, MessageFlags } from 'discord.js';
import { Command } from '../../structures/Command.js';
const command: Command = {
data: new SlashCommandBuilder()
.setName('trigger')
.setDescription('Auto-Antworten verwalten.')
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Fügt einen neuen Trigger hinzu.')
.addStringOption(option =>
option.setName('trigger')
.setDescription('Das Trigger-Wort')
.setRequired(true))
.addStringOption(option =>
option.setName('antwort')
.setDescription('Die Antwort-Nachricht')
.setRequired(true)))
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Entfernt einen Trigger.')
.addStringOption(option =>
option.setName('trigger')
.setDescription('Das Trigger-Wort')
.setRequired(true)))
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('Listet alle Trigger auf.'))
.addSubcommand(subcommand =>
subcommand
.setName('help')
.setDescription('Zeigt Hilfe zu den Trigger-Befehlen an.')),
category: 'Admin',
async execute(interaction: any) {
const subcommand = interaction.options.getSubcommand();
const guildId = interaction.guildId;
const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.ModerateMembers);
const DB = interaction.client.DB;
if (subcommand === 'help') {
const embed = new EmbedBuilder()
.setTitle('🎯 Trigger Hilfe')
.setColor(0x3498db)
.setDescription('Automatische Antworten auf Trigger-Wörter.')
.addFields(
{ name: '`/trigger add <wort> <antwort>`', value: 'Fügt einen neuen Trigger hinzu (Mod).\nTrigger-Wörter werden automatisch klein geschrieben.', inline: false },
{ name: '`/trigger remove <wort>`', value: 'Entfernt einen Trigger (Mod).', inline: false },
{ name: '`/trigger list`', value: 'Zeigt alle Trigger auf diesem Server an.', inline: false },
{ name: 'Beispiel', value: '`/trigger add hallo Hallo! Wie kann ich helfen?`', inline: false }
)
.setTimestamp();
await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] });
return;
}
if (subcommand === 'list') {
const triggers: any[] = DB.all('SELECT * FROM auto_responses WHERE guild_id = ? ORDER BY trigger_word ASC', guildId);
if (triggers.length === 0) {
await interaction.reply({ content: 'Keine Trigger konfiguriert.', flags: [MessageFlags.Ephemeral] });
return;
}
const embed = new EmbedBuilder()
.setTitle('🎯 Auto-Antworten')
.setColor(0x3498db)
.setTimestamp();
const chunks: string[] = [];
const chunkSize = 10;
for (let i = 0; i < triggers.length; i += chunkSize) {
const chunk = triggers.slice(i, i + chunkSize).map((t: any) =>
`• **${t.trigger_word}** → ${t.response_text}`
).join('\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;
}
// Admin only for add/remove
if (!isAdmin) {
await interaction.reply({ content: '❌ Keine Berechtigung. Moderatoren erforderlich.', flags: [MessageFlags.Ephemeral] });
return;
}
if (subcommand === 'add') {
const triggerWord = interaction.options.getString('trigger')!.toLowerCase();
const responseText = interaction.options.getString('antwort')!;
DB.run(
'INSERT INTO auto_responses (guild_id, trigger_word, response_text) VALUES (?, ?, ?) ON CONFLICT(guild_id, trigger_word) DO UPDATE SET response_text = ?',
guildId,
triggerWord,
responseText,
responseText
);
await interaction.reply({ content: `✅ Trigger **${triggerWord}** hinzugefügt/aktualisiert.`, flags: [MessageFlags.Ephemeral] });
return;
}
if (subcommand === 'remove') {
const triggerWord = interaction.options.getString('trigger')!.toLowerCase();
const result = DB.run('DELETE FROM auto_responses WHERE guild_id = ? AND trigger_word = ?', guildId, triggerWord);
if (result.changes === 0) {
await interaction.reply({ content: `❌ Trigger **${triggerWord}** nicht gefunden.`, flags: [MessageFlags.Ephemeral] });
return;
}
await interaction.reply({ content: `✅ Trigger **${triggerWord}** entfernt.`, flags: [MessageFlags.Ephemeral] });
}
},
};
export default command;

View File

@@ -36,7 +36,11 @@ const command: Command = {
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('Listet überwachte Kanäle.')),
.setDescription('Listet überwachte Kanäle.'))
.addSubcommand(subcommand =>
subcommand
.setName('help')
.setDescription('Zeigt Hilfe zu den Twitch-Befehlen an.')),
category: 'Public',
async execute(interaction: any) {
const subcommand = interaction.options.getSubcommand();
@@ -44,6 +48,24 @@ const command: Command = {
const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.ModerateMembers);
const DB = interaction.client.DB;
if (subcommand === 'help') {
const embed = new EmbedBuilder()
.setTitle('📺 Twitch Hilfe')
.setColor(0x6441a5)
.setDescription('Überwache Twitch-Streams und bekomme Benachrichtigungen.')
.addFields(
{ name: '`/twitch online <kanal>`', value: 'Prüft, ob ein Kanal gerade online ist.', inline: false },
{ name: '`/twitch add <kanal> [nachricht]`', value: 'Überwacht einen Kanal in diesem Kanal (Mod).\nWenn der Streamer live geht, wird benachrichtigt.', inline: false },
{ name: '`/twitch remove <kanal>`', value: 'Entfernt einen Kanal aus der Überwachung (Mod).', inline: false },
{ name: '`/twitch list`', value: 'Zeigt alle überwachten Kanäle in diesem Kanal an.', inline: false },
{ name: 'Beispiel', value: '`/twitch add shroud Hallo! Shroud ist live!`', inline: false }
)
.setTimestamp();
await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] });
return;
}
if (subcommand === 'online') {
const channelName = interaction.options.getString('channel')!.toLowerCase();
await interaction.deferReply();

View File

@@ -6,6 +6,7 @@ import { dirname, join } from 'node:path';
import { Command } from './structures/Command.js';
import { DB } from './structures/Database.js';
import { TwitchManager } from './structures/TwitchManager.js';
import { ReminderManager } from './structures/ReminderManager.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -59,4 +60,7 @@ for (const file of eventFiles) {
// Start Twitch Polling
TwitchManager.startPolling(client);
// Start Reminder Polling
ReminderManager.startPolling(client);
client.login(process.env.DISCORD_TOKEN);

View File

@@ -86,6 +86,19 @@ export class DB {
)
`).run();
// Reminders
db.prepare(`
CREATE TABLE IF NOT EXISTS reminders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT,
channel_id TEXT,
user_id TEXT,
reminder_text TEXT,
target_time DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`).run();
console.log('[DATABASE] Initialization complete.');
}

View File

@@ -0,0 +1,62 @@
import { TextChannel, EmbedBuilder } from 'discord.js';
export class ReminderManager {
static async checkReminders(client: any) {
const now = new Date().toISOString();
// Get all due reminders
const dueReminders: any[] = (client as any).DB?.all(
'SELECT * FROM reminders WHERE target_time <= ?',
now
) || [];
if (dueReminders.length === 0) return;
for (const reminder of dueReminders) {
try {
const guild = client.guilds.cache.get(reminder.guild_id);
if (!guild) {
// Remove reminder if guild doesn't exist
(client as any).DB?.run('DELETE FROM reminders WHERE id = ?', reminder.id);
continue;
}
const channel = guild.channels.cache.get(reminder.channel_id) as TextChannel;
if (!channel || !channel.isTextBased()) {
// Remove reminder if channel doesn't exist or is not text-based
(client as any).DB?.run('DELETE FROM reminders WHERE id = ?', reminder.id);
continue;
}
const user = await guild.members.fetch(reminder.user_id).catch(() => null);
const userMention = user ? `<@${user.id}>` : `<@${reminder.user_id}>`;
const embed = new EmbedBuilder()
.setTitle('⏰ Erinnerung')
.setColor(0x3498db)
.setDescription(reminder.reminder_text)
.addFields({
name: 'Gesetzt von',
value: userMention,
inline: true
})
.setTimestamp();
await channel.send({ content: `${userMention} deine Erinnerung:`, embeds: [embed] });
// Delete the reminder after sending
(client as any).DB?.run('DELETE FROM reminders WHERE id = ?', reminder.id);
console.log(`[REMINDER] Sent reminder ID ${reminder.id} in ${guild.name}`);
} catch (error) {
console.error(`[REMINDER] Error sending reminder ID ${reminder.id}:`, error);
}
}
}
static startPolling(client: any) {
// Check every 30 seconds
setInterval(() => this.checkReminders(client), 30 * 1000);
console.log('[REMINDER] Polling started (30s interval).');
}
}