Some checks failed
Auto Build and Push Docker Image / build (push) Failing after 8s
- Modular Discord Bot mit TypeScript und discord.js - Grouped Commands (/admin, /owner subcommands) - SQLite Datenbank mit Persistenz - Twitch Monitoring mit Stream-ID Tracking - Konfigurierbares Warn-System (Auto-Kick/Mute/Ban) - Mute mit verschiedenen Zeiteinheiten (sec, min, hour, day, week, month, year, perm) - Auto-Deploy beim Start (Option 3) - Docker Support Features: - /ping, /help, /twitch (online, add, remove, list) - /admin (kick, warn, unwarn, mute, unmute, ban, unban, config) - /owner (deploy, stats, servers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.9 KiB
7.9 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.
Key Design Patterns:
- Extended Client Pattern: Extend
discord.jsClientclass to hold global state. - Dynamic Discovery:
index.tsusesreaddirSyncand dynamicimport()to register commands/events. - Interface-Driven Commands: All commands implement
Commandinterface. - SQLite Database: Persistent data storage with
better-sqlite3. - Twitch Monitoring: Periodic API polling with Stream ID tracking.
- 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
{
"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)
);
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;
}
Deployer - Auto-Deploy (Option 3):
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):
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
// 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 actionwarn_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(...);
}
7. 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
- Environment Variables: Always use
process.env, never hardcode. - Type Safety: Use
anyfor commanddataandexecuteparameters (compatibility). - Interaction States: Check
interaction.repliedbefore replying. - Twitch API: Max 100 channels per request, batch properly.
- 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 |