Optimize database, implement automatic command deployment, and improve project-wide type safety
Some checks failed
Auto Build and Push Docker Image / build (push) Failing after 14s
Some checks failed
Auto Build and Push Docker Image / build (push) Failing after 14s
This commit is contained in:
138
AGENTS-BOT.md
138
AGENTS-BOT.md
@@ -4,42 +4,42 @@ This document is intended for AI agents to understand and recreate the `pixelpö
|
||||
|
||||
## 🏗 Architecture Overview
|
||||
|
||||
The bot uses a **Modular Command & Event Loading** pattern with **ESM (ECMAScript Modules)** and **TypeScript**.
|
||||
The bot uses a **Modular Command & Event Loading** pattern with **ESM (ECMAScript Modules)** and **TypeScript**, emphasizing strict typing and performance.
|
||||
|
||||
### Key Design Patterns:
|
||||
1. **Extended Client Pattern:** Extend `discord.js` `Client` class to hold global state.
|
||||
1. **Extended Client Pattern:** Extend `discord.js` `Client` class to hold global state and a typed `DB` instance.
|
||||
2. **Dynamic Discovery:** `index.ts` uses `readdirSync` and dynamic `import()` to register commands/events.
|
||||
3. **Interface-Driven Commands:** All commands implement `Command` interface.
|
||||
4. **Optimized SQLite Database:** Persistent storage with `better-sqlite3`, utilizing **WAL (Write-Ahead Logging)** and **In-Memory Caching** for frequently accessed data.
|
||||
5. **Twitch Monitoring:** Periodic API polling using **Batch-Requests** (100 channels per request) with Stream ID tracking.
|
||||
6. **TwitchMonitor IRC:** TMI.js-based IRC chat monitoring for mod events.
|
||||
7. **Reminder System:** Periodic reminder checking with target_time tracking.
|
||||
8. **Auto-Response System:** Trigger word detection for automatic replies (cached).
|
||||
9. **Welcome/Goodbye System:** Guild member add/remove events with customizable messages.
|
||||
10. **Logging System:** Configurable event logging (messages, roles, moderation, etc.).
|
||||
11. **Role Selection System:** Self-service role assignment via select menus.
|
||||
12. **Grouped Commands:** Admin/Owner/Trigger/Timer commands grouped under subcommands.
|
||||
3. **Automatic Command Deployment:** Commands are automatically registered with Discord on startup via `Deployer.deployCommands()`, unless disabled via `AUTO_DEPLOY=false`.
|
||||
4. **Interface-Driven Commands:** All commands implement the `Command` interface.
|
||||
5. **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).
|
||||
6. **Twitch Monitoring:** Periodic API polling using **Batch-Requests** (100 channels per request) with Stream ID tracking and **database transactions** for status updates.
|
||||
7. **TwitchMonitor IRC:** TMI.js-based IRC chat monitoring for mod events, using **reusable WebhookClient instances** for efficiency.
|
||||
8. **Reminder System:** Periodic reminder checking with `target_time` tracking.
|
||||
9. **Auto-Response System:** Trigger word detection for automatic replies (cached).
|
||||
10. **Welcome/Goodbye System:** Guild member add/remove events with customizable messages.
|
||||
11. **Logging System:** Configurable event logging (messages, roles, moderation, etc.).
|
||||
12. **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
|
||||
│ ├── deploy-commands.ts # Deploy script for Discord
|
||||
│ ├── 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
|
||||
│ ├── ExtendedClient.ts # Typed client with DB property
|
||||
│ ├── Command.ts
|
||||
│ ├── Database.ts # Database with Caching logic
|
||||
│ ├── TwitchManager.ts # Batch Polling logic
|
||||
│ ├── TwitchMonitor.ts # IRC Monitoring logic
|
||||
│ ├── 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
|
||||
│ └── Deployer.ts # Centralized deployment logic
|
||||
├── data/ # SQLite database (volume mounted)
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
@@ -51,96 +51,96 @@ pixelpoebel/
|
||||
|
||||
## 📝 Implementation Steps
|
||||
|
||||
### 1. Database with Caching
|
||||
### 1. Database with Surgical Caching
|
||||
|
||||
The database uses WAL mode for performance and an in-memory Map for caching settings and triggers.
|
||||
The database uses WAL mode and foreign keys. Caching is surgical (clears specific guild entries when possible).
|
||||
|
||||
```typescript
|
||||
// src/structures/Database.ts
|
||||
export class DB {
|
||||
private static settingsCache = new Map<string, any>();
|
||||
private static triggersCache = new Map<string, any[]>();
|
||||
|
||||
|
||||
static init() {
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
// ... Table creation ...
|
||||
}
|
||||
|
||||
static getSettings(guildId: string) {
|
||||
if (this.settingsCache.has(guildId)) return this.settingsCache.get(guildId);
|
||||
const settings = db.prepare('SELECT * FROM guild_settings WHERE guild_id = ?').get(guildId);
|
||||
if (settings) this.settingsCache.set(guildId, settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
static run(query: string, ...params: any[]) {
|
||||
const result = db.prepare(query).run(...params);
|
||||
if (query.toLowerCase().includes('guild_settings') || query.toLowerCase().includes('auto_responses')) {
|
||||
this.settingsCache.clear();
|
||||
this.triggersCache.clear();
|
||||
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
|
||||
### 2. Twitch Batch Polling with Transactions
|
||||
|
||||
To stay within API limits and provide faster updates, the bot polls Twitch in batches of 100.
|
||||
Updates are collected and saved in a single transaction to reduce disk I/O.
|
||||
|
||||
```typescript
|
||||
// src/structures/TwitchManager.ts
|
||||
static async checkStreams(client: any) {
|
||||
const monitors = client.DB.all('SELECT * FROM twitch_monitors');
|
||||
const uniqueChannels = [...new Set(monitors.map(m => m.channel_name.toLowerCase()))];
|
||||
|
||||
for (let i = 0; i < uniqueChannels.length; i += 100) {
|
||||
const chunk = uniqueChannels.slice(i, i + 100);
|
||||
const query = chunk.map(name => `user_login=${encodeURIComponent(name)}`).join('&');
|
||||
// fetch https://api.twitch.tv/helix/streams?${query}
|
||||
// ... process results ...
|
||||
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 ...
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static startPolling(client: any) {
|
||||
setInterval(() => this.checkStreams(client), 2 * 60 * 1000); // 2 minute interval
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Event Logging Optimization
|
||||
### 3. Webhook Reuse in TwitchMonitor
|
||||
|
||||
All logging events use a centralized `sendLog` pattern that utilizes the `DB.getSettings()` cache.
|
||||
Avoid recreating `WebhookClient` instances for every message to improve performance.
|
||||
|
||||
```typescript
|
||||
async function sendLog(client: any, guildId: string, event: string, embed: EmbedBuilder) {
|
||||
const settings = client.DB.getSettings(guildId);
|
||||
if (!settings?.log_events || !settings?.log_channel) return;
|
||||
|
||||
const activeEvents = settings.log_events.split(',').filter(Boolean);
|
||||
if (!activeEvents.includes(event)) return;
|
||||
|
||||
const channel = client.guilds.cache.get(guildId)?.channels.cache.get(settings.log_channel);
|
||||
if (channel?.isTextBased()) await channel.send({ embeds: [embed] });
|
||||
// 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. TwitchMonitor IRC Chat Logging
|
||||
### 4. Automatic Deployment
|
||||
|
||||
**Overview:** Uses TMI.js to connect to Twitch IRC and logs moderation events (bans, timeouts, deletes) via webhooks.
|
||||
Commands are loaded into an array and deployed during the `index.ts` startup sequence.
|
||||
|
||||
**Key Feature:** FIFO Message Cache (`TwitchCache.ts`) allows restoring the text of deleted messages in the logs.
|
||||
```typescript
|
||||
// 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) |
|
||||
|--------|------------------|-------------------|
|
||||
| Metric | Old (Individual) | New (Batch/Cached/Transaction) |
|
||||
|--------|------------------|-------------------------------|
|
||||
| API Calls (500 channels) | 500 | 5 |
|
||||
| DB Access (Triggers) | Disk I/O per msg | RAM access |
|
||||
| DB Access (Polling) | Individual writes | Single Transaction |
|
||||
| Webhook Overhead | New Connection | Connection Reuse |
|
||||
| Notification Delay | ~5-7 min | ~2 min |
|
||||
|
||||
## ⚠️ Critical Constraints
|
||||
|
||||
1. **Batching:** Never exceed 100 channels per Twitch API request.
|
||||
2. **Caching:** Always invalidate cache (`clearCache`) when updating settings.
|
||||
3. **WAL Mode:** Ensure the `data/` directory has proper permissions for `.shm` and `.wal` files.
|
||||
4. **Webhook Safety:** Use `WebhookClient` for Twitch IRC logging to avoid Discord bot rate limits.
|
||||
1. **Transaction Safety:** Never call async functions (like `fetch` or `channel.send`) inside a synchronous `db.transaction()`.
|
||||
2. **Foreign Keys:** Always enable `foreign_keys = ON` to maintain data integrity.
|
||||
3. **Type Safety:** Use `ExtendedClient` instead of `any` for the client instance.
|
||||
4. **Webhook Reuse:** Map `WebhookClient` instances to their monitor keys to prevent leaks.
|
||||
|
||||
Reference in New Issue
Block a user