Skip to content

Repository files navigation

ttrpg-signal

Signal-Bot der als Dungeon Master via Claude API antwortet. Spieler schreiben in eine Signal-Gruppe (oder 1:1), der Bot antwortet als DM.

Spielwelt-Daten: Engine, Templates und Abenteuer-Struktur kommen aus phieb/ttrpg — wird beim ersten docker compose up automatisch geklont.

Stack

  • signal-cli (bbernhard/signal-cli-rest-api) — Signal Protokoll
  • Python 3.11 — Bot-Service
  • DM AI — konfigurierbar: OpenAI GPT-4o, Claude Sonnet, oder Gemini (siehe DM_PROVIDER)
  • Claude Haiku — Hilfsaufgaben (Charakter-Extraktion, Session-Komprimierung)
  • Vertex AI Imagen 4 — Charakter-Portrait-Generierung
  • Docker — containerisiert

Setup

1. Arbeitsverzeichnis anlegen

mkdir ttrpg-signal && cd ttrpg-signal
curl -O https://raw.githubusercontent.com/phieb/ttrpg-signal/main/docker-compose.example.yml
curl -O https://raw.githubusercontent.com/phieb/ttrpg-signal/main/.env.example
cp docker-compose.example.yml docker-compose.yml
cp .env.example .env

Der Bot-Code kommt als fertiges Image von ghcr.io/phieb/ttrpg-signal:latest — kein Repo-Clone nötig. Das ttrpg Engine-Repo wird beim ersten docker compose up automatisch geklont.

2. docker-compose.yml anpassen

Signal-CLI-Daten werden als Bind Mount eingebunden — Pfad anpassen:

signal-cli:
  volumes:
    - /pfad/zu/signal-cli-data:/home/.local/share/signal-cli

Wer signal-cli schon laufen hat: einfach den bestehenden Datenpfad eintragen, fertig — keine Neuregistrierung nötig.

3. signal-cli starten und registrieren

docker compose up -d signal-cli

Als linked device registrieren — QR-Code generieren:

curl -s "http://localhost:8085/v1/qrcodelink?device_name=ttrpg-bot" -o qrcode.png

PNG öffnen → Signal → Einstellungen → Verknüpfte Geräte → Gerät hinzufügen → scannen.

4. .env befüllen

cp .env.example .env
# DM Provider: openai | anthropic | gemini
DM_PROVIDER=openai
OPENAI_API_KEY=sk-...           # für DM_PROVIDER=openai
ANTHROPIC_API_KEY=sk-ant-...   # immer benötigt (Charakter-Extraktion, Komprimierung)
# GEMINI_API_KEY=...            # für DM_PROVIDER=gemini

# Optionale Modell-Overrides (Defaults siehe config.py)
# OPENAI_DM_MODEL=gpt-4o
# ANTHROPIC_DM_MODEL=claude-sonnet-4-6
# GEMINI_DM_MODEL=gemini-2.0-flash

SIGNAL_PHONE_NUMBER=+43...      # Bot-Nummer (linked device)
ADMIN_PHONE_NUMBER=+43...       # Wer !kommandos schicken darf
GCP_PROJECT=...                 # GCP Projekt-ID für Vertex AI (Avatar-Generierung)
GCP_LOCATION=us-central1

Alle Variablen mit Beschreibung und Defaults: siehe .env.example.

5. GCP Service Account (für Avatar-Generierung)

gcloud iam service-accounts create ttrpg-bot \
  --display-name="TTRPG Bot" --project=PROJEKT_ID

gcloud projects add-iam-policy-binding PROJEKT_ID \
  --member="serviceAccount:ttrpg-bot@PROJEKT_ID.iam.gserviceaccount.com" \
  --role="roles/aiplatform.user"

gcloud iam service-accounts keys create gcp-sa.json \
  --iam-account="ttrpg-bot@PROJEKT_ID.iam.gserviceaccount.com"

gcp-sa.json im Projektordner ablegen (in .gitignore, nie ins Git!).

6. Addons einbinden (optional)

Addons werden als zusätzliche Volume-Mounts aktiviert — jeder Flavour eines Addons bekommt eine eigene Zeile in docker-compose.yml:

