cms.c2sgmbh/prompts/PROMPT_TELEGRAM_MEDIA_BOT.md
Martin Porwoll 52a266d72d docs: add telegram media bot plan and sensualmoment design docs
- 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>
2026-03-01 22:14:44 +00:00

22 KiB
Raw Permalink Blame History

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:

curl -X POST "https://cms.c2sgmbh.de/api/users/login" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@example.com",
    "password": "your-password"
  }'

Response:

{
  "message": "Auth Passed",
  "user": { "id": 1, "email": "admin@example.com", "isSuperAdmin": true },
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Token verwenden:

curl "https://cms.c2sgmbh.de/api/media" \
  -H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..."

Media Upload Endpoint

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

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

{
  "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

{
  "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

# 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

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

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

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

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

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

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

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

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

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

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

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.

# 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

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

// 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 BLOCKED

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:

TELEGRAM_BOT_COMPLETE