mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 16:14:12 +00:00
- Telegram media bot implementation plan and prompt - sensualmoment.de design prototypes (color scheme, prototype, design doc) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
749 lines
22 KiB
Markdown
749 lines
22 KiB
Markdown
# Telegram Media Upload Bot – Payload CMS Integration
|
||
|
||
## Kontext
|
||
|
||
- **Projektverzeichnis:** `/home/payload/telegram-media-bot` (auf sv-payload, LXC 700, IP: 10.10.181.100)
|
||
- **Alternative:** Neues GitHub Repository `complexcaresolutions/telegram-media-bot`
|
||
- **Tech-Stack:** Node.js 22 LTS, TypeScript, grammy (Telegram Bot Framework), PM2
|
||
- **Ziel-System:** Payload CMS Multi-Tenant (Production: `https://cms.c2sgmbh.de`, Staging: `https://pl.porwoll.tech`)
|
||
- **Betriebssystem:** Debian/Ubuntu (LXC Container im Proxmox-Cluster)
|
||
- **Package Manager:** pnpm
|
||
|
||
---
|
||
|
||
## Projektbeschreibung
|
||
|
||
Erstelle einen Telegram Bot, der es autorisierten Benutzern ermöglicht, Bilder direkt aus dem Telegram-Chat in die Media Collection des Payload CMS hochzuladen. Der Bot authentifiziert sich gegen die Payload REST-API, empfängt Bilder über die Telegram Bot API, lädt sie von den Telegram-Servern herunter und leitet sie per `multipart/form-data` an den Payload Media-Endpoint weiter. Dabei wird die Multi-Tenant-Isolation strikt eingehalten.
|
||
|
||
---
|
||
|
||
## Technische Referenzen
|
||
|
||
### Payload CMS REST-API
|
||
|
||
**Base URLs:**
|
||
- Production: `https://cms.c2sgmbh.de/api`
|
||
- Staging: `https://pl.porwoll.tech/api`
|
||
- Swagger UI: `https://cms.c2sgmbh.de/api/docs`
|
||
|
||
**Authentifizierung – Login:**
|
||
```bash
|
||
curl -X POST "https://cms.c2sgmbh.de/api/users/login" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"email": "admin@example.com",
|
||
"password": "your-password"
|
||
}'
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"message": "Auth Passed",
|
||
"user": { "id": 1, "email": "admin@example.com", "isSuperAdmin": true },
|
||
"token": "eyJhbGciOiJIUzI1NiIs..."
|
||
}
|
||
```
|
||
|
||
**Token verwenden:**
|
||
```bash
|
||
curl "https://cms.c2sgmbh.de/api/media" \
|
||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..."
|
||
```
|
||
|
||
### Media Upload Endpoint
|
||
|
||
```bash
|
||
curl -X POST "https://cms.c2sgmbh.de/api/media" \
|
||
-H "Authorization: JWT <token>" \
|
||
-F "file=@/path/to/image.jpg" \
|
||
-F "alt=Beschreibungstext" \
|
||
-F "tenant=1"
|
||
```
|
||
|
||
**WICHTIG:** Das `tenant`-Feld ist **Pflicht** bei jedem Upload. Ohne korrekte Tenant-Zuordnung gibt die API 403 Forbidden zurück. Die Tenant-Isolation ist ein Kernprinzip des gesamten Systems.
|
||
|
||
### Verfügbare Tenants
|
||
|
||
| ID | Name | Slug | Domain |
|
||
|----|------|------|--------|
|
||
| 1 | porwoll.de | porwoll | porwoll.de |
|
||
| 4 | Complex Care Solutions GmbH | c2s | complexcaresolutions.de |
|
||
| 5 | Gunshin | gunshin | gunshin.de |
|
||
| (weitere Tenants ggf. dynamisch über API abrufen) |
|
||
|
||
### Media Collection – Automatische Bildverarbeitung
|
||
|
||
Payload generiert automatisch folgende responsive Größen beim Upload:
|
||
|
||
| Size | Auflösung | Format |
|
||
|------|-----------|--------|
|
||
| thumbnail | 150×150 | Original + AVIF |
|
||
| small | 300×300 | Original + AVIF |
|
||
| medium | 600×600 | Original + AVIF |
|
||
| large | 1200×1200 | Original + AVIF |
|
||
| xlarge | 1920×1920 | Original + AVIF |
|
||
| 2k | 2560×2560 | Original + AVIF |
|
||
| og | 1200×630 | Original (Social Media) |
|
||
|
||
→ Der Bot muss sich NICHT um Bildgrößen kümmern. Payload erledigt das serverseitig.
|
||
|
||
### Rate Limiting (Payload API)
|
||
|
||
| Limiter | Limit | Fenster |
|
||
|---------|-------|---------|
|
||
| publicApiLimiter | 60 Requests | 1 Minute |
|
||
| authLimiter | 5 Requests | 15 Minuten |
|
||
|
||
→ Der Bot sollte Token cachen und nicht bei jedem Upload neu einloggen.
|
||
|
||
---
|
||
|
||
## Aufgaben
|
||
|
||
### 1. Projekt-Setup
|
||
|
||
#### 1.1 Projektstruktur erstellen
|
||
|
||
```
|
||
telegram-media-bot/
|
||
├── src/
|
||
│ ├── index.ts # Entry Point, Bot-Start
|
||
│ ├── bot.ts # Grammy Bot-Instanz + Handler
|
||
│ ├── config.ts # Environment-Konfiguration (typisiert)
|
||
│ ├── payload/
|
||
│ │ ├── client.ts # Payload API Client (Login, Token-Management)
|
||
│ │ └── media.ts # Media Upload Logik
|
||
│ ├── telegram/
|
||
│ │ ├── handlers.ts # Message Handler (Photo, Document, Commands)
|
||
│ │ └── keyboards.ts # Inline Keyboards (Tenant-Auswahl etc.)
|
||
│ ├── middleware/
|
||
│ │ └── auth.ts # User-Whitelist Middleware
|
||
│ └── utils/
|
||
│ ├── logger.ts # Logging Utility
|
||
│ └── download.ts # Telegram File Download Helper
|
||
├── .env.example # Template für Environment Variables
|
||
├── .env # (gitignored) Actual Config
|
||
├── package.json
|
||
├── tsconfig.json
|
||
├── ecosystem.config.cjs # PM2 Konfiguration
|
||
├── .gitignore
|
||
└── README.md
|
||
```
|
||
|
||
**Akzeptanzkriterien:**
|
||
- [ ] `pnpm init` ausgeführt
|
||
- [ ] TypeScript konfiguriert (strict mode)
|
||
- [ ] Alle Dependencies installiert
|
||
- [ ] `.gitignore` enthält `node_modules`, `.env`, `dist`
|
||
|
||
#### 1.2 Dependencies installieren
|
||
|
||
```bash
|
||
pnpm add grammy dotenv
|
||
pnpm add -D typescript @types/node tsx
|
||
```
|
||
|
||
**Hinweis:** `grammy` ist das bevorzugte Telegram Bot Framework (modern, TypeScript-first, aktiv maintained). Alternativ wäre `node-telegram-bot-api` möglich, aber `grammy` hat bessere TypeScript-Unterstützung.
|
||
|
||
#### 1.3 TypeScript Konfiguration
|
||
|
||
**Datei:** `tsconfig.json`
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"target": "ES2022",
|
||
"module": "ESNext",
|
||
"moduleResolution": "bundler",
|
||
"outDir": "./dist",
|
||
"rootDir": "./src",
|
||
"strict": true,
|
||
"esModuleInterop": true,
|
||
"skipLibCheck": true,
|
||
"forceConsistentCasingInFileNames": true,
|
||
"resolveJsonModule": true,
|
||
"declaration": true,
|
||
"declarationMap": true,
|
||
"sourceMap": true
|
||
},
|
||
"include": ["src/**/*"],
|
||
"exclude": ["node_modules", "dist"]
|
||
}
|
||
```
|
||
|
||
#### 1.4 Package.json Scripts
|
||
|
||
```json
|
||
{
|
||
"name": "telegram-media-bot",
|
||
"version": "1.0.0",
|
||
"type": "module",
|
||
"scripts": {
|
||
"dev": "tsx watch src/index.ts",
|
||
"build": "tsc",
|
||
"start": "node dist/index.js",
|
||
"lint": "tsc --noEmit"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Konfiguration
|
||
|
||
#### 2.1 Environment Variables
|
||
|
||
**Datei:** `.env.example`
|
||
|
||
```env
|
||
# Telegram Bot
|
||
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
|
||
|
||
# Zugelassene Telegram User-IDs (kommasepariert)
|
||
ALLOWED_USER_IDS=123456789,987654321
|
||
|
||
# Payload CMS
|
||
PAYLOAD_API_URL=https://cms.c2sgmbh.de/api
|
||
PAYLOAD_ADMIN_EMAIL=admin@example.com
|
||
PAYLOAD_ADMIN_PASSWORD=your-secure-password
|
||
|
||
# Standard-Tenant (wird verwendet wenn kein Tenant gewählt)
|
||
DEFAULT_TENANT_ID=1
|
||
|
||
# Logging
|
||
LOG_LEVEL=info
|
||
|
||
# Node Environment
|
||
NODE_ENV=production
|
||
```
|
||
|
||
#### 2.2 Typisierte Config
|
||
|
||
**Datei:** `src/config.ts`
|
||
|
||
```typescript
|
||
// Alle Environment Variables typisiert laden und validieren.
|
||
// Bei fehlenden Pflicht-Variablen: Prozess mit Error beenden.
|
||
// ALLOWED_USER_IDS als number[] parsen (kommasepariert).
|
||
// Validation beim Import, nicht lazy.
|
||
|
||
interface Config {
|
||
telegram: {
|
||
botToken: string;
|
||
allowedUserIds: number[];
|
||
};
|
||
payload: {
|
||
apiUrl: string;
|
||
email: string;
|
||
password: string;
|
||
};
|
||
defaultTenantId: number;
|
||
logLevel: string;
|
||
nodeEnv: string;
|
||
}
|
||
```
|
||
|
||
**Akzeptanzkriterien:**
|
||
- [ ] Config wird beim Start validiert
|
||
- [ ] Fehlende Pflicht-Variablen = sofortiger Exit mit klarer Fehlermeldung
|
||
- [ ] `ALLOWED_USER_IDS` wird als `number[]` geparst
|
||
|
||
---
|
||
|
||
### 3. Payload API Client
|
||
|
||
#### 3.1 Authentifizierung mit Token-Caching
|
||
|
||
**Datei:** `src/payload/client.ts`
|
||
|
||
Implementiere einen Payload API Client mit folgender Logik:
|
||
|
||
1. **Login:** `POST /api/users/login` mit Email/Password
|
||
2. **Token speichern** (In-Memory, NICHT auf Disk)
|
||
3. **Token-Expiry tracken:** JWT decodieren (ohne Verifikation, nur Payload lesen), `exp` Feld prüfen
|
||
4. **Auto-Refresh:** Vor jedem API-Call prüfen ob Token noch gültig (mit 5 Min. Buffer). Falls abgelaufen → neu einloggen.
|
||
5. **Retry-Logik:** Bei 401-Response einmal neu einloggen und Request wiederholen.
|
||
|
||
```typescript
|
||
class PayloadClient {
|
||
private token: string | null = null;
|
||
private tokenExpiry: number = 0;
|
||
|
||
async getToken(): Promise<string> { /* ... */ }
|
||
async login(): Promise<void> { /* ... */ }
|
||
async uploadMedia(file: Buffer, filename: string, options: MediaUploadOptions): Promise<MediaResponse> { /* ... */ }
|
||
async listMedia(tenantId: number, limit?: number): Promise<MediaListResponse> { /* ... */ }
|
||
async deleteMedia(mediaId: number): Promise<void> { /* ... */ }
|
||
}
|
||
|
||
interface MediaUploadOptions {
|
||
alt: string;
|
||
tenantId: number;
|
||
caption?: string;
|
||
}
|
||
|
||
interface MediaResponse {
|
||
id: number;
|
||
url: string;
|
||
filename: string;
|
||
alt: string;
|
||
sizes: Record<string, { url: string; width: number; height: number }>;
|
||
}
|
||
```
|
||
|
||
**WICHTIG – Multi-Tenant:**
|
||
- Jeder API-Call der Media betrifft MUSS `tenant` als Feld mitsenden
|
||
- Bei Reads: `?where[tenant][equals]=<ID>` als Query-Parameter
|
||
- Bei Writes: `tenant` im Request-Body
|
||
|
||
**Akzeptanzkriterien:**
|
||
- [ ] Login funktioniert, Token wird gecacht
|
||
- [ ] Token wird vor Ablauf automatisch erneuert
|
||
- [ ] 401 Response triggert Re-Login + Retry
|
||
- [ ] Alle API-Calls enthalten korrekte Tenant-Filterung
|
||
|
||
#### 3.2 Media Upload Funktion
|
||
|
||
**Datei:** `src/payload/media.ts`
|
||
|
||
```typescript
|
||
// Upload einer Bilddatei an POST /api/media
|
||
// Content-Type: multipart/form-data
|
||
//
|
||
// Felder:
|
||
// file: Die Bilddatei (Buffer) mit korrektem filename + mimetype
|
||
// alt: Alt-Text (string)
|
||
// tenant: Tenant-ID (number)
|
||
//
|
||
// Die Payload API generiert automatisch alle responsiven Größen.
|
||
// Response enthält die vollständige Media-Resource inkl. aller Size-URLs.
|
||
```
|
||
|
||
Für den multipart Upload verwende die native `FormData` API (ab Node.js 18+ verfügbar) oder `form-data` Package. **Kein axios nötig** – nutze native `fetch`.
|
||
|
||
**Akzeptanzkriterien:**
|
||
- [ ] Upload funktioniert mit JPG, PNG, WebP, AVIF
|
||
- [ ] Alt-Text wird korrekt gesetzt
|
||
- [ ] Tenant-Zuordnung funktioniert
|
||
- [ ] Response wird korrekt geparst
|
||
|
||
---
|
||
|
||
### 4. Telegram Bot
|
||
|
||
#### 4.1 Bot-Instanz und Middleware
|
||
|
||
**Datei:** `src/bot.ts`
|
||
|
||
```typescript
|
||
import { Bot, Context, session } from 'grammy';
|
||
|
||
interface SessionData {
|
||
selectedTenantId: number;
|
||
selectedTenantName: string;
|
||
}
|
||
|
||
type BotContext = Context & { session: SessionData };
|
||
|
||
// Bot erstellen mit Grammy
|
||
// Session-Middleware für Tenant-Auswahl pro User
|
||
// Auth-Middleware für User-Whitelist
|
||
```
|
||
|
||
#### 4.2 Auth Middleware (User-Whitelist)
|
||
|
||
**Datei:** `src/middleware/auth.ts`
|
||
|
||
```typescript
|
||
// Middleware die prüft ob ctx.from.id in ALLOWED_USER_IDS enthalten ist.
|
||
// Falls nicht: Antwort "⛔ Du bist nicht autorisiert, diesen Bot zu verwenden."
|
||
// und ctx.next() NICHT aufrufen.
|
||
//
|
||
// WICHTIG: Auch in Gruppen-Chats nur auf autorisierte User reagieren.
|
||
```
|
||
|
||
**Akzeptanzkriterien:**
|
||
- [ ] Nicht-autorisierte User erhalten Fehlermeldung
|
||
- [ ] Autorisierte User können alle Funktionen nutzen
|
||
- [ ] Middleware blockiert alle Handler, nicht nur einzelne
|
||
|
||
#### 4.3 Command Handler
|
||
|
||
**Datei:** `src/telegram/handlers.ts`
|
||
|
||
Implementiere folgende Befehle:
|
||
|
||
**`/start`**
|
||
- Begrüßungsnachricht mit Kurzanleitung
|
||
- Zeige aktuell gewählten Tenant
|
||
- Text:
|
||
```
|
||
🤖 Payload Media Upload Bot
|
||
|
||
Schicke mir ein Bild und ich lade es in die Payload CMS Media-Bibliothek hoch.
|
||
|
||
📌 Aktueller Tenant: [Tenant-Name]
|
||
📋 Befehle:
|
||
/tenant - Tenant wechseln
|
||
/list - Letzte 5 Uploads anzeigen
|
||
/status - Bot- und API-Status
|
||
/help - Hilfe anzeigen
|
||
```
|
||
|
||
**`/tenant`**
|
||
- Zeige Inline-Keyboard mit allen verfügbaren Tenants
|
||
- Tenants dynamisch von der API laden: `GET /api/tenants` (Auth erforderlich)
|
||
- Nach Auswahl: Tenant in Session speichern
|
||
- Bestätigung: `✅ Tenant gewechselt zu: [Name] (ID: [ID])`
|
||
|
||
**`/list`**
|
||
- Zeige die letzten 5 hochgeladenen Medien des aktuellen Tenants
|
||
- API: `GET /api/media?where[tenant][equals]=<ID>&sort=-createdAt&limit=5`
|
||
- Ausgabe als Liste mit Thumbnail-URL, Dateiname, Datum
|
||
|
||
**`/status`**
|
||
- Zeige:
|
||
- Bot-Uptime
|
||
- Payload API erreichbar? (Quick-Check: `GET /api/users/me`)
|
||
- Aktueller Tenant
|
||
- Token-Status (gültig bis...)
|
||
|
||
**`/help`**
|
||
- Ausführliche Hilfe mit allen Befehlen und Nutzungshinweisen
|
||
|
||
#### 4.4 Photo Handler (Kern-Funktionalität)
|
||
|
||
**Datei:** `src/telegram/handlers.ts` (fortgesetzt)
|
||
|
||
```typescript
|
||
// Handler für bot.on('message:photo')
|
||
//
|
||
// Ablauf:
|
||
// 1. Höchste verfügbare Auflösung wählen:
|
||
// ctx.message.photo ist ein Array von PhotoSize-Objekten,
|
||
// sortiert nach Größe. Letztes Element = höchste Auflösung.
|
||
//
|
||
// 2. File-Info abrufen:
|
||
// const file = await ctx.api.getFile(photo.file_id)
|
||
// Download-URL: https://api.telegram.org/file/bot<TOKEN>/<file.file_path>
|
||
//
|
||
// 3. Bild herunterladen (als Buffer):
|
||
// fetch() auf die Download-URL
|
||
//
|
||
// 4. Alt-Text bestimmen:
|
||
// - Falls Caption vorhanden (ctx.message.caption) → als Alt-Text verwenden
|
||
// - Falls nicht → Generiere: "Upload via Telegram – [Datum] [Uhrzeit]"
|
||
//
|
||
// 5. Statusmeldung senden:
|
||
// "⏳ Bild wird hochgeladen..."
|
||
//
|
||
// 6. An Payload API hochladen:
|
||
// payloadClient.uploadMedia(buffer, filename, { alt, tenantId })
|
||
//
|
||
// 7. Erfolgsmeldung:
|
||
// "✅ Upload erfolgreich!
|
||
// 📎 ID: [id]
|
||
// 📁 Dateiname: [filename]
|
||
// 🔗 URL: [url]
|
||
// 🏷️ Tenant: [tenant-name]
|
||
// 📐 Größen: thumbnail, small, medium, large, xlarge, 2k, og"
|
||
//
|
||
// 8. Bei Fehler:
|
||
// "❌ Upload fehlgeschlagen: [Fehlermeldung]"
|
||
// Logge den vollständigen Error serverseitig.
|
||
```
|
||
|
||
**Akzeptanzkriterien:**
|
||
- [ ] Bilder werden in höchster Auflösung heruntergeladen
|
||
- [ ] Caption wird als Alt-Text verwendet (falls vorhanden)
|
||
- [ ] Statusmeldung wird gesendet BEVOR der Upload startet
|
||
- [ ] Erfolgsmeldung enthält Media-ID und URL
|
||
- [ ] Fehler werden sauber abgefangen und dem User angezeigt
|
||
- [ ] Tenant-Zuordnung ist korrekt
|
||
|
||
#### 4.5 Document Handler (Erweitert)
|
||
|
||
```typescript
|
||
// Handler für bot.on('message:document')
|
||
//
|
||
// Akzeptiere nur Bildformate: jpg, jpeg, png, webp, avif, gif, svg
|
||
// Bei nicht unterstütztem Format:
|
||
// "⚠️ Format nicht unterstützt. Erlaubt: JPG, PNG, WebP, AVIF, GIF, SVG"
|
||
//
|
||
// Vorteil von Document-Upload gegenüber Photo:
|
||
// Telegram komprimiert Bilder die als Foto gesendet werden.
|
||
// Als Dokument gesendet bleibt die Originalqualität erhalten.
|
||
// → Dem User diesen Tipp in /help erklären.
|
||
```
|
||
|
||
#### 4.6 Album/Bulk Handler
|
||
|
||
```typescript
|
||
// Handler für mehrere Bilder gleichzeitig (Media Group / Album)
|
||
//
|
||
// Telegram sendet Alben als einzelne Messages mit gleicher media_group_id.
|
||
// Sammle alle Messages mit gleicher media_group_id über ein kurzes Zeitfenster
|
||
// (500ms Debounce), dann lade alle Bilder sequentiell hoch.
|
||
//
|
||
// Status: "⏳ Album erkannt: [N] Bilder werden hochgeladen..."
|
||
// Pro Bild: Fortschritt melden: "📤 [X]/[N] hochgeladen..."
|
||
// Am Ende: Zusammenfassung aller Upload-IDs
|
||
```
|
||
|
||
#### 4.7 Inline Keyboards
|
||
|
||
**Datei:** `src/telegram/keyboards.ts`
|
||
|
||
```typescript
|
||
// Tenant-Auswahl Keyboard
|
||
// Dynamisch aus API geladen (GET /api/tenants)
|
||
// Format: 2 Buttons pro Reihe
|
||
// Jeder Button: callback_data = "tenant:<ID>"
|
||
//
|
||
// Beispiel:
|
||
// [ [porwoll.de] [C2S] ]
|
||
// [ [Gunshin] [BlogWoman] ]
|
||
|
||
// Callback Query Handler:
|
||
// Bei "tenant:<ID>" → Session updaten, Bestätigung senden
|
||
```
|
||
|
||
---
|
||
|
||
### 5. Utilities
|
||
|
||
#### 5.1 Logger
|
||
|
||
**Datei:** `src/utils/logger.ts`
|
||
|
||
```typescript
|
||
// Einfacher Logger mit Levels: debug, info, warn, error
|
||
// Format: [TIMESTAMP] [LEVEL] [MODULE] Message
|
||
// Beispiel: [2026-03-01 14:30:00] [INFO] [PayloadClient] Login erfolgreich
|
||
//
|
||
// Kein externes Logging-Framework nötig – console.log basiert reicht.
|
||
// LOG_LEVEL aus config bestimmt Mindest-Level.
|
||
```
|
||
|
||
#### 5.2 Download Helper
|
||
|
||
**Datei:** `src/utils/download.ts`
|
||
|
||
```typescript
|
||
// Funktion zum Herunterladen einer Datei von einer URL als Buffer.
|
||
// Nutze native fetch().
|
||
// Timeout: 30 Sekunden
|
||
// Max. Dateigröße: 20 MB (Telegram-Limit)
|
||
// Bei Fehler: Spezifische Error-Messages (Timeout, Too Large, Network Error)
|
||
|
||
async function downloadFile(url: string): Promise<{ buffer: Buffer; mimeType: string }> { /* ... */ }
|
||
```
|
||
|
||
---
|
||
|
||
### 6. PM2 Konfiguration & Deployment
|
||
|
||
#### 6.1 PM2 Ecosystem File
|
||
|
||
**Datei:** `ecosystem.config.cjs`
|
||
|
||
```javascript
|
||
module.exports = {
|
||
apps: [{
|
||
name: 'telegram-media-bot',
|
||
script: './dist/index.js',
|
||
instances: 1, // NUR 1 Instanz! Telegram Long-Polling verträgt kein Clustering
|
||
autorestart: true,
|
||
watch: false,
|
||
max_memory_restart: '256M',
|
||
env: {
|
||
NODE_ENV: 'production'
|
||
},
|
||
error_file: './logs/error.log',
|
||
out_file: './logs/out.log',
|
||
merge_logs: true,
|
||
time: true
|
||
}]
|
||
};
|
||
```
|
||
|
||
**WICHTIG:** Der Bot nutzt Long-Polling (kein Webhook). Deshalb darf nur EINE Instanz laufen. Mehrere Instanzen führen zu Konflikten bei der Telegram API.
|
||
|
||
#### 6.2 Deployment auf sv-payload
|
||
|
||
Der Bot läuft als zusätzlicher PM2-Prozess auf **sv-payload (LXC 700, 10.10.181.100)**, wo bereits das Payload CMS per PM2 managed wird.
|
||
|
||
```bash
|
||
# Auf sv-payload (als user 'payload')
|
||
cd /home/payload
|
||
git clone git@github.com:complexcaresolutions/telegram-media-bot.git
|
||
cd telegram-media-bot
|
||
pnpm install
|
||
cp .env.example .env
|
||
# → .env mit echten Werten befüllen
|
||
|
||
# Build und Start
|
||
pnpm build
|
||
pm2 start ecosystem.config.cjs
|
||
pm2 save
|
||
```
|
||
|
||
#### 6.3 GitHub Actions Workflow (Optional)
|
||
|
||
**Datei:** `.github/workflows/deploy.yml`
|
||
|
||
```yaml
|
||
name: Deploy Telegram Bot
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
workflow_dispatch:
|
||
|
||
jobs:
|
||
deploy:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Deploy via SSH
|
||
uses: appleboy/ssh-action@v1
|
||
with:
|
||
host: 37.24.237.181 # Externe IP (UDM Pro SE)
|
||
username: payload
|
||
key: ${{ secrets.STAGING_SSH_KEY }}
|
||
port: 22122 # SSH-Port über Port-Forwarding
|
||
script: |
|
||
cd /home/payload/telegram-media-bot
|
||
git pull origin main
|
||
pnpm install --frozen-lockfile
|
||
pnpm build
|
||
pm2 restart telegram-media-bot
|
||
```
|
||
|
||
**SSH-Zugang:** Externer Zugang über UDM Pro SE Port-Forwarding (Port 22122 → sv-payload:22). Der SSH-Key `STAGING_SSH_KEY` ist bereits als GitHub Secret konfiguriert.
|
||
|
||
---
|
||
|
||
### 7. Sicherheit
|
||
|
||
#### 7.1 Maßnahmen
|
||
|
||
1. **User-Whitelist:** Nur explizit erlaubte Telegram User-IDs dürfen den Bot nutzen
|
||
2. **Token-Sicherheit:** Payload JWT wird nur im Memory gehalten, nie auf Disk geschrieben
|
||
3. **Environment Variables:** Alle Secrets in `.env`, nie im Code
|
||
4. **Rate Limiting:** Maximal 10 Uploads pro Minute pro User (Bot-seitig implementiert)
|
||
5. **Dateigrößen-Limit:** Telegram begrenzt auf 20 MB, zusätzlich Bot-seitiges Limit von 20 MB
|
||
6. **Dateityp-Validierung:** Nur erlaubte MIME-Types akzeptieren (image/jpeg, image/png, image/webp, image/avif, image/gif, image/svg+xml)
|
||
7. **Kein Webhook-Modus:** Long-Polling vermeidet die Notwendigkeit eines öffentlich erreichbaren Endpoints
|
||
|
||
#### 7.2 Telegram Bot erstellen
|
||
|
||
1. Öffne Telegram und suche `@BotFather`
|
||
2. Sende `/newbot`
|
||
3. Name: `CCS Media Upload Bot` (oder ähnlich)
|
||
4. Username: `ccs_media_upload_bot` (muss eindeutig sein und auf `bot` enden)
|
||
5. Token sichern → in `.env` als `TELEGRAM_BOT_TOKEN` eintragen
|
||
6. Optional via BotFather:
|
||
- `/setdescription` – Bot-Beschreibung setzen
|
||
- `/setcommands` – Bot-Befehle registrieren:
|
||
```
|
||
start - Bot starten und Hilfe anzeigen
|
||
tenant - Ziel-Tenant wechseln
|
||
list - Letzte Uploads anzeigen
|
||
status - Bot- und API-Status prüfen
|
||
help - Ausführliche Hilfe
|
||
```
|
||
|
||
---
|
||
|
||
### 8. Error Handling & Edge Cases
|
||
|
||
#### 8.1 Zu behandelnde Szenarien
|
||
|
||
| Szenario | Verhalten |
|
||
|----------|-----------|
|
||
| Payload API nicht erreichbar | User informieren, Retry nach 30s, Log Error |
|
||
| JWT abgelaufen während Upload | Auto-Relogin + Retry (max. 1x) |
|
||
| Telegram File Download fehlschlägt | User informieren mit spezifischem Error |
|
||
| Bild zu groß (>20 MB) | `⚠️ Datei zu groß. Maximum: 20 MB` |
|
||
| Nicht unterstütztes Format | `⚠️ Format nicht unterstützt. Erlaubt: JPG, PNG, WebP, AVIF, GIF, SVG` |
|
||
| Kein Tenant gewählt | Default-Tenant verwenden, User informieren |
|
||
| Payload 403 (Tenant-Problem) | `❌ Zugriff verweigert. Prüfe die Tenant-Zuordnung.` |
|
||
| Payload 429 (Rate Limit) | `⏳ Zu viele Anfragen. Bitte warte [X] Sekunden.` |
|
||
| Bot-Start ohne gültige Config | Sofortiger Exit mit klarem Fehlertext |
|
||
| Album mit >10 Bildern | Hinweis dass max. 10 gleichzeitig verarbeitet werden |
|
||
|
||
#### 8.2 Graceful Shutdown
|
||
|
||
```typescript
|
||
// Bei SIGINT/SIGTERM:
|
||
// 1. Bot-Polling stoppen
|
||
// 2. Laufende Uploads abwarten (max. 60s Timeout)
|
||
// 3. Prozess beenden
|
||
// PM2 sendet SIGINT, dann nach Timeout SIGKILL.
|
||
```
|
||
|
||
---
|
||
|
||
## Erfolgskriterien (Gesamt)
|
||
|
||
- [ ] `pnpm lint` (tsc --noEmit) ohne Errors
|
||
- [ ] `pnpm build` erfolgreich
|
||
- [ ] Bot startet und verbindet sich mit Telegram
|
||
- [ ] `/start` zeigt Begrüßung
|
||
- [ ] `/tenant` zeigt Inline-Keyboard mit Tenants aus der API
|
||
- [ ] Tenant-Wechsel funktioniert und wird in Session gespeichert
|
||
- [ ] Bild-Upload (als Foto) → Bild erscheint in Payload CMS Media Collection mit korrektem Tenant
|
||
- [ ] Bild-Upload (als Dokument) → Bild in Originalqualität hochgeladen
|
||
- [ ] Caption wird als Alt-Text übernommen
|
||
- [ ] Album-Upload funktioniert (mehrere Bilder)
|
||
- [ ] `/list` zeigt letzte 5 Uploads
|
||
- [ ] `/status` zeigt API-Status und Token-Validität
|
||
- [ ] Nicht-autorisierte User werden blockiert
|
||
- [ ] Fehler werden dem User als verständliche Meldungen angezeigt
|
||
- [ ] PM2 managed den Prozess mit Auto-Restart
|
||
- [ ] Logs werden in `./logs/` geschrieben
|
||
|
||
---
|
||
|
||
## Selbst-Prüfung
|
||
|
||
Nach jeder Iteration:
|
||
1. `pnpm lint` (tsc --noEmit)
|
||
2. `pnpm build`
|
||
3. Bei Fehler: korrigieren und wiederholen
|
||
4. Manueller Test-Flow:
|
||
- Bot starten mit `pnpm dev`
|
||
- `/start` senden
|
||
- `/tenant` → Tenant wählen
|
||
- Bild senden → Upload prüfen
|
||
- `/list` → Upload in Liste sichtbar
|
||
5. Fortschritt in README.md dokumentieren
|
||
|
||
---
|
||
|
||
## Escape Hatch
|
||
|
||
Nach 15 Iterationen ohne Fortschritt:
|
||
- Dokumentiere was blockiert in BLOCKERS.md
|
||
- Liste alle versuchten Ansätze auf
|
||
- Schlage 3 alternative Lösungswege vor
|
||
- Output <promise>BLOCKED</promise>
|
||
|
||
---
|
||
|
||
## Hinweise für die Implementierung
|
||
|
||
1. **Telegram Bot Token** muss vor dem Start via @BotFather erstellt und in `.env` eingetragen werden.
|
||
2. **Payload Admin Credentials** müssen einen User mit SuperAdmin-Rechten referenzieren (für Tenant-übergreifenden Zugriff).
|
||
3. **Grammy statt node-telegram-bot-api** – Grammy ist moderner, hat bessere TypeScript-Unterstützung und aktive Wartung.
|
||
4. **Native fetch statt axios** – Node.js 22 hat native fetch/FormData. Keine zusätzliche HTTP-Library nötig.
|
||
5. **Long-Polling statt Webhooks** – Einfacher zu deployen (kein öffentlicher Endpoint nötig), perfekt für den Use Case.
|
||
6. **Kein separater LXC-Container** nötig – der Bot läuft als zusätzlicher PM2-Prozess auf sv-payload.
|
||
|
||
---
|
||
|
||
## Fertig?
|
||
|
||
Wenn ALLE Aufgaben erledigt sind UND alle Erfolgskriterien erfüllt sind:
|
||
|
||
<promise>TELEGRAM_BOT_COMPLETE</promise>
|