# AI Agent Guide: PixelpΓΆbel Discord Bot This document is intended for AI agents to understand and recreate the `pixelpΓΆbel` Discord bot. ## πŸ— Architecture Overview The bot uses a **Modular Command & Event Loading** pattern with **ESM (ECMAScript Modules)** and **TypeScript**. ### Key Design Patterns: 1. **Extended Client Pattern:** Extend `discord.js` `Client` class to hold global state. 2. **Dynamic Discovery:** `index.ts` uses `readdirSync` and dynamic `import()` to register commands/events. 3. **Interface-Driven Commands:** All commands implement `Command` interface. 4. **SQLite Database:** Persistent data storage with `better-sqlite3`. 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. **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 ``` pixelpoebel/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ index.ts # Entry point, loads commands/events β”‚ β”œβ”€β”€ deploy-commands.ts # Deploy script for Discord β”‚ β”œβ”€β”€ commands/ β”‚ β”‚ └── utility/ # All commands grouped here β”‚ β”‚ β”œβ”€β”€ ping.ts β”‚ β”‚ β”œβ”€β”€ help.ts β”‚ β”‚ β”œβ”€β”€ twitch.ts β”‚ β”‚ β”œβ”€β”€ trigger.ts # Auto-responses β”‚ β”‚ β”œβ”€β”€ 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 β”‚ β”‚ β”œβ”€β”€ messageCreate.ts # Auto-response triggers β”‚ β”‚ β”œβ”€β”€ messageDelete.ts # Deleted messages β”‚ β”‚ β”œβ”€β”€ messageUpdate.ts # Edited messages β”‚ β”‚ β”œβ”€β”€ guildMemberAdd.ts # Welcome messages β”‚ β”‚ β”œβ”€β”€ guildMemberRemove.ts # Goodbye messages β”‚ β”‚ β”œβ”€β”€ guildMemberUpdate.ts # Nickname/role changes β”‚ β”‚ β”œβ”€β”€ channelCreate.ts # Channel create β”‚ β”‚ └── channelDelete.ts # Channel delete β”‚ └── structures/ β”‚ β”œβ”€β”€ ExtendedClient.ts β”‚ β”œβ”€β”€ Command.ts β”‚ β”œβ”€β”€ Database.ts β”‚ β”œβ”€β”€ TwitchManager.ts β”‚ β”œβ”€β”€ ReminderManager.ts β”‚ └── Deployer.ts β”œβ”€β”€ data/ # SQLite database (volume mounted) β”œβ”€β”€ package.json β”œβ”€β”€ tsconfig.json β”œβ”€β”€ Dockerfile β”œβ”€β”€ docker-compose.yml β”œβ”€β”€ .env.example └── .gitattributes ``` ## πŸ“ Implementation Steps ### 1. Project Setup ```json { "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "nodemon --watch 'src/**/*.ts' --exec 'node --loader ts-node/esm' src/index.ts", "deploy": "node --loader ts-node/esm src/deploy-commands.ts" } } ``` ```json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "./dist", "rootDir": "./src", "strict": true } } ``` ### 2. Database Structure ```sql -- guild_settings CREATE TABLE guild_settings ( guild_id TEXT PRIMARY KEY, 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 ); -- mod_logs CREATE TABLE mod_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, guild_id TEXT, user_id TEXT, moderator_id TEXT, action TEXT, reason TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP ); -- command_stats CREATE TABLE command_stats ( command_name TEXT PRIMARY KEY, uses INTEGER DEFAULT 0 ); -- dm_users CREATE TABLE dm_users ( user_id TEXT PRIMARY KEY, username TEXT, last_seen DATETIME DEFAULT CURRENT_TIMESTAMP ); -- twitch_monitors CREATE TABLE twitch_monitors ( id INTEGER PRIMARY KEY AUTOINCREMENT, guild_id TEXT, channel_name TEXT, discord_channel_id TEXT, custom_message TEXT, last_status TEXT DEFAULT 'offline', last_stream_id TEXT, UNIQUE(guild_id, channel_name) ); -- auto_responses CREATE TABLE auto_responses ( id INTEGER PRIMARY KEY AUTOINCREMENT, guild_id TEXT, trigger_word TEXT, response_text TEXT, UNIQUE(guild_id, trigger_word) ); -- reminders CREATE TABLE 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 ); ``` ### 3. Key Components **ExtendedClient.ts:** ```typescript import { Client, Collection, GatewayIntentBits } from 'discord.js'; import { Command } from './Command.js'; export class ExtendedClient extends Client { public commands: Collection = new Collection(); constructor() { super({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers, ], }); } } ``` **Command Interface:** ```typescript export interface Command { data: any; category: 'Owner' | 'Admin' | 'Public'; execute: (interaction: any, client: any) => Promise | void; cooldown?: number; } ``` ### 4. Timer System **Time Parsing:** ```typescript // Duration parsing: "30s", "2h", "1d30m", "3wochen" function parseDuration(input: string): number | null { 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 }; // Parse and calculate total milliseconds } // Date parsing: "25.12.2024 14:30", "morgen 15:00" function parseDateTime(input: string): Date | null { // German date format, ISO format, or relative "tomorrow" } ``` **Reminder Polling:** ```typescript export class ReminderManager { static async checkReminders(client: any) { const now = new Date().toISOString(); const dueReminders = DB.all('SELECT * FROM reminders WHERE target_time <= ?', now); for (const reminder of dueReminders) { const guild = client.guilds.cache.get(reminder.guild_id); const channel = guild.channels.cache.get(reminder.channel_id); // Send notification and delete reminder } } static startPolling(client: any) { setInterval(() => this.checkReminders(client), 30 * 1000); } } ``` ### 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 **Event Handler (`messageCreate.ts`):** ```typescript // Listens for messages and checks against auto_responses // Ignores bots and DM messages const triggers = DB.all('SELECT * FROM auto_responses WHERE guild_id = ?', guildId); const contentLower = message.content.toLowerCase(); for (const trigger of triggers) { if (contentLower.includes(trigger.trigger_word.toLowerCase())) { await message.channel.send(trigger.response_text); return; } } ``` **Command (`trigger.ts`):** ```typescript // Add trigger (admin only) DB.run('INSERT INTO auto_responses (guild_id, trigger_word, response_text) VALUES (?, ?, ?)', guildId, triggerWord.toLowerCase(), responseText); // List triggers (public) DB.all('SELECT * FROM auto_responses WHERE guild_id = ? ORDER BY trigger_word ASC', guildId); // Remove trigger (admin only) DB.run('DELETE FROM auto_responses WHERE guild_id = ? AND trigger_word = ?', guildId, triggerWord); ``` ### 8. Twitch Monitoring with Stream ID ```typescript // Stream ID tracking prevents duplicate notifications const isNewStream = monitor.last_stream_id !== stream.id; const wasOffline = monitor.last_status === 'offline'; if (wasOffline || (monitor.last_status === 'online' && isNewStream)) { // Send notification DB.run('UPDATE twitch_monitors SET last_status = "online", last_stream_id = ? WHERE id = ?', stream.id, monitor.id); } ``` ### 9. Configurable Warn System **Database Schema:** - `warn_threshold`: Number of warns before action - `warn_action`: 'none', 'kick', 'mute', 'ban' - `warn_mute_duration`: Duration in seconds for mute **Auto-Action Logic:** ```typescript const warnCount = countResult?.count ?? 0; const warnThreshold = settings?.warn_threshold ?? 3; const warnAction = settings?.warn_action ?? 'ban'; if (warnCount >= warnThreshold) { if (warnAction === 'kick') await target.kick(...); else if (warnAction === 'mute') await target.timeout(duration, ...); else if (warnAction === 'ban') await target.ban(...); } ``` ### 10. Mute with Multiple Time Units ```typescript const unitSeconds: Record = { sec: 1, min: 60, hour: 60 * 60, day: 60 * 60 * 24, week: 60 * 60 * 24 * 7, month: 60 * 60 * 24 * 30, year: 60 * 60 * 24 * 365 }; if (unit === 'perm') { durationMs = null; // Permanent } else { durationMs = duration * unitSeconds[unit!] * 1000; } ``` ### 11. Purge Command (Message Deletion) **Subcommands:** - `purge amount <1-100>` - Delete X messages - `purge time ` - Delete messages from last X time - `purge user <1-100>` - Delete X messages from specific user - `purge all` - Delete all possible messages (max 100) **Discord API Constraints:** - Max 100 messages per bulk delete - Messages older than 14 days cannot be bulk deleted - Requires `ModerateMembers` permission ```typescript // Filter messages older than 14 days (Discord API limit) const twoWeeksAgo = Date.now() - 14 * 24 * 60 * 60 * 1000; const filtered = messages.filter((m: any) => m.createdTimestamp > twoWeeksAgo); await channel.bulkDelete(filtered, true); ``` **Logging:** Purge actions are logged to the moderation log channel with event type `messages`. ## πŸš€ Build & Deploy ```bash # Build npm run build # Deploy Commands npm run deploy # Run npm start # Docker docker-compose up -d --build ``` ## ⚠️ Critical Constraints 1. **Environment Variables:** Always use `process.env`, never hardcode. 2. **Type Safety:** Use `any` for command `data` and `execute` parameters (compatibility). 3. **Interaction States:** Check `interaction.replied` before replying. 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 | Command | Subcommands | Permission | |---------|-------------|------------| | `/ping` | – | Public | | `/help` | – | Public | | `/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, purge, config, help | Admin | | `/owner` | deploy, stats, servers, help | Owner |