Files
pixelpoebel/AGENTS-BOT.md
sarah 5c6188ea13
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 6s
Add: Timer and Trigger modules with help subcommands
- Add /timer command: add, remove, list, listall, help
- Add /trigger command: add, remove, list, help
- Add /admin help and /owner help subcommands
- Add /twitch help subcommand
- Add ReminderManager for timer notifications
- Add reminders table to database
- Update README.md and AGENTS-BOT.md documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:53:33 +01:00

9.4 KiB
Raw Blame History

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. 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
│   │       ├── admin.ts      # All admin subcommands
│   │       └── owner.ts      # All owner subcommands
│   ├── events/
│   │   ├── ready.ts
│   │   └── interactionCreate.ts
│   └── 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

📝 Implementation Steps

1. Project Setup

{
  "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"
  }
}
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true
  }
}

2. Database Structure

-- 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)
);

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

import { Client, Collection, GatewayIntentBits } from 'discord.js';
import { Command } from './Command.js';

export class ExtendedClient extends Client {
    public commands: Collection<string, Command> = new Collection();

    constructor() {
        super({
            intents: [
                GatewayIntentBits.Guilds,
                GatewayIntentBits.GuildMessages,
                GatewayIntentBits.MessageContent,
                GatewayIntentBits.GuildMembers,
            ],
        });
    }
}

Command Interface:

export interface Command {
    data: any;
    category: 'Owner' | 'Admin' | 'Public';
    execute: (interaction: any, client: any) => Promise<void> | void;
    cooldown?: number;
}

4. Timer System

Time Parsing:

// Duration parsing: "30s", "2h", "1d30m", "3wochen"
function parseDuration(input: string): number | null {
    const unitMs: Record<string, number> = {
        '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:

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. Auto-Response (Trigger) System

// 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);

6. Twitch Monitoring with Stream ID

// 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);
}

7. 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:

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(...);
}

8. Mute with Multiple Time Units

const unitSeconds: Record<string, number> = {
    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

# 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.

📋 Command Summary

Command Subcommands Permission
/ping Public
/help Public
/twitch online, add, remove, list, help Public/Admin
/trigger add, remove, list, help Public/Admin
/timer add, remove, list, listall, help Public/Admin
/admin kick, warn, unwarn, mute, unmute, ban, unban, config, help Admin
/owner deploy, stats, servers, help Owner