Compare commits

...

2 Commits

Author SHA1 Message Date
6b259ddaa2 Docs: Update README.md and AGENTS-BOT.md
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 6s
Add Welcome/Goodbye and Logging modules to documentation.
Add new events structure, database fields, and command summary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 10:42:32 +01:00
610cbf7b4d Add: Logging module with moderation events
Commands:
- /log setchannel, enable, disable, list, help

Events logged:
- messages: deleted/edited messages
- roles: role changes
- nicks: nickname changes
- channels: channel create/delete
- warns, mutes, kicks, bans: moderation actions

Moderation logging integrated into admin.ts for all
mod actions (warn, mute, kick, ban).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 10:35:40 +01:00
9 changed files with 590 additions and 19 deletions

View File

@@ -14,7 +14,9 @@ The bot uses a **Modular Command & Event Loading** pattern with **ESM (ECMAScrip
5. **Twitch Monitoring:** Periodic API polling with Stream ID tracking.
6. **Reminder System:** Periodic reminder checking with target_time tracking.
7. **Auto-Response System:** Trigger word detection for automatic replies.
8. **Grouped Commands:** Admin/Owner/Trigger/Timer commands grouped under subcommands.
8. **Welcome/Goodbye System:** Guild member add/remove events with customizable messages.
10. **Logging System:** Configurable event logging (messages, roles, moderation, etc.).
11. **Grouped Commands:** Admin/Owner/Trigger/Timer commands grouped under subcommands.
## 📁 Project Structure
@@ -29,12 +31,20 @@ pixelpoebel/
│ │ ├── help.ts
│ │ ├── twitch.ts
│ │ ├── trigger.ts # Auto-responses
│ │ ├── timer.ts # Reminders
│ │ ├── admin.ts # All admin subcommands
│ │ ── owner.ts # All owner subcommands
│ │ ├── timer.ts # Reminders
│ │ ├── welcome.ts # Welcome/Goodbye messages
│ │ ── log.ts # Event logging
│ │ ├── admin.ts # All admin subcommands
│ │ └── owner.ts # All owner subcommands
│ ├── events/
│ │ ├── ready.ts
│ │ ── interactionCreate.ts
│ │ ── interactionCreate.ts
│ │ ├── guildMemberAdd.ts # Welcome messages
│ │ ├── guildMemberRemove.ts # Goodbye messages
│ │ ├── guildMemberUpdate.ts # Nickname/role changes
│ │ ├── messageDelete.ts # Deleted messages
│ │ ├── messageUpdate.ts # Edited messages
│ │ └── channelEvent.ts # Channel create/delete
│ └── structures/
│ ├── ExtendedClient.ts
│ ├── Command.ts
@@ -47,7 +57,8 @@ pixelpoebel/
├── tsconfig.json
├── Dockerfile
├── docker-compose.yml
── .env.example
── .env.example
└── .gitattributes
```
## 📝 Implementation Steps
@@ -88,6 +99,10 @@ CREATE TABLE guild_settings (
prefix TEXT DEFAULT '!',
mod_log_channel TEXT,
welcome_channel TEXT,
welcome_message TEXT,
goodbye_message TEXT,
log_channel TEXT,
log_events TEXT DEFAULT 'messages,roles,nicks,channels,warns,mutes,kicks,bans',
warn_threshold INTEGER DEFAULT 3,
warn_action TEXT DEFAULT 'ban',
warn_mute_duration INTEGER DEFAULT 1800
@@ -227,7 +242,48 @@ export class ReminderManager {
}
```
### 5. Auto-Response (Trigger) System
### 5. Welcome/Goodbye System
**Variables in messages:** `{user}`, `{username}`, `{server}`, `{membercount}`
**Auto-use Discord system channel if no custom channel set:**
```typescript
const channelId = settings?.welcome_channel || member.guild.systemChannelId;
```
### 6. Logging System
**Events that can be logged:**
- `messages` - Deleted and edited messages
- `roles` - Role changes
- `nicks` - Nickname changes
- `channels` - Channel create/delete
- `warns` - User warnings
- `mutes` - User mutes
- `kicks` - User kicks
- `bans` - User bans
**Log moderation action:**
```typescript
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;
await (channel as any).send({ embeds: [embed] });
}
```
### 7. Auto-Response (Trigger) System
```typescript
// Add trigger (admin only)
@@ -241,7 +297,7 @@ DB.all('SELECT * FROM auto_responses WHERE guild_id = ? ORDER BY trigger_word AS
DB.run('DELETE FROM auto_responses WHERE guild_id = ? AND trigger_word = ?', guildId, triggerWord);
```
### 6. Twitch Monitoring with Stream ID
### 8. Twitch Monitoring with Stream ID
```typescript
// Stream ID tracking prevents duplicate notifications
@@ -255,7 +311,7 @@ if (wasOffline || (monitor.last_status === 'online' && isNewStream)) {
}
```
### 7. Configurable Warn System
### 9. Configurable Warn System
**Database Schema:**
- `warn_threshold`: Number of warns before action
@@ -275,7 +331,7 @@ if (warnCount >= warnThreshold) {
}
```
### 8. Mute with Multiple Time Units
### 10. Mute with Multiple Time Units
```typescript
const unitSeconds: Record<string, number> = {
@@ -319,6 +375,7 @@ docker-compose up -d --build
4. **Twitch API:** Max 100 channels per request, batch properly.
5. **Deploy:** Commands NOT auto-registered on start use `/owner deploy`.
6. **Ephemeral Replies:** Use `flags: [MessageFlags.Ephemeral]` instead of deprecated `ephemeral: true`.
7. **Line Endings:** Use `.gitattributes` to ensure LF line endings for consistency.
## 📋 Command Summary
@@ -326,8 +383,10 @@ docker-compose up -d --build
|---------|-------------|------------|
| `/ping` | | Public |
| `/help` | | Public |
| `/twitch` | online, add, remove, list, help | Public/Admin |
| `/twitch` | online, add, remove, list, listall, help | Public/Admin |
| `/trigger` | add, remove, list, help | Public/Admin |
| `/timer` | add, remove, list, listall, help | Public/Admin |
| `/welcome` | setchannel, add, remove, list, help | Admin |
| `/log` | setchannel, enable, disable, list, help | Admin |
| `/admin` | kick, warn, unwarn, mute, unmute, ban, unban, config, help | Admin |
| `/owner` | deploy, stats, servers, help | Owner |

View File

@@ -59,10 +59,8 @@ npm run deploy
- `/twitch online <channel>` Twitch-Status
- `/twitch list` Überwachte Kanäle
- `/twitch help` Twitch-Hilfe
### Trigger (Auto-Antworten)
- `/trigger add <wort> <antwort>` Trigger hinzufügen (Mod)
- `/trigger remove <wort>` Trigger entfernen (Mod)
- `/timer list` Timer dieses Kanals
- `/timer help` Timer-Hilfe
- `/trigger list` Alle Trigger anzeigen
- `/trigger help` Trigger-Hilfe
@@ -73,9 +71,21 @@ npm run deploy
- `/timer listall` Alle Timer (Mod)
- `/timer help` Timer-Hilfe
### Trigger (Auto-Antworten)
- `/trigger add <wort> <antwort>` Trigger hinzufügen (Mod)
- `/trigger remove <wort>` Trigger entfernen (Mod)
- `/trigger list` Alle Trigger anzeigen
- `/trigger help` Trigger-Hilfe
### Twitch
- `/twitch online <kanal>` Prüft ob Kanal online ist
- `/twitch add <streamer1, streamer2, ...>` Streamer überwachen (Mod)
- `/twitch remove <streamer1, streamer2, ...>` Streamer entfernen (Mod)
- `/twitch list` Überwachte Kanäle
- `/twitch listall` Alle überwachten Streamer (Mod)
- `/twitch help` Twitch-Hilfe
### Admin
- `/twitch add <channel>` Kanal überwachen
- `/twitch remove <channel>` Kanal entfernen
- `/admin kick <user>` Nutzer kicken
- `/admin warn <user> <reason>` Nutzer verwarnen
- `/admin unwarn <user>` Letzte Warnung entfernen
@@ -98,9 +108,10 @@ npm run deploy
- ✅ TypeScript
- ✅ SQLite Database
- ✅ Twitch Monitoring
- ✅ Auto-Deploy beim Start
- ✅ Moderation Tools
- ✅ Konfigurierbares Warn-System
- ✅ Auto-Antworten (Trigger)
- ✅ Timer & Erinnerungen
- ✅ Welcome/Goodbye Messages
- ✅ Event Logging
- ✅ Docker Support

View File

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

175
src/commands/utility/log.ts Normal file
View File

@@ -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 <event>`', value: 'Aktiviert ein Event zum Loggen (Mod).', inline: false },
{ name: '`/log disable <event>`', 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;

View File

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

View File

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

View File

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

View File

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

View File

@@ -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