All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 19s
6.5 KiB
6.5 KiB
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, emphasizing strict typing and performance.
Key Design Patterns:
- Extended Client Pattern: Extend
discord.jsClientclass to hold global state and a typedDBinstance. - Dynamic Discovery:
index.tsusesreaddirSyncand dynamicimport()to register commands/events. - Automatic Command Deployment: Commands are automatically registered with Discord on startup via
Deployer.deployCommands(), unless disabled viaAUTO_DEPLOY=false. - Interface-Driven Commands: All commands implement the
Commandinterface. - Optimized SQLite Database: Persistent storage with
better-sqlite3, utilizing WAL (Write-Ahead Logging), Foreign Key support, and surgical In-Memory Caching (clearing specific guild data on updates). - Twitch Monitoring: Periodic API polling using Batch-Requests (100 channels per request) with Stream ID tracking and database transactions for status updates.
- TwitchMonitor IRC: TMI.js-based IRC chat monitoring for mod events, using reusable WebhookClient instances for efficiency.
- Reminder System: Periodic reminder checking with
target_timetracking. - Auto-Response System: Trigger word detection for automatic replies (cached).
- Welcome/Goodbye System: Guild member add/remove events with customizable messages.
- Logging System: Configurable event logging (messages, roles, moderation, etc.).
- Role Selection System: Self-service role assignment via select menus with support for exclusive categories and max-role limits.
📁 Project Structure
pixelpoebel/
├── src/
│ ├── index.ts # Entry point, loads commands/events/auto-deploy
│ ├── deploy-commands.ts # Manual deploy script for Discord
│ ├── commands/
│ │ └── utility/ # All commands grouped here
│ ├── events/
│ │ ├── ... # All Discord event handlers
│ └── structures/
│ ├── ExtendedClient.ts # Typed client with DB property
│ ├── Command.ts
│ ├── Database.ts # Database with Transaction & Surgical Caching
│ ├── TwitchManager.ts # Batch Polling & Transaction logic
│ ├── TwitchMonitor.ts # IRC Monitoring & Webhook reuse
│ ├── TwitchCache.ts # IRC Message FIFO Cache
│ ├── ReminderManager.ts
│ └── Deployer.ts # Centralized deployment logic
├── data/ # SQLite database (volume mounted)
├── package.json
├── tsconfig.json
├── Dockerfile
├── docker-compose.yml
├── .env.example
└── .gitattributes
📝 Implementation Steps
1. Database with Surgical Caching
The database uses WAL mode and foreign keys. Caching is surgical (clears specific guild entries when possible).
// src/structures/Database.ts
export class DB {
private static settingsCache = new Map<string, any>();
static init() {
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
// ... Table creation ...
}
static run(query: string, ...params: any[]) {
const result = db.prepare(query).run(...params);
if (query.toLowerCase().includes('guild_settings')) {
const guildId = params.find(p => typeof p === 'string' && /^\d{17,20}$/.test(p));
if (guildId) this.settingsCache.delete(guildId);
else this.settingsCache.clear();
}
return result;
}
static transaction<T>(fn: () => T): T {
return db.transaction(fn)();
}
}
2. Twitch Batch Polling with Transactions
Updates are collected and saved in a single transaction to reduce disk I/O.
// src/structures/TwitchManager.ts
static async checkStreams(client: ExtendedClient) {
// ... fetch streams ...
const updates = []; // collect updates here
if (updates.length > 0) {
client.DB.transaction(() => {
for (const update of updates) {
// ... client.DB.run update ...
}
});
}
}
3. Webhook Reuse in TwitchMonitor
Avoid recreating WebhookClient instances for every message to improve performance.
// src/structures/TwitchMonitor.ts
private async sendWebhook(webhookUrl: string, data: any, monitorKey?: string) {
let webhook = monitorKey ? this.webhookClients.get(monitorKey) : new WebhookClient({ url: webhookUrl });
await webhook.send({ embeds: [embed] });
}
4. Automatic Deployment
Commands are loaded into an array and deployed during the index.ts startup sequence.
// src/index.ts
const commandData = [];
// ... while loading commands ...
commandData.push(command.data.toJSON());
if (process.env.AUTO_DEPLOY !== 'false') {
await Deployer.deployCommands(process.env.CLIENT_ID, process.env.DISCORD_TOKEN, commandData);
}
🚀 Performance Benchmarks (approx.)
| Metric | Old (Individual) | New (Batch/Cached/Transaction) |
|---|---|---|
| API Calls (500 channels) | 500 | 5 |
| DB Access (Polling) | Individual writes | Single Transaction |
| Webhook Overhead | New Connection | Connection Reuse |
| Notification Delay | ~5-7 min | ~2 min |
⚠️ Critical Constraints
- Transaction Safety: Never call async functions (like
fetchorchannel.send) inside a synchronousdb.transaction(). - Foreign Keys: Always enable
foreign_keys = ONto maintain data integrity. - Type Safety: Use
ExtendedClientinstead ofanyfor the client instance. - Webhook Reuse: Map
WebhookClientinstances to their monitor keys to prevent leaks. - Partial Message Handling: In
messageDeleteandmessageUpdateevents, always check for nullauthorproperty to avoid runtime crashes on uncached partial messages. - Centralized Settings Cache: Always use
client.DB.getSettings(guildId)instead of raw SQLite select queries forguild_settingsto leverage central caching and automatic default generation. - Category Role Selection Limits: Do not count general guild roles when checking role selection limits; filter the user's role list using only the specific role IDs registered under that category.