Initial: Pixelpöbel Discord Bot
Some checks failed
Auto Build and Push Docker Image / build (push) Failing after 8s
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>
This commit is contained in:
293
AGENTS-BOT.md
Normal file
293
AGENTS-BOT.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# 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<string, Command> = 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> | 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<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
|
||||
|
||||
```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 |
|
||||
Reference in New Issue
Block a user