# 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. **Grouped Commands:** Admin/Owner 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 β”‚ β”‚ β”œβ”€β”€ admin.ts # All admin subcommands β”‚ β”‚ └── owner.ts # All owner subcommands β”‚ β”œβ”€β”€ events/ β”‚ β”‚ β”œβ”€β”€ ready.ts β”‚ β”‚ └── interactionCreate.ts β”‚ └── structures/ β”‚ β”œβ”€β”€ ExtendedClient.ts β”‚ β”œβ”€β”€ Command.ts β”‚ β”œβ”€β”€ Database.ts β”‚ β”œβ”€β”€ TwitchManager.ts β”‚ └── Deployer.ts β”œβ”€β”€ data/ # SQLite database (volume mounted) β”œβ”€β”€ package.json β”œβ”€β”€ tsconfig.json β”œβ”€β”€ Dockerfile β”œβ”€β”€ docker-compose.yml └── .env.example ``` ## πŸ“ 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, 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) ); ``` ### 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; } ``` **Deployer - Auto-Deploy (Option 3):** ```typescript export class Deployer { static async deployIfMissing(clientId: string, token: string) { const rest = new REST().setToken(token); try { const data = await rest.get(Routes.applicationCommands(clientId)); if (data.length > 0) { console.log('[DEPLOYER] Commands already registered. Skipping deployment.'); return data.length; } } catch (error) { // Commands don't exist yet } return this.deploy(clientId, token); } } ``` ### 4. Command Examples **Grouped Command (admin):** ```typescript data: new SlashCommandBuilder() .setName('admin') .setDescription('Admin-Commands') .addSubcommand(sub => sub.setName('kick').setDescription('Kickt einen Nutzer') .addUserOption(opt => opt.setName('target').setDescription('Nutzer').setRequired(true)) .addStringOption(opt => opt.setName('reason').setDescription('Grund'))) .addSubcommand(sub => sub.setName('warn').setDescription('Verwarnt einen Nutzer') .addUserOption(opt => opt.setName('target').setDescription('Nutzer').setRequired(true)) .addStringOption(opt => opt.setName('reason').setDescription('Grund').setRequired(true))) // ... more subcommands ``` ### 5. 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); } ``` ### 6. 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(...); } ``` ### 7. 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; } ``` ## πŸš€ 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`. ## πŸ“‹ Command Summary | Command | Subcommands | Permission | |---------|-------------|------------| | `/ping` | – | Public | | `/help` | – | Public | | `/twitch` | online, add, remove, list | Public/Admin | | `/admin` | kick, warn, unwarn, mute, unmute, ban, unban, config | Admin | | `/owner` | deploy, stats, servers | Owner |