volumes:
  - /pfad/zu/mein-addon/flavours/mein-flavour:/mnt/ttrpg/_engine/flavours/mein-flavour

Kein Code-Change nötig — der Bot erkennt neue Flavour-Ordner automatisch. Wie du ein eigenes Addon baust steht in ttrpg-adult/README.md als Referenzimplementierung (Struktur, manifest.yaml, CHARACTER_FIELDS.yaml).

7. Starten

docker compose up -d

Beim ersten Start klont Docker automatisch das ttrpg Engine-Repo und legt status.yaml aus der Vorlage an. Spieler danach per !invite direkt über den Bot registrieren.


Receive-Modus: Polling vs. Webhook

Der Bot bietet zwei Modi, um eingehende Signal-Nachrichten zu empfangen.

Polling (Default)

Standardverhalten — keine Konfiguration nötig. Der Bot ruft signal-cli periodisch über GET /v1/receive/<number> ab und verarbeitet neue Envelopes. Standalone deploybar, keine externen Abhängigkeiten.

Webhook (optional)

Statt aktiv zu pollen, kann ein externer Dispatcher (z.B. n8n, ein Reverse-Proxy oder eine eigene Integration) eingehende Envelopes per POST /receive an den Bot pushen. Der Polling-Loop läuft in diesem Modus nicht — der Bot wartet ausschließlich auf Webhook-Aufrufe.

Aktivieren:

RECEIVE_MODE=webhook
WEBHOOK_SECRET=ein_geheimes_token   # optional, aber empfohlen
WEBHOOK_PORT=8090                   # default

In docker-compose.yml den Port veröffentlichen (oder hinter einen Reverse-Proxy stellen):

ttrpg-bot:
  ports:
    - "8090:8090"

Der Dispatcher sendet pro eingehender Signal-Nachricht ein POST:

POST /receive
Content-Type: application/json
Authorization: Bearer <WEBHOOK_SECRET>     # oder: X-Webhook-Secret: <WEBHOOK_SECRET>

<envelope-json wie von signal-cli /v1/receive zurückgegeben>

Der Body ist entweder ein einzelnes Envelope-Objekt oder eine Liste — beides wird akzeptiert. Antwort: 200 OK bei Erfolg, 401 bei falschem Secret.

Health-Check: GET /health{"status": "ok"}.

Routing (für geteilte Signal-Nummer / mehrere Bots am selben Webhook): Der Dispatcher fragt vor dem Weiterleiten per POST /claims ab, ob eine Nachricht zu diesem Bot gehört. Der Body ist dasselbe Envelope, das sonst an /receive ginge — akzeptiert wird das native {"envelope": …}, ein blankes Envelope oder ein n8n-{"body": {"envelope": …}}. Antwort immer 200 mit {"claims": true} bzw. {"claims": false}. Beansprucht werden nur Nachrichten, die der Bot auch verarbeiten würde: keine eigenen Echos, keine unbekannten Absender, Gruppen nur wenn Setup-Kanal oder registrierte Abenteuer-Gruppe, DMs bekannter Spieler/Admins immer. Read-only, kein Secret nötig — bei Parse-Fehlern oder nicht zustellbaren Envelopes {"claims": false}.


Neues Abenteuer anlegen

  1. Spieler registrieren (einmalig pro Spieler):
    !invite +43... Name
    
  2. Abenteuer anlegen — erstellt Ordnerstruktur, Signal-Gruppe und schickt Willkommenstext:
    !new Mein Abenteuer @Spieler1 @Spieler2 --fantasy
    
    Optionale Flavours mit --name anhängen. Addon-Flavours funktionieren genauso sobald das Addon eingebunden ist.
  3. Session 0 starten (Charaktererstellung + Weltenbau):
    !session0
    

Der Bot finalisiert Session 0 automatisch sobald alle Charakterblätter vollständig sind — er legt YAMLs an, generiert Portraits und schickt Charakterblatt-PDFs in die Gruppe.


Kommandos

All players (group, private setup channel, or 1:1)

Commands work in all three contexts: the adventure group, the private 1:1 setup channel, and direct messages.

Command Description
!help Show available commands
!status In group: current adventure state. In DM: list your adventures
!status <name> In DM: details of a named adventure (own adventures only)
!charakter Show your character sheet + PDF
!charakter <name> Find a specific character by name
!avatar Show your current portrait and prompt
!avatar regen Regenerate portrait with the existing prompt
!avatar prompt Show the current image generation prompt
!avatar prompt <text> Update the prompt and regenerate portrait
!bugreport <text> Report a bug

Admin only

Command Description
!save Compress & save game state → session.yaml, end session
!session0 Start Session 0 — DM leads world-building + intro scene
!new <name> [@Player1 @Player2 ...] Create adventure, Signal group, private setup channels per player
!invite +43... Name Register player (creates players/Name.yaml) + welcome message
!dm @Player <text> Secret 1:1 message to a player
!players List all registered players with number and role
!showme [idea] Generate and send an atmospheric scene image — optional idea as inspiration
!usage API usage & estimated costs (all providers + Vertex AI)

Dateistruktur

ttrpg-signal/                  ← dieses Repo (Bot-Code)
├── Dockerfile
├── docker-compose.yml
├── .env                       ← nie ins Git!
├── gcp-sa.json                ← nie ins Git!
└── bot/
    ├── main.py                ← Event Loop, Kommando-Router
    ├── signal_client.py       ← signal-cli REST API Wrapper
    ├── dm_engine.py           ← DM-Logik, History, Log, Komprimierung
    ├── llm_client.py          ← AI-Provider-Adapter (OpenAI / Anthropic / Gemini)
    ├── session_manager.py     ← YAML lesen/schreiben, Kontext-Builder
    ├── generate_avatar.py     ← Vertex AI Imagen, Prompt-Verwaltung
    ├── usage_tracker.py       ← Token- und Kosten-Tracking
    └── config.py              ← alle Env-Variablen

ttrpg/                         ← separates Repo, eingebunden via TTRPG_PATH
├── status.yaml                ← Abenteuer-Übersicht + Signal-Gruppen
├── status.example.yaml        ← Vorlage
├── players/                   ← ein YAML pro Spieler (Telefonnummer etc.)
├── _engine/
│   ├── DUNGEON_MASTER.md      ← DM System-Prompt
│   ├── CHARACTER_SETUP.md     ← privater Setup-Kanal Prompt
│   └── templates/             ← YAML-Vorlagen für neue Abenteuer
└── adventures/
    └── mein-abenteuer/
        ├── session.yaml
        ├── setting.yaml
        ├── npcs.yaml
        ├── spielprotokoll.jsonl   ← Crash-sicheres Log (wird bei !save geleert)
        └── characters/
            ├── held.yaml
            ├── held_avatar.png
            └── held_avatar.txt    ← Imagen-Prompt (optional, überschreibt YAML-Feld)

Persistenz

Was Wo Wann
Jede Nachricht spielprotokoll.jsonl sofort (append)
History bei Neustart aus spielprotokoll.jsonl beim ersten Zugriff
Spielstand/Zusammenfassung session.yaml bei !save (Claude komprimiert)
JSONL geleert bei !save

Backup

Die Engine-Dateien (_engine/, Templates) kommen aus Git und sind jederzeit wiederherstellbar. Was nicht in Git liegt und gesichert werden sollte:

Was Wo im ttrpg-data Volume
Spielstände & Szenen adventures/*/session.yaml
Charakterblätter adventures/*/characters/*.yaml
Portraits & PDFs adventures/*/characters/*.png, *.pdf
Spielprotokolle adventures/*/spielprotokoll.jsonl
Spieler-Registry players/*.yaml
Abenteuer-Übersicht status.yaml

Einfachstes Backup — ttrpg/-Ordner sichern (Bind Mount, direkt zugänglich):

tar czf /pfad/zu/backup/ttrpg-backup-$(date +%Y%m%d).tar.gz -C /pfad/zu/ttrpg-signal ttrpg

Bot aktualisieren

GitHub Actions baut bei jedem Push auf main automatisch ein neues Image und pusht es nach ghcr.io/phieb/ttrpg-signal:latest. Update auf dem Server:

docker compose pull ttrpg-bot
docker compose up -d ttrpg-bot

About

Signal Bot als Dungeon Master — Claude API + signal-cli

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages