diff --git a/src/commands/utility/admin.ts b/src/commands/utility/admin.ts index 7b72333..a58494e 100644 --- a/src/commands/utility/admin.ts +++ b/src/commands/utility/admin.ts @@ -1,6 +1,27 @@ import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, GuildMember, MessageFlags } from 'discord.js'; import { Command } from '../../structures/Command.js'; +async function sendModerationLog(client: any, guildId: string, event: string, embed: EmbedBuilder) { + const DB = client.DB; + const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId); + + if (!settings?.log_events) return; + const activeEvents = settings.log_events.split(',').filter(Boolean); + if (!activeEvents.includes(event)) return; + + const logSettings: any = DB.get('SELECT log_channel FROM guild_settings WHERE guild_id = ?', guildId); + if (!logSettings?.log_channel) return; + + const channel = client.guilds.cache.get(guildId)?.channels.cache.get(logSettings.log_channel); + if (!channel || !channel.isTextBased()) return; + + try { + await (channel as any).send({ embeds: [embed] }); + } catch (error) { + console.error(`[LOG] Error sending moderation log (${event}):`, error); + } +} + const command: Command = { data: new SlashCommandBuilder() .setName('admin') @@ -174,6 +195,17 @@ const command: Command = { try { await target.kick(reason); await interaction.reply(`✅ Gekickt: ${target.user.tag}\nGrund: ${reason}`); + + const embed = new EmbedBuilder() + .setTitle('👢 User gekickt') + .setColor(0xf39c12) + .addFields( + { name: 'Nutzer', value: `${target.user.tag} (<@${target.id}>)`, inline: true }, + { name: 'Moderator', value: `${interaction.user.tag} (<@${interaction.user.id}>)`, inline: true }, + { name: 'Grund', value: reason, inline: false } + ) + .setTimestamp(); + await sendModerationLog(interaction.client, guildId, 'kicks', embed); } catch (error) { console.error(error); await interaction.reply({ content: '❌ Fehler beim Kicken.', flags: [MessageFlags.Ephemeral] }); @@ -236,6 +268,18 @@ const command: Command = { } await interaction.reply({ content: message }); + + const embed = new EmbedBuilder() + .setTitle('⚠️ User verwarnt') + .setColor(0xf39c12) + .addFields( + { name: 'Nutzer', value: `${target.user.tag} (<@${target.id}>)`, inline: true }, + { name: 'Moderator', value: `${interaction.user.tag} (<@${interaction.user.id}>)`, inline: true }, + { name: 'Grund', value: reason, inline: false }, + { name: 'Warnungen', value: `${warnCount}/${warnThreshold}`, inline: true } + ) + .setTimestamp(); + await sendModerationLog(interaction.client, guildId, 'warns', embed); return; } @@ -320,6 +364,18 @@ const command: Command = { try { await target.timeout(durationMs, reason); await interaction.reply(`✅ Gemuted: ${target.user.tag}\nDauer: ${durationText}\nGrund: ${reason}`); + + const embed = new EmbedBuilder() + .setTitle('🔇 User gemutet') + .setColor(0xf39c12) + .addFields( + { name: 'Nutzer', value: `${target.user.tag} (<@${target.id}>)`, inline: true }, + { name: 'Moderator', value: `${interaction.user.tag} (<@${interaction.user.id}>)`, inline: true }, + { name: 'Dauer', value: durationText, inline: true }, + { name: 'Grund', value: reason, inline: false } + ) + .setTimestamp(); + await sendModerationLog(interaction.client, guildId, 'mutes', embed); } catch (error) { console.error(error); await interaction.reply({ content: '❌ Fehler beim Muten.', flags: [MessageFlags.Ephemeral] }); @@ -363,6 +419,18 @@ const command: Command = { try { await target.ban({ deleteMessageSeconds: deleteDays * 86400, reason }); await interaction.reply(`✅ Gebannt: ${target.user.tag}\nGrund: ${reason}`); + + const embed = new EmbedBuilder() + .setTitle('🔨 User gebannt') + .setColor(0xe74c3c) + .addFields( + { name: 'Nutzer', value: `${target.user.tag} (<@${target.id}>)`, inline: true }, + { name: 'Moderator', value: `${interaction.user.tag} (<@${interaction.user.id}>)`, inline: true }, + { name: 'Grund', value: reason, inline: false }, + { name: 'Nachrichten gelöscht', value: `${deleteDays} Tage`, inline: true } + ) + .setTimestamp(); + await sendModerationLog(interaction.client, guildId, 'bans', embed); } catch (error) { console.error(error); await interaction.reply({ content: '❌ Fehler beim Bannen.', flags: [MessageFlags.Ephemeral] }); diff --git a/src/commands/utility/log.ts b/src/commands/utility/log.ts new file mode 100644 index 0000000..e7695f1 --- /dev/null +++ b/src/commands/utility/log.ts @@ -0,0 +1,175 @@ +import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, MessageFlags } from 'discord.js'; +import { Command } from '../../structures/Command.js'; + +const AVAILABLE_EVENTS = [ + { value: 'messages', label: 'Nachrichten', desc: 'Gelöschte und edierte Nachrichten' }, + { value: 'roles', label: 'Rollen', desc: 'Rollenänderungen' }, + { value: 'nicks', label: 'Nicknames', desc: 'Nickname-Änderungen' }, + { value: 'channels', label: 'Channels', desc: 'Channel erstellt/gelöscht' }, + { value: 'warns', label: 'Verwarnungen', desc: 'User verwarnt' }, + { value: 'mutes', label: 'Mutes', desc: 'User gemutet' }, + { value: 'kicks', label: 'Kicks', desc: 'User gekickt' }, + { value: 'bans', label: 'Bans', desc: 'User gebannt' }, +]; + +const command: Command = { + data: new SlashCommandBuilder() + .setName('log') + .setDescription('Log-Einstellungen verwalten (Mod).') + .addSubcommand(subcommand => + subcommand + .setName('setchannel') + .setDescription('Log-Kanal setzen (Mod).') + .addChannelOption(option => + option.setName('channel') + .setDescription('Discord-Kanal für Logs') + .setRequired(true))) + .addSubcommand(subcommand => + subcommand + .setName('enable') + .setDescription('Event-Logging aktivieren (Mod).') + .addStringOption(option => + option.setName('event') + .setDescription('Welches Event loggen?') + .setRequired(true) + .addChoices(...AVAILABLE_EVENTS.map(e => ({ name: `${e.label} - ${e.desc}`, value: e.value }))))) + .addSubcommand(subcommand => + subcommand + .setName('disable') + .setDescription('Event-Logging deaktivieren (Mod).') + .addStringOption(option => + option.setName('event') + .setDescription('Welches Event nicht mehr loggen?') + .setRequired(true) + .addChoices(...AVAILABLE_EVENTS.map(e => ({ name: `${e.label} - ${e.desc}`, value: e.value }))))) + .addSubcommand(subcommand => + subcommand + .setName('list') + .setDescription('Aktive Log-Events anzeigen (Mod).')) + .addSubcommand(subcommand => + subcommand + .setName('help') + .setDescription('Hilfe zu Log-Befehlen (Mod).')), + 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; + + // Ensure guild settings exist + DB.run('INSERT OR IGNORE INTO guild_settings (guild_id) VALUES (?)', guildId); + + if (subcommand === 'help') { + const embed = new EmbedBuilder() + .setTitle('📋 Log Hilfe') + .setColor(0x9b59b6) + .setDescription('Loggt verschiedene Server-Events in einen Kanal.') + .addFields( + { name: '`/log setchannel <#kanal>`', value: 'Setzt den Kanal für Logs (Mod).', inline: false }, + { name: '`/log enable `', value: 'Aktiviert ein Event zum Loggen (Mod).', inline: false }, + { name: '`/log disable `', value: 'Deaktiviert ein Event (Mod).', inline: false }, + { name: '`/log list`', value: 'Zeigt aktive Log-Events (Mod).', inline: false }, + { name: 'Events', value: AVAILABLE_EVENTS.map(e => `• **${e.value}**: ${e.desc}`).join('\n'), inline: false } + ) + .setTimestamp(); + + await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); + return; + } + + // Admin only for all commands + if (!isAdmin) { + await interaction.reply({ content: '❌ Keine Berechtigung. Moderatoren erforderlich.', flags: [MessageFlags.Ephemeral] }); + return; + } + + if (subcommand === 'setchannel') { + const channel = interaction.options.getChannel('channel'); + + DB.run( + 'UPDATE guild_settings SET log_channel = ? WHERE guild_id = ?', + channel.id, + guildId + ); + + await interaction.reply({ content: `✅ Log-Kanal auf ${channel} gesetzt.`, flags: [MessageFlags.Ephemeral] }); + return; + } + + if (subcommand === 'list') { + const settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId); + + const channel = settings?.log_channel + ? `<#${settings.log_channel}>` + : 'Nicht gesetzt'; + + const activeEvents = (settings?.log_events || '').split(',').filter(Boolean); + const eventsList = activeEvents.length > 0 + ? AVAILABLE_EVENTS.filter(e => activeEvents.includes(e.value)) + .map(e => `• **${e.label}**: ${e.desc}`) + .join('\n') + : 'Keine Events aktiv'; + + const embed = new EmbedBuilder() + .setTitle('📋 Log Einstellungen') + .setColor(0x9b59b6) + .setDescription(`Server: **${interaction.guild.name}**`) + .addFields( + { name: 'Log-Kanal', value: channel, inline: true }, + { name: 'Aktive Events', value: eventsList, inline: false } + ) + .setTimestamp(); + + await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); + return; + } + + if (subcommand === 'enable') { + const event = interaction.options.getString('event')!; + const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId); + + const currentEvents = (settings?.log_events || '').split(',').filter(Boolean); + + if (currentEvents.includes(event)) { + await interaction.reply({ content: `ℹ️ Event **${event}** ist bereits aktiviert.`, flags: [MessageFlags.Ephemeral] }); + return; + } + + currentEvents.push(event); + + DB.run( + 'UPDATE guild_settings SET log_events = ? WHERE guild_id = ?', + currentEvents.join(','), + guildId + ); + + await interaction.reply({ content: `✅ Event **${event}** aktiviert.`, flags: [MessageFlags.Ephemeral] }); + return; + } + + if (subcommand === 'disable') { + const event = interaction.options.getString('event')!; + const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId); + + const currentEvents = (settings?.log_events || '').split(',').filter(Boolean); + + if (!currentEvents.includes(event)) { + await interaction.reply({ content: `ℹ️ Event **${event}** ist nicht aktiviert.`, flags: [MessageFlags.Ephemeral] }); + return; + } + + const newEvents = currentEvents.filter((e: string) => e !== event); + + DB.run( + 'UPDATE guild_settings SET log_events = ? WHERE guild_id = ?', + newEvents.join(','), + guildId + ); + + await interaction.reply({ content: `✅ Event **${event}** deaktiviert.`, flags: [MessageFlags.Ephemeral] }); + } + }, +}; + +export default command; \ No newline at end of file diff --git a/src/events/channelEvent.ts b/src/events/channelEvent.ts new file mode 100644 index 0000000..88dbf48 --- /dev/null +++ b/src/events/channelEvent.ts @@ -0,0 +1,56 @@ +import { Events, GuildChannel, EmbedBuilder } from 'discord.js'; + +async function sendLog(client: any, guildId: string, event: string, embed: EmbedBuilder) { + const DB = client.DB; + const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId); + + if (!settings?.log_events) return; + const activeEvents = settings.log_events.split(',').filter(Boolean); + if (!activeEvents.includes(event)) return; + + const logSettings: any = DB.get('SELECT log_channel FROM guild_settings WHERE guild_id = ?', guildId); + if (!logSettings?.log_channel) return; + + const channel = client.guilds.cache.get(guildId)?.channels.cache.get(logSettings.log_channel); + if (!channel || !channel.isTextBased()) return; + + try { + await (channel as any).send({ embeds: [embed] }); + } catch (error) { + console.error(`[LOG] Error sending log (${event}):`, error); + } +} + +// Channel Create +export const channelCreateEvent = { + name: Events.ChannelCreate, + async execute(channel: GuildChannel) { + const embed = new EmbedBuilder() + .setTitle('📁 Channel erstellt') + .setColor(0x27ae60) + .addFields( + { name: 'Name', value: channel.name, inline: true }, + { name: 'Typ', value: channel.type.toString(), inline: true } + ) + .setTimestamp(); + + await sendLog(channel.client, channel.guild!.id, 'channels', embed); + }, +}; + +// Channel Delete +export const channelDeleteEvent = { + name: Events.ChannelDelete, + async execute(channel: GuildChannel) { + const embed = new EmbedBuilder() + .setTitle('📁 Channel gelöscht') + .setColor(0xe74c3c) + .addFields( + { name: 'Name', value: channel.name, inline: true }, + { name: 'Typ', value: channel.type.toString(), inline: true } + ) + .setTimestamp(); + + await sendLog(channel.client, channel.guild!.id, 'channels', embed); + }, +}; \ No newline at end of file diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts new file mode 100644 index 0000000..611d328 --- /dev/null +++ b/src/events/guildMemberUpdate.ts @@ -0,0 +1,80 @@ +import { Events, GuildMember, EmbedBuilder } from 'discord.js'; + +async function sendLog(client: any, guildId: string, event: string, embed: EmbedBuilder) { + const DB = client.DB; + const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId); + + if (!settings?.log_events) return; + const activeEvents = settings.log_events.split(',').filter(Boolean); + if (!activeEvents.includes(event)) return; + + const logSettings: any = DB.get('SELECT log_channel FROM guild_settings WHERE guild_id = ?', guildId); + if (!logSettings?.log_channel) return; + + const channel = client.guilds.cache.get(guildId)?.channels.cache.get(logSettings.log_channel); + if (!channel || !channel.isTextBased()) return; + + try { + await (channel as any).send({ embeds: [embed] }); + } catch (error) { + console.error(`[LOG] Error sending log (${event}):`, error); + } +} + +// Guild Member Update (nickname + roles) +export default { + name: Events.GuildMemberUpdate, + async execute(oldMember: GuildMember, newMember: GuildMember) { + const guildId = newMember.guild.id; + + // Nickname change + if (oldMember.nickname !== newMember.nickname) { + const embed = new EmbedBuilder() + .setTitle('📝 Nickname geändert') + .setColor(0x3498db) + .addFields( + { name: 'Nutzer', value: `${newMember.user.tag} (<@${newMember.id}>)`, inline: true } + ) + .addFields( + { name: 'Vorher', value: oldMember.nickname || newMember.user.username, inline: true }, + { name: 'Nachher', value: newMember.nickname || newMember.user.username, inline: true } + ) + .setTimestamp(); + + await sendLog(newMember.client, guildId, 'nicks', embed); + } + + // Role changes + const oldRoles = oldMember.roles.cache; + const newRoles = newMember.roles.cache; + + const addedRoles = newRoles.filter(r => !oldRoles.has(r.id)); + const removedRoles = oldRoles.filter(r => !newRoles.has(r.id)); + + for (const role of addedRoles) { + const embed = new EmbedBuilder() + .setTitle('🎭 Rolle hinzugefügt') + .setColor(0x27ae60) + .addFields( + { name: 'Nutzer', value: `${newMember.user.tag} (<@${newMember.id}>)`, inline: true }, + { name: 'Rolle', value: `${role[1].name} (<@&${role[1].id}>)`, inline: true } + ) + .setTimestamp(); + + await sendLog(newMember.client, guildId, 'roles', embed); + } + + for (const role of removedRoles) { + const embed = new EmbedBuilder() + .setTitle('🎭 Rolle entfernt') + .setColor(0xe74c3c) + .addFields( + { name: 'Nutzer', value: `${newMember.user.tag} (<@${newMember.id}>)`, inline: true }, + { name: 'Rolle', value: `${role[1].name} (<@&${role[1].id}>)`, inline: true } + ) + .setTimestamp(); + + await sendLog(newMember.client, guildId, 'roles', embed); + } + }, +}; \ No newline at end of file diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts new file mode 100644 index 0000000..71783b2 --- /dev/null +++ b/src/events/messageDelete.ts @@ -0,0 +1,73 @@ +import { Events, Message, TextChannel, GuildChannel, AuditLogEvent, EmbedBuilder } from 'discord.js'; + +function getLogChannel(client: any, guildId: string): TextChannel | null { + const DB = client.DB; + const settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId); + + if (!settings?.log_channel) { + return null; + } + + const channel = client.guilds.cache.get(guildId)?.channels.cache.get(settings.log_channel); + if (!channel || !channel.isTextBased()) { + return null; + } + + return channel as TextChannel; +} + +function isEventEnabled(client: any, guildId: string, event: string): boolean { + const DB = client.DB; + const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId); + + if (!settings?.log_events) { + return false; + } + + const activeEvents = settings.log_events.split(',').filter(Boolean); + return activeEvents.includes(event); +} + +async function sendLog(client: any, guildId: string, event: string, embed: EmbedBuilder) { + if (!isEventEnabled(client, guildId, event)) { + return; + } + + const channel = getLogChannel(client, guildId); + if (!channel) { + return; + } + + try { + await channel.send({ embeds: [embed] }); + } catch (error) { + console.error(`[LOG] Error sending log (${event}):`, error); + } +} + +// Message Delete +export default { + name: Events.MessageDelete, + async execute(message: Message) { + if (!message.guild || message.author.bot) return; + + const embed = new EmbedBuilder() + .setTitle('🗑️ Nachricht gelöscht') + .setColor(0xe74c3c) + .addFields( + { name: 'Autor', value: `${message.author.tag} (<@${message.author.id}>)`, inline: true }, + { name: 'Kanal', value: `<#${message.channelId}>`, inline: true } + ) + .setTimestamp(); + + if (message.content) { + embed.addFields({ name: 'Inhalt', value: message.content.substring(0, 1024), inline: false }); + } + + if (message.attachments.size > 0) { + embed.addFields({ name: 'Anhänge', value: `${message.attachments.size} Datei(en)`, inline: true }); + } + + await sendLog(message.client, message.guildId!, 'messages', embed); + }, +}; \ No newline at end of file diff --git a/src/events/messageUpdate.ts b/src/events/messageUpdate.ts new file mode 100644 index 0000000..09ec8e3 --- /dev/null +++ b/src/events/messageUpdate.ts @@ -0,0 +1,47 @@ +import { Events, Message, EmbedBuilder } from 'discord.js'; + +async function sendLog(client: any, guildId: string, event: string, embed: EmbedBuilder) { + const DB = client.DB; + const settings: any = DB.get('SELECT log_events FROM guild_settings WHERE guild_id = ?', guildId); + + if (!settings?.log_events) return; + const activeEvents = settings.log_events.split(',').filter(Boolean); + if (!activeEvents.includes(event)) return; + + const logSettings: any = DB.get('SELECT log_channel FROM guild_settings WHERE guild_id = ?', guildId); + if (!logSettings?.log_channel) return; + + const channel = client.guilds.cache.get(guildId)?.channels.cache.get(logSettings.log_channel); + if (!channel || !channel.isTextBased()) return; + + try { + await (channel as any).send({ embeds: [embed] }); + } catch (error) { + console.error(`[LOG] Error sending log (${event}):`, error); + } +} + +// Message Update (Edit) +export default { + name: Events.MessageUpdate, + async execute(oldMessage: Message, newMessage: Message) { + if (!newMessage.guild || newMessage.author?.bot) return; + if (!oldMessage.content || !newMessage.content) return; + if (oldMessage.content === newMessage.content) return; + + const embed = new EmbedBuilder() + .setTitle('✏️ Nachricht editiert') + .setColor(0xf39c12) + .addFields( + { name: 'Autor', value: `${newMessage.author.tag} (<@${newMessage.author.id}>)`, inline: true }, + { name: 'Kanal', value: `<#${newMessage.channelId}>`, inline: true } + ) + .addFields( + { name: 'Vorher', value: oldMessage.content.substring(0, 1024), inline: false }, + { name: 'Nachher', value: newMessage.content.substring(0, 1024), inline: false } + ) + .setTimestamp(); + + await sendLog(newMessage.client, newMessage.guildId!, 'messages', embed); + }, +}; \ No newline at end of file diff --git a/src/structures/Database.ts b/src/structures/Database.ts index c887dd2..65131a4 100644 --- a/src/structures/Database.ts +++ b/src/structures/Database.ts @@ -27,6 +27,8 @@ export class DB { welcome_channel TEXT, welcome_message TEXT, goodbye_message TEXT, + log_channel TEXT, + log_events TEXT DEFAULT 'messages,roles,nicks,channels,moderation', warn_threshold INTEGER DEFAULT 3, warn_action TEXT DEFAULT 'ban', warn_mute_duration INTEGER DEFAULT 1800