Files
Homelab--Bratonein-Kontroll…/Kapitel 19/Premium Rohtext.md

1056 lines
40 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Premium WAHA: Automatisierte Statusmeldungen & KI-gestützte Antworten
WAHA ist im UCC weit mehr als ein Werkzeug zum Senden einzelner WhatsApp-Nachrichten.
Im Premium-Kapitel wird der Dienst zu einem **intelligenten Kommunikationsmodul**, das auf Ereignisse in deinem System reagieren und Informationen automatisiert weitergeben kann.
Ziel ist eine **vollständige Integration von WAHA in deine bestehende Infrastruktur** vor allem in Verbindung mit n8n und Netdata.
So kannst du dich über wichtige Systemereignisse informieren lassen oder, optional, eingehende Nachrichten automatisch beantworten lassen datensouverän, lokal und ohne externe Cloud-APIs.
Das Kapitel ist in zwei praxisnahe Szenarien unterteilt:
1. **Automatische Statusbenachrichtigung über WhatsApp**
Wenn Netdata einen Alarm oder kritischen Wert erkennt, löst n8n automatisch eine WhatsApp-Nachricht über WAHA aus.
Du erhältst sofort eine Benachrichtigung über Auslastung, Fehler oder Warnungen, ohne dich aktiv einloggen zu müssen.
2. **KI-gestützte, regelkonforme Antwortfunktion**
Eine eingehende WhatsApp-Nachricht wird von WAHA empfangen, in n8n verarbeitet und an eine lokale KI weitergeleitet.
Die KI generiert eine personalisierte Antwort, die über WAHA zurückgesendet wird.
Diese Variante bewegt sich aktuell **im erlaubten Rahmen**, da sie nur auf vom Nutzer initiierte Nachrichten reagiert,
liegt jedoch **im Graubereich zukünftiger Richtlinienänderungen**.
> [!CAUTION]
> Die Nutzung einer automatisierten KI-Antwort über WAHA ist derzeit zulässig, solange jede Kommunikation vom Nutzer selbst initiiert wird
> und keine Werbung oder massenhafte Nachrichten versendet werden.
> Beachte jedoch, dass Meta die WhatsApp-Richtlinien jederzeit ändern kann.
> Sollte WhatsApp künftig KI-basierte Antworten oder WAHA-ähnliche Systeme einschränken,
> kann dieses Setup Anpassungen erfordern.
👉 **Screenshot geeignet:** Diagramm mit Datenfluss zwischen Netdata, n8n, KI-Agent und WAHA
Am Ende dieses Kapitels verfügst du über ein vollständig integriertes Benachrichtigungs- und Kommunikationssystem,
das deine Infrastruktur in Echtzeit überwacht und bei Bedarf automatisiert reagiert
ein klarer Schritt in Richtung eines selbstlernenden, souveränen Kontrollzentrums.
---
## Statusbenachrichtigung über WAHA
### Schritt 1 n8n-Workflow mit Deduplizierung und WAHA-Versand
Ziel: Ein **n8n-Workflow** soll Systemereignisse aus Netdata empfangen, diese in Textform bringen, doppelte Alarme herausfiltern und anschließend über **WAHA** als WhatsApp-Nachricht versenden.
Damit entsteht ein zentraler Benachrichtigungsweg für kritische Zustände im gesamten UCC.
👉 **Screenshot geeignet:** n8n-Canvas mit drei verbundenen Nodes: *Webhook (POST)**Function (Nachricht & Filter)**HTTP Request (WAHA)*
#### Node 1 Webhook (Trigger)
**Zweck:** Erstellt eine Empfangsadresse, über die Netdata Alarmmeldungen an n8n sendet.
**Node-Typ:** Webhook
**Name:** Eingang Statusalarm
**Parameter:**
- **HTTP Method:** `POST`
- **Path:** `/waha/statusalarm`
- **Response Mode:** `Last Node`
- **Response Code:** `200`
> [!NOTE]
> Nach dem Speichern zeigt n8n automatisch die Webhook-URL an, z. B.:
> `https://n8n.deinedomain.tld/webhook/waha/statusalarm`
> Diese URL wird später in der Netdata-Alarmregel eingetragen, damit Netdata Alarme direkt an diesen Workflow sendet.
👉 **Screenshot geeignet:** Webhook-Node mit sichtbarer URL `.../webhook/waha/statusalarm`
#### Node 2 Function (Nachricht aufbereiten + Dedupe)
**Zweck:** Formatiert die eingehenden Daten und verhindert, dass wiederholt identische Alarme mehrfach gesendet werden.
Die Funktion merkt sich den letzten Alarm jedes Containers für 10 Minuten.
Wenn innerhalb dieses Zeitraums derselbe Host und Alarm erneut eintreffen, wird die Nachricht unterdrückt.
**Node-Typ:** Function
**Name:** Nachricht & Filter
**Code:**
```js
const p = items[0].json || {};
const host = p.host || 'unbekannt';
const alarm = p.alarm || 'Alarm';
const status = p.status || 'warn';
const value = (p.value !== undefined && p.value !== null) ? String(p.value) : '-';
const when = p.when || new Date().toISOString();
const key = `${host}_${alarm}_${status}`;
const now = Date.now();
const last = $flow.get(key) || 0;
const interval = 10 * 60 * 1000; // 10 Minuten
if (now - last < interval) {
// gleicher Alarm innerhalb von 10 Minuten -> überspringen
return [];
}
$flow.set(key, now);
const text =
`⚠️ ${alarm}\n` +
`Host: ${host}\n` +
`Status: ${status}\n` +
`Wert: ${value}\n` +
`Zeit: ${when}`;
return [{ json: { text } }];
```
> [!TIP]
> Die Filterzeit lässt sich über die Variable `interval` anpassen.
> Für produktive Umgebungen mit vielen Containern sind 10 bis 15 Minuten meist ideal.
👉 **Screenshot geeignet:** Function-Node mit geöffnetem Code-Editor, markiertem Kommentar `// gleicher Alarm innerhalb von 10 Minuten`
#### Node 3 HTTP Request (WAHA Statusmeldung senden)
**Zweck:** Sendet die aufbereitete Nachricht über WAHA an WhatsApp.
**Node-Typ:** HTTP Request
**Name:** WAHA Statusmeldung senden
**Parameter:**
- **Method:** `POST`
- **URL:**
```
https://waha.deinedomain.tld/api/sendText
```
- **Headers:**
- `Content-Type`: `application/json`
- *(optional)* `Authorization`: `Bearer <DEIN_WAHA_API_TOKEN>`
- **Body → JSON:**
```json
{
"chatId": "49XXXXXXXXXXX@c.us",
"text": "={{$json[\"text\"]}}"
}
```
> [!NOTE]
> **Chat-ID ermitteln:**
> 1. Sende mit deinem WhatsApp-Gerät eine Testnachricht an den Ziel-Chat.
> 2. Öffne `https://waha.deinedomain.tld/api/messages`.
> 3. Kopiere den Wert `chatId` aus der letzten eingegangenen Nachricht.
> Einzelchat endet auf `@c.us`
> Gruppenchat endet auf `@g.us`
> 4. Trage diese ID im Node ein.
> [!IMPORTANT]
> WAHA muss im Webinterface den Status **Connected** anzeigen.
> Bei *Pairing* oder *Disconnected* kann keine Nachricht gesendet werden.
👉 **Screenshot geeignet:** HTTP-Request-Node mit sichtbarer `chatId`
#### Test des Workflows
Der Workflow kann manuell getestet werden, bevor Netdata angebunden ist:
```bash
curl -X POST "https://n8n.deinedomain.tld/webhook/waha/statusalarm" \
-H "Content-Type: application/json" \
-d '{
"host": "ucc-core-01",
"alarm": "CPU high",
"status": "crit",
"value": "96%",
"when": "2025-10-20T18:42:00Z"
}'
```
Wenn alles funktioniert, erscheint die Meldung wenige Sekunden später als WhatsApp-Nachricht.
👉 **Screenshot geeignet:** WhatsApp-Chat mit empfangener Meldung „⚠️ CPU high …“
> [!TIP]
> Wenn keine Nachricht ankommt, prüfe in n8n die **Execution Logs**.
> Dort sollte der HTTP-Request-Node den Statuscode `200` zurückgeben.
> Wird keine Ausführung gestartet, ist meist der Webhook-Pfad falsch oder Netdata erreicht den Server nicht.
### Schritt 2 Netdata-Alarmregel im Parent-LXC für WAHA-Benachrichtigung
Die zentrale Netdata-Instanz im UCC empfängt bereits alle Metriken der Child-Container per Streaming.
Damit entfällt die Notwendigkeit, in jedem einzelnen LXC eigene Alarme zu konfigurieren.
Stattdessen wird **eine einzige Alarmregel** direkt im **Parent-Container** hinterlegt,
die auf aggregierte Systemwerte zugreift und bei Erreichen bestimmter Schwellwerte eine Nachricht über **n8n → WAHA** sendet.
👉 **Screenshot geeignet:** Netdata-Parent-Dashboard mit aktivem Alarm für einen Child-Container
> [!INFO]
> Die zentrale Überwachung mit Netdata setzt voraus, dass alle Child-LXC-Container korrekt angebunden sind.
> Im **Kapitel 15 Netdata** wird beschrieben, wie du das Streaming-System einrichtest
> und wie die einzelnen Container ihre Metriken an den Parent weitergeben.
> Es wird **dringend empfohlen**, dieses Kapitel vollständig umzusetzen,
> bevor du hier mit der WAHA-Benachrichtigung fortfährst
> sonst können Alarme fehlen oder falsche Hostnamen übermittelt werden.
#### Zentrale Alarmregel anlegen
1. Öffne auf deinem **Netdata-Parent-LXC** die Health-Konfiguration:
```bash
nano /etc/netdata/health.d/waha_notify.conf
```
2. Füge folgende Definition ein:
```
alarm: ucc_cpu_high
on: system.cpu
lookup: average -1m unaligned of user + system
units: %
every: 1m
warn: $this > 80
crit: $this > 95
exec: /usr/libexec/netdata/plugins.d/alarm-notify.sh
to: webhook
WEBHOOK_URL="https://n8n.deinedomain.tld/webhook/waha/statusalarm"
WEBHOOK_HEADER="Content-Type: application/json"
WEBHOOK_BODY="{\"host\":\"${hostname}\",\"alarm\":\"CPU high\",\"status\":\"${status}\",\"value\":\"${value}\",\"when\":\"$(date -Iseconds)\"}"
```
Diese Regel löst aus, sobald die durchschnittliche CPU-Last eines überwachten Systems über **80 %** steigt (Warnung)
oder **95 %** erreicht (Kritisch).
Netdata ruft dann automatisch den im ersten Schritt erstellten **n8n-Webhook** auf,
der die Nachricht an WAHA weiterleitet.
3. Datei speichern (STRG + O, Enter) und schließen (STRG + X).
4. Konfiguration neu laden:
```bash
systemctl restart netdata
```
👉 **Screenshot geeignet:** geöffnete `waha_notify.conf` mit markierten Zeilen `WEBHOOK_URL` und `WEBHOOK_BODY`
#### Test des zentralen Alarms
Um die Funktion zu prüfen, kann ein Test-Alarm ausgelöst werden:
```bash
sudo -u netdata /usr/libexec/netdata/plugins.d/alarm-notify.sh test webhook
```
Wenn die Konfiguration korrekt ist,
sollte im n8n-Log eine eingehende Anfrage erscheinen
und kurz darauf eine WhatsApp-Nachricht mit dem simulierten Alarmtext eintreffen.
👉 **Screenshot geeignet:** WhatsApp-Chat mit empfangener Testnachricht „⚠️ CPU high … (Test)“
> [!NOTE]
> Diese Konfiguration gilt global für alle über Streaming verbundenen Systeme.
> Netdata sendet bei jeder betroffenen Instanz den Hostnamen automatisch mit (`${hostname}`),
> sodass du in WhatsApp genau siehst, **welcher Container** den Alarm ausgelöst hat.
#### Erweiterung auf weitere Ressourcen
Analog zur CPU-Überwachung können weitere Alarme hinzugefügt werden.
Dazu kopierst du den Block und passt lediglich den Metrikpfad (`on:`) und Namen (`alarm:`) an.
Beispiele:
**RAM-Überwachung**
```
alarm: ucc_ram_high
on: system.ram
lookup: average -1m unaligned of used
units: %
warn: $this > 80
crit: $this > 95
exec: /usr/libexec/netdata/plugins.d/alarm-notify.sh
to: webhook
WEBHOOK_URL="https://n8n.deinedomain.tld/webhook/waha/statusalarm"
WEBHOOK_HEADER="Content-Type: application/json"
WEBHOOK_BODY="{\"host\":\"${hostname}\",\"alarm\":\"RAM high\",\"status\":\"${status}\",\"value\":\"${value}\",\"when\":\"$(date -Iseconds)\"}"
```
**Speicherplatz-Überwachung**
```
alarm: ucc_disk_low
on: disk_space._
lookup: max -5m unaligned of used
units: %
warn: $this > 85
crit: $this > 95
exec: /usr/libexec/netdata/plugins.d/alarm-notify.sh
to: webhook
WEBHOOK_URL="https://n8n.deinedomain.tld/webhook/waha/statusalarm"
WEBHOOK_HEADER="Content-Type: application/json"
WEBHOOK_BODY="{\"host\":\"${hostname}\",\"alarm\":\"Disk usage high\",\"status\":\"${status}\",\"value\":\"${value}\",\"when\":\"$(date -Iseconds)\"}"
```
> [!TIP]
> So entsteht eine skalierbare Struktur mit mehreren einfachen Health-Regeln,
> die alle denselben Webhook nutzen.
> Dadurch bleibt das System wartungsarm, aber du erhältst bei kritischen Zuständen sofort eine Nachricht auf dein Smartphone.
---
## Premium Elyra: Intelligente WhatsApp-Interaktion mit Opt-In-Trigger
In diesem Premium-Abschnitt erweiterst du dein bestehendes WAHA-Setup um **Elyra**
eine KI-Instanz, die über WhatsApp gezielt auf Nachrichten reagiert, wenn sie vom Nutzer aktiviert wird.
Dadurch bleibt die Nutzung im Rahmen der WhatsApp-Richtlinien, da Elyra niemals selbstständig Kontakt aufnimmt.
👉 **Screenshot geeignet:** WhatsApp-Chat mit „Ich brauche deine Hilfe, Elyra“ als Startbefehl und automatischer Antwort
> [!NOTE]
> Elyra ist ein optionales Modul, das lokale oder externe KI-Modelle (z. B. OpenAI, Ollama) ansprechen kann.
> Die Integration setzt ein funktionierendes WAHA- und n8n-System aus dem Free-Kapitel voraus.
>
> Der Ansatz bewegt sich in einem **zulässigen Graubereich**,
> da die Kommunikation ausschließlich **user-initiiert** erfolgt und somit kein unerlaubtes Messaging stattfindet.
### Aktivierungslogik
Elyra reagiert nur, wenn sie bewusst aktiviert wird.
Die Aktivierung erfolgt über eine eindeutige Eingabe, die als **Opt-In-Trigger** dient:
**Trigger-Phrase:**
> „Ich brauche deine Hilfe, Elyra“
Sobald diese Nachricht eingeht, aktiviert n8n den zugehörigen Workflow
und leitet den Inhalt an den KI-Knoten weiter.
Elyra verarbeitet die Anfrage, erstellt eine Antwort und sendet sie über WAHA an den Chat zurück.
### Beendigungslogik
Die Sitzung endet automatisch, wenn:
1. Der Nutzer „Danke, Elyra“ schreibt, **oder**
2. **10 Minuten** lang keine neue Nachricht eingeht.
Beide Bedingungen setzen den Workflow-Status zurück,
sodass Elyra keine weiteren Nachrichten beantwortet, bis erneut ein gültiger Trigger empfangen wird.
> [!TIP]
> Dieses Verhalten entspricht dem Prinzip klassischer Sprachassistenten wie „Hey Siri“ oder „Alexa“
> und sorgt für klare Grenzen zwischen aktiver und passiver Phase.
### Beispielhafte Gesprächssequenz
```
👤 Nutzer: Ich brauche deine Hilfe, Elyra
🤖 Elyra: Natürlich. Wobei darf ich dir helfen?
👤 Nutzer: Wann ist der nächste Stream?
🤖 Elyra: Der nächste Stream findet am Freitag um 19:30 Uhr statt Thema: Disney Dreams 🌟
👤 Nutzer: Danke, Elyra
🤖 Elyra: Gern geschehen. Ich bin wieder im Ruhemodus.
```
👉 **Screenshot geeignet:** WhatsApp-Chat mit Elyra, die freundlich auf eine Zuschauerfrage reagiert und danach automatisch inaktiv wird.
#### Node 1 Webhook (Eingang: WhatsApp-Nachricht)
**Zweck:** Empfängt eingehende WhatsApp-Nachrichten, die über WAHA an diesen Workflow weitergeleitet werden.
**Node-Typ:** Webhook
**Name:** Elyra Nachrichteneingang
**Parameter:**
- **HTTP Method:** `POST`
- **Path:** `/elyra/inbound`
- **Response Mode:** `On Received`
- **Response Code:** `200`
- **Response Data:** `No Response Body`
> [!NOTE]
> Die Webhook-URL wird nach dem Speichern automatisch angezeigt, z.B.:
> `https://n8n.deinedomain.tld/webhook/elyra/inbound`
>
> Diese Adresse muss im WAHA-Backend als Ziel für eingehende Nachrichten konfiguriert werden
> üblicherweise per Weiterleitung oder Trigger-Konfiguration in deinem WhatsApp-Bot-Modul.
👉 **Screenshot geeignet:** Webhook-Node mit sichtbarem Pfad `/webhook/elyra/inbound`
#### Node 2 Function (Nachricht vorbereiten + Zeitstempel aktualisieren)
**Zweck:**
Extrahiert die `chatId` aus der eingehenden Nachricht, bereitet die Daten für den weiteren Verlauf auf
und speichert zusätzlich den Zeitpunkt der letzten Aktivität, um inaktive Sessions später automatisch beenden zu können.
**Node-Typ:** Function
**Name:** Elyra Nachricht vorbereiten
**Code:**
```js
const body = items[0].json || {};
const chatId = body.chatId || 'unbekannt';
const text = (body.text || '').trim().toLowerCase();
const timestamp = body.timestamp || new Date().toISOString();
// Zeitstempel der letzten Aktivität speichern
$global.set('elyra_lastmsg_' + chatId, Date.now());
// Standardisierte Ausgabe für den weiteren Workflow
return [{
json: {
chatId,
text,
timestamp
}
}];
```
> [!NOTE]
> Der Eintrag `elyra_lastmsg_<chatId>` ermöglicht dem separaten Cleanup-Workflow,
> inaktive Sessions nach 10 Minuten automatisch zu beenden.
> Jede eingehende Nachricht aktualisiert diesen Zeitstempel automatisch.
👉 **Screenshot geeignet:** Function-Node mit markierter Zeile `$global.set('elyra_lastmsg_' + chatId, Date.now());`
#### Node 3 IF (Trigger: Elyra deaktivieren?)
**Zweck:** Erkennt, ob der Nutzer Elyra gezielt deaktivieren will z.B. durch die Nachricht „Danke, Elyra“.
**Node-Typ:** IF
**Name:** Elyra Deaktivierung erkennen
**Bedingung:**
- **Mode:** `All`
- **Type:** `String`
- **Value 1:** `{{$json["text"]}}`
- **Operation:** `Contains`
- **Value 2:** `danke, elyra`
> [!TIP]
> Du kannst weitere Formulierungen ergänzen z.B. `tschüss elyra`, `ich bin fertig`, etc.
> Aktuell genügt ein einzelner präziser Begriff zur Deaktivierung.
👉 **Screenshot geeignet:** IF-Node mit Bedingung `text contains "danke, elyra"`
**Anschlusslogik:**
- **TRUE →** weiter mit Node 4 *Elyra Session deaktivieren*
- **FALSE →** weiter mit Node 5 *Elyra Aktivierung erkennen*
#### Node 4 Set (Elyra-Session deaktivieren)
**Zweck:** Setzt den Aktivierungsstatus der Elyra-Session für diesen Chat auf „inaktiv“.
**Node-Typ:** Set
**Name:** Elyra Session deaktivieren
**Operation:**
- **Add Field:** `elyra_active` → `false`
> [!NOTE]
> Die Variable `elyra_active` wird später mit der `chatId` kombiniert,
> um eine **sitzungsspezifische Statusvariable** zu erzeugen, z.B.:
> `elyra_active_491751234567@c.us`
👉 **Screenshot geeignet:** Set-Node mit Feld `elyra_active = false`
#### Node 5 IF (Trigger: Elyra aktivieren?)
**Zweck:** Erkennt, ob Elyra durch den Nutzer aktiviert werden soll z.B. mit „Ich brauche deine Hilfe, Elyra“.
**Node-Typ:** IF
**Name:** Elyra Aktivierung erkennen
**Bedingung:**
- **Mode:** `All`
- **Type:** `String`
- **Value 1:** `{{$json["text"]}}`
- **Operation:** `Contains`
- **Value 2:** `ich brauche deine hilfe, elyra`
> [!TIP]
> Achte auf die Kleinschreibung, wenn du den Text vorher mit `.toLowerCase()` vereinheitlichst (siehe Node 2).
> Weitere Aktivierungsphrasen kannst du über zusätzliche OR-Verzweigungen ergänzen.
👉 **Screenshot geeignet:** IF-Node mit Bedingung `text contains "ich brauche deine hilfe, elyra"`
**Anschlusslogik:**
- **TRUE →** weiter mit Node 6 *Sessionzähler prüfen*
- **FALSE →** weiter mit Node 9 *Elyra Aktivstatus prüfen*
#### Node 6 Function (Sessionzähler prüfen)
**Zweck:** Zählt die aktuell aktiven Elyra-Sessions anhand der globalen Variablen
und entscheidet, ob eine weitere Session zulässig ist (Maximalgrenze: 10).
**Node-Typ:** Function
**Name:** Elyra Sessionzähler prüfen
```js
// Aktuelle Liste aller aktiven Sessions abrufen
const allKeys = Object.keys($global);
const activeSessions = allKeys.filter(k => k.startsWith('elyra_active_') && $global.get(k) === true);
// Ergebnis als Flag setzen
item.sessionCount = activeSessions.length;
item.sessionAllowed = activeSessions.length < 10;
return [item];
```
> [!NOTE]
> Jede aktive Elyra-Session wird im weiteren Verlauf als globale Variable gespeichert, z.B.:
> `elyra_active_491751234567@c.us = true`
>
> Diese Function zählt exakt, wie viele davon derzeit auf `true` stehen.
👉 **Screenshot geeignet:** Function-Node mit markierter Zeile `item.sessionAllowed = ...`
**Anschlusslogik:**
- **→** weiter mit Node 7 *Aktivierung zulässig?* (IF)
#### Node 7 IF (Aktivierung zulässig?)
**Zweck:** Prüft, ob die Elyra-Aktivierung für diesen Nutzer erlaubt ist (d.h. weniger als 10 aktive Sessions).
**Node-Typ:** IF
**Name:** Elyra Aktivierung zulässig?
**Bedingung:**
- **Mode:** `All`
- **Type:** `Boolean`
- **Value 1:** `{{$json["sessionAllowed"]}}`
- **Operation:** `Is true`
👉 **Screenshot geeignet:** IF-Node mit Bedingung `sessionAllowed is true`
**Anschlusslogik:**
- **TRUE →** weiter mit Node 8 *Elyra Session aktivieren*
- **FALSE →** weiter mit Node 9 *Elyra Aktivstatus prüfen*
#### Node 8 Set (Elyra Session aktivieren)
**Zweck:** Aktiviert Elyra für diesen Nutzer, indem eine globale Sitzungsvariable gesetzt wird.
**Node-Typ:** Set
**Name:** Elyra Session aktivieren
**Operation:**
- **Add Field:**
- `elyra_active` → `true`
> [!NOTE]
> Die eigentliche Speicherung erfolgt erst im nächsten Schritt durch Kombination mit der `chatId`.
> Dieses Feld dient als Zwischenschritt für die globale Speicherung mit Namen wie:
> `elyra_active_491751234567@c.us`
👉 **Screenshot geeignet:** Set-Node mit Feld `elyra_active = true`
**Anschlusslogik:**
- **→** weiter mit Node 9 *Elyra Aktivstatus prüfen*
#### Node 9 IF (Elyra ist aktiv?)
**Zweck:** Prüft, ob für diesen Chat eine Elyra-Session aktiv ist.
**Node-Typ:** IF
**Name:** Elyra Aktivstatus prüfen
**Bedingung:**
- **Mode:** `All`
- **Type:** `Expression`
- **Value 1:**
```js
{{
const key = 'elyra_active_' + $json["chatId"];
return $global.get(key) === true;
}}
```
- **Operation:** `Is true`
> [!TIP]
> Diese Prüfung stellt sicher, dass Elyra nur antwortet, wenn eine Sitzung explizit aktiv ist.
> Ohne vorherige Aktivierung wird der Nutzer ignoriert oder optional weitergeleitet.
👉 **Screenshot geeignet:** IF-Node mit geöffneter Expression und `return $global.get(...) === true`
**Anschlusslogik:**
- **TRUE →** weiter mit Node 10 *Thema klassifizieren*
- **FALSE →** weiter mit Node 16 *Antwort: Elyra ist nicht aktiv*
#### Node 10 HTTP Request (Themenklassifizierung über Ollama)
**Zweck:** Übergibt die empfangene WhatsApp-Nachricht an das lokale Mistral-Modell in Ollama,
um eine präzise Themenklassifizierung zu erhalten z.B. `streamzeit`, `eventinfo`, `zeiten`, `adminanfrage`, `chatbefehl` oder `nicht zulässig`.
**Node-Typ:** HTTP Request
**Name:** Elyra Thema klassifizieren (Ollama)
**Parameter:**
- **HTTP Method:** `POST`
- **URL:** `http://ollama.deinedomain.tld:11434/api/chat`
- **Headers:**
- `Content-Type`: `application/json`
- **Body Content Type:** `JSON`
- **Body Parameters:**
```json
{
"model": "mistral",
"messages": [
{
"role": "system",
"content": "Du bist ein Klassifizierungsagent für WhatsApp-Nachrichten. Ordne die folgende Nachricht exakt einem der folgenden Themen zu:\n\n- streamzeit\n- eventinfo\n- zeiten\n- adminanfrage\n- chatbefehl\n\nWenn keine eindeutige Zuordnung möglich ist oder der Inhalt nicht erlaubt ist, antworte exakt mit: nicht zulässig."
},
{
"role": "user",
"content": "={{$json[\"text\"]}}"
}
],
"stream": false
}
```
> [!NOTE]
> Die Antwort muss **exakt** einem der oben genannten Schlüsselwörter entsprechen.
> Dadurch kannst du im nächsten Schritt zuverlässig prüfen, ob Elyra antworten darf auch bei hoher Auslastung.
👉 **Screenshot geeignet:** HTTP-Request-Node mit geöffnetem Body, markierter Promptabschnitt `"Ordne die folgende Nachricht ..."`
**Anschlusslogik:**
- **→** weiter mit Node 11 *Zugriffsprüfung: Thema + Sessionauslastung*
#### Node 11 Function (Zugriffsprüfung: Thema + Sessionauslastung)
**Zweck:** Entscheidet, ob die Anfrage trotz möglicher Überlast bearbeitet werden darf.
Bevorzugte Themen wie `streamzeit`, `eventinfo` und `zeiten` dürfen auch bei Volllast durch
alles andere wird bei Erreichen der Kapazitätsgrenze blockiert.
**Node-Typ:** Function
**Name:** Elyra Zugriff prüfen
```js
const thema = $json["message"]?.content?.toLowerCase() || "nicht zulässig";
const chatId = $json["chatId"];
// Speichern für spätere Entscheidung
item.thema = thema;
item.chatId = chatId;
// Bevorzugte Themen
const priorisiert = ["streamzeit", "eventinfo", "zeiten"];
// Aktive Sessions zählen
const allKeys = Object.keys($global);
const aktive = allKeys.filter(k => k.startsWith('elyra_active_') && $global.get(k) === true);
const sessionCount = aktive.length;
// Entscheidung
item.sessionCount = sessionCount;
item.zugriffErlaubt = sessionCount < 10 || priorisiert.includes(thema);
item.zugriffVerweigert = !item.zugriffErlaubt || thema === "nicht zulässig";
return [item];
```
> [!TIP]
> Durch diese Logik reagiert Elyra auch bei starker Auslastung noch auf einfache Standardfragen.
> Nur komplexe oder nicht erlaubte Inhalte werden in dem Fall geblockt oder verzögert.
👉 **Screenshot geeignet:** Function-Node mit sichtbarer Logik `zugriffErlaubt = sessionCount < 10 || `
**Anschlusslogik:**
- **TRUE →** weiter mit Node 12 *Antwort generieren (Ollama)*
- **FALSE →** weiter mit Node 22 *Antwort: Kapazitätsgrenze oder Thema abgelehnt*
#### Node 12 Set (Kontext für Themenverzweigung vorbereiten)
**Zweck:** Bereitet die vom Klassifizierungsmodell gelieferte Themenkategorie und alle relevanten Kontextdaten für die nachfolgende Verzweigung vor.
Dieser Node bündelt alle Variablen, die für die spätere Entscheidung im Switch-Node benötigt werden.
**Node-Typ:** Set
**Name:** Elyra Kontext vorbereiten
**Felder hinzufügen:**
- `thema` → `={{$json["message"]["content"]}}`
- `chatId` → `={{$json["chatId"]}}`
- `text` → `={{$json["text"]}}`
- `timestamp` → `={{$json["timestamp"]}}`
> [!NOTE]
> Die Variable `thema` stammt direkt aus der Klassifizierungsantwort von Mistral (Node 10).
> Diese wird im nächsten Node verwendet, um über einen `Switch`-Node gezielt auf das Thema zu reagieren.
👉 **Screenshot geeignet:** Set-Node mit Feldern `thema`, `chatId`, `text`, `timestamp`
**Anschlusslogik:**
- **→** weiter mit Node 13 *Switch: Thema auswählen*
#### Node 13 Switch (Thema auswählen und Pfad verzweigen)
**Zweck:** Leitet die Verarbeitung abhängig von der klassifizierten Themenkategorie an den passenden Ablauf weiter z.B. Datenabruf, Rechteprüfung oder feste Antwort.
**Node-Typ:** Switch
**Name:** Elyra Thema verzweigen
**Bedingungstyp:** `String`
**Vergleichswert:** `={{$json["thema"]}}`
**Cases:**
- **streamzeit** → weiter mit Node 14 *Twitch-Kalender abrufen*
- **eventinfo** → weiter mit Node 15 *Eventtext setzen*
- **zeiten** → weiter mit Node 16 *Sendezeit abrufen*
- **chatbefehl** → weiter mit Node 17 *Standardantwort setzen*
- **adminanfrage** → weiter mit Node 18 *Adminrechte prüfen*
- **nicht zulässig** → weiter mit Node 19 *Antwort: Thema nicht erlaubt*
> [!TIP]
> Der Switch kann später beliebig erweitert werden, z.B. für `netdatainfo`, `feedback`, etc.
> Die Themenbezeichnungen müssen exakt zu denen aus dem Klassifizierungs-Prompt (Node 10) passen.
👉 **Screenshot geeignet:** Switch-Node mit geöffneten CaseBedingungen `streamzeit`, `adminanfrage`, etc.
**Anschlusslogik:**
- Je nach Thema → zu Node 1419 (jeweils themenspezifisch)
#### Node 14 Execute Subworkflow (Twitch-Kalender abfragen)
**Zweck:** Führt einen ausgelagerten Workflow aus, der den nächsten geplanten Streamtermin über die Twitch-API abruft
und die Antwort in strukturierter Form (z.B. als `streaminfo`) zurückliefert.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Nächsten Stream abrufen
**Parameter:**
- **Workflow:** `elyra_next_stream` *(wird als JSON-Download bereitgestellt)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
- `timestamp`: `={{$json["timestamp"]}}`
> [!NOTE]
> Dieser SubWorkflow wird nicht im Tutorial dokumentiert, sondern separat als JSON-Datei bereitgestellt.
> Er ruft die Twitch-API auf, extrahiert den nächsten geplanten Stream
> und speichert die Information im Feld `streaminfo` für die spätere Antwortgenerierung.
👉 **Screenshot geeignet:** Execute Sub-workflow Node mit gewähltem Workflow `elyra_next_stream` und aktiver Option `Wait for Completion`
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 15 Execute Subworkflow (Event-Info abrufen)
**Zweck:** Führt einen externen Workflow aus, der die Beschreibung des nächsten geplanten Events liefert
z.B. aus einer Kalenderquelle, Textdatei oder Datenbank.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Eventinfo abrufen
**Parameter:**
- **Workflow:** `elyra_eventinfo` *(bereitgestellt als JSON-Download)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
- `timestamp`: `={{$json["timestamp"]}}`
> [!NOTE]
> Auch dieser Workflow wird **nicht im Tutorial beschrieben**, sondern als JSON-Datei zum Download bereitgestellt.
> Er liefert z.B. das Feld `eventinfo` zurück, das später in den Antwort-Prompt eingebaut werden kann.
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow-Namen `elyra_eventinfo` und gesetzten Parametern
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 16 Execute Subworkflow (Sendezeit abrufen)
**Zweck:** Führt einen ausgelagerten Workflow aus, der die aktuelle oder nächste Sendezeit ermittelt
z.B. über eine ICS-Datei, einen festgelegten Wochenplan oder eine API-basierte Quelle.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Sendezeit abrufen
**Parameter:**
- **Workflow:** `elyra_zeiten` *(bereitgestellt als JSON-Download)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
- `timestamp`: `={{$json["timestamp"]}}`
> [!NOTE]
> Der Workflow `elyra_zeiten` wird separat als JSON bereitgestellt
> und extrahiert z.B. aus einem Kalender die geplante Startzeit des Streams.
> Die Ausgabe erfolgt idealerweise als String im Feld `sendezeit`.
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow `elyra_zeiten` und gesetzten Übergabeparametern
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 17 Execute Subworkflow (Chatbefehle aus StreamElements abrufen)
**Zweck:** Führt einen ausgelagerten Workflow aus, der die öffentlich zugängliche StreamElementsCommandsSeite abruft,
alle verfügbaren Chatbefehle extrahiert und als JSON-String bereitstellt.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Chatbefehle laden
**Parameter:**
- **Workflow:** `elyra_chatcommands` *(bereitgestellt als JSON-Download)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
> [!NOTE]
> Dieser SubWorkflow ruft z.B. `https://streamelements.com/bratonien_tv/commands` auf,
> analysiert das HTML und extrahiert die Befehle samt Beschreibung als JSONObjekt.
> Das Ergebnis wird später an Ollama übergeben, damit Elyra kontextbezogen passende Befehle vorschlagen kann.
>
> Auch andere Anbieter wie **Nightbot**, **Moobot** oder **Botisimo** stellen vergleichbare Listen zur Verfügung
> im Rahmen dieses Tutorials verwenden wir jedoch ausschließlich **StreamElements** als zentrale Bot-Instanz.
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow-Namen `elyra_chatcommands`
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 18 Execute Subworkflow (Adminrechte prüfen)
**Zweck:** Führt einen externen Workflow aus, der prüft, ob der aktuelle Absender (`chatId`) zu den autorisierten Admins gehört.
Nur wenn das zutrifft, wird die Anfrage weiterverarbeitet sonst erhält der Nutzer eine entsprechende Ablehnung.
**Node-Typ:** Execute Sub-workflow
**Name:** Elyra Adminrechte prüfen
**Parameter:**
- **Workflow:** `elyra_admincheck` *(bereitgestellt als JSON-Download)*
- **Options → Wait for Completion:** `true`
- **Options → Variables:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
> [!NOTE]
> Der Sub-Workflow `elyra_admincheck` vergleicht die `chatId` mit einer internen Liste autorisierter Nutzer
> (z.B. als JSON-Datei, Umgebungsvariable oder Datenbankeintrag).
> Ist der Nutzer nicht autorisiert, wird das Feld `admin_erlaubt` auf `false` gesetzt und die Anfrage verworfen.
👉 **Screenshot geeignet:** Execute Sub-workflow mit Workflow `elyra_admincheck` und Variable `chatId`
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 19 Set (Ablehnungsprompt für nicht erlaubtes Thema)
**Zweck:** Bereitet den Prompt für eine freundlich-bestimmte Ablehnungsantwort durch Ollama vor.
Dies erfolgt, wenn das Thema vom Klassifizierungsmodell als `nicht zulässig` eingestuft wurde.
**Node-Typ:** Set
**Name:** Elyra Thema abgelehnt (Prompt vorbereiten)
**Felder hinzufügen:**
- `chatId`: `={{$json["chatId"]}}`
- `text`: `={{$json["text"]}}`
- `ablehnungskontext`:
```text
Die folgende Anfrage konnte keinem zugelassenen Thema zugeordnet werden.
Formuliere eine kurze, aber klare Antwort, die dem Nutzer freundlich mitteilt,
dass diese Art von Anfrage aktuell nicht unterstützt wird.
Die Antwort soll sachlich bleiben, aber höflich und professionell wirken.
```
👉 **Screenshot geeignet:** Set-Node mit sichtbarem Feld `ablehnungskontext`
**Anschlusslogik:**
- **→** weiter mit Node 20 *Antwort generieren (Ollama mit Kontext)*
#### Node 20 HTTP Request (Antwort generieren Ollama)
**Zweck:** Sendet die Nutzeranfrage zusammen mit dem relevanten Kontext (z.B. Chatbefehle, Eventinfos, Ablehnungshinweis)
an das lokale Sprachmodell **Mistral** über den Ollama-Endpunkt und erhält eine formulierte Antwort zurück.
**Node-Typ:** HTTP Request
**Name:** Elyra Antwort generieren (Ollama)
**Parameter:**
- **Method:** `POST`
- **URL:**
```
http://ollama.<deine-domain>.tld/api/generate
```
- **Headers:**
- `Content-Type`: `application/json`
- **Body → JSON:**
```json
{
"model": "mistral",
"stream": false,
"prompt": "{{$json['ablehnungskontext'] || ''}}{{$json['kontext'] || ''}}\n\nNachricht:\n{{$json['text']}}"
}
```
> [!NOTE]
> Der Prompt setzt sich aus verschiedenen Quellen zusammen je nach Pfad:
> bei `chatbefehl`: Liste aller Chatkommandos (`kontext`)
> bei `eventinfo` oder `sendezeit`: Zusatzinfos aus Sub-Workflows
> bei `nicht erlaubt`: Ablehnungshinweis (`ablehnungskontext`)
>
> Der finale Prompt enthält immer den Originaltext des Nutzers.
> [!TIP]
> Du kannst über die `prompt:`-Zusammensetzung bei Bedarf eigene Regeln einbauen,
> z.B. Priorisierungen, stilistische Hinweise oder Sprachwechsel.
👉 **Screenshot geeignet:** HTTP-Request-Node mit Body-Feld `prompt`
**Anschlusslogik:**
- **→** weiter mit Node 21 *Antwort an WAHA senden*
#### Node 21 HTTP Request (Antwort über WAHA senden)
**Zweck:** Sendet die generierte Antwort als WhatsApp-Nachricht über die WAHAAPI an den ursprünglichen Absender.
**Node-Typ:** HTTP Request
**Name:** Elyra Antwort senden (WAHA)
**Parameter:**
- **Method:** `POST`
- **URL:**
```
https://waha.<deine-domain>.tld/api/sendText
```
- **Headers:**
- `Content-Type`: `application/json`
- `Authorization`: `Bearer <DEIN_WAHA_API_TOKEN>`
- **Body → JSON:**
```json
{
"chatId": "={{$json[\"chatId\"]}}",
"text": "={{$json[\"antwort\"] || $json[\"text\"]}}"
}
```
> [!IMPORTANT]
> Der **API-Token ist zwingend erforderlich**, damit WAHA die Anfrage akzeptiert.
> Du findest ihn im WAHA-Webinterface unter **Settings → API → Token**.
> Ohne gültigen Token gibt die API den Fehlercode `401 Unauthorized` zurück.
👉 **Screenshot geeignet:** HTTP-Request-Node mit gesetztem AuthorizationHeader
**Anschlusslogik:**
→ Ende des Haupt-Workflows
#### Node 22 Set (Ablehnungsprompt: Ressourcenmangel)
**Zweck:**
Bereitet den Prompt für eine höfliche, aber bestimmte Ablehnungsantwort durch **Ollama** vor,
wenn das System aktuell keine ausreichenden Ressourcen (z. B. Rechenkapazität oder Slots) zur Bearbeitung hat.
**Node-Typ:**
Set
**Name:**
Elyra Thema abgelehnt (Ressourcenmangel)
**Felder hinzufügen:**
- `chatId`:
`={{$json["chatId"]}}`
- `text`:
`={{$json["text"]}}`
- `ablehnungskontext`:
```text
Die Anfrage kann derzeit nicht bearbeitet werden, da die verfügbaren Ressourcen
(z. B. Rechenkapazität oder Ausführungsslots) vorübergehend ausgelastet sind.
Formuliere eine höfliche, aber klare Antwort, die den Nutzer darüber informiert,
dass Elyra aktuell keine weiteren Anfragen annehmen kann.
Bitte den Nutzer freundlich um Verständnis und erkläre,
dass er es zu einem späteren Zeitpunkt erneut versuchen kann.
```
👉 **Screenshot geeignet:**
Set-Node mit sichtbarem Feld `ablehnungskontext`
**Anschlusslogik:**
→ weiter mit **Node 20 Antwort generieren (Ollama mit Kontext)**
### Workflow: Elyra Session Cleanup (automatische Sitzungsbeendigung)
**Zweck:**
Beendet inaktive Elyra-Sessions automatisch, wenn über 10 Minuten keine Nachricht mehr eingegangen ist.
Dadurch werden belegte Slots im UCC wieder freigegeben und die Systemkapazität bleibt stabil.
Der Workflow wird alle **5 Minuten** automatisch gestartet.
Er durchsucht den n8n-Global-Store nach aktiven Sessions (`elyra_active_<chatId>`)
und prüft anhand des zugehörigen Zeitstempels (`elyra_lastmsg_<chatId>`),
ob die letzte Nutzerinteraktion länger als 10 Minuten her ist.
Falls ja, wird die Session beendet.
👉 **Screenshot geeignet:** Diagramm mit Cron-Trigger → Function-Node „Inaktive Sessions schließen“
#### Node 1 Cron (Trigger)
**Zweck:**
Startet den Workflow regelmäßig, um alte Sessions zu bereinigen.
**Node-Typ:** Cron
**Name:** Elyra Cleanup Trigger
**Parameter:**
- **Mode:** Every X Minutes
- **Every:** `5`
> [!NOTE]
> Der Workflow läuft alle 5 Minuten automatisch.
> Du kannst das Intervall bei Bedarf anpassen z. B. auf 2 Minuten für sehr aktive Systeme.
👉 **Screenshot geeignet:** Cron-Node mit „Every 5 Minutes“
**Anschlusslogik:**
→ weiter mit **Node 2 Inaktive Sessions schließen**
#### Node 2 Function (Inaktive Sessions schließen)
**Zweck:**
Überprüft alle global gespeicherten Elyra-Sessions und deaktiviert diejenigen,
bei denen seit mehr als 10 Minuten keine Aktivität registriert wurde.
**Node-Typ:** Function
**Name:** Elyra Inaktive Sessions schließen
**Code:**
```js
const allKeys = Object.keys($global);
const now = Date.now();
const timeout = 10 * 60 * 1000; // 10 Minuten
for (const key of allKeys) {
if (key.startsWith('elyra_active_') && $global.get(key) === true) {
const chatId = key.replace('elyra_active_', '');
const lastMsg = $global.get('elyra_lastmsg_' + chatId) || 0;
if (now - lastMsg > timeout) {
$global.set(key, false);
}
}
}
return [];
```
> [!TIP]
> Die Timeout-Dauer (`timeout`) lässt sich frei anpassen.
> Standardwert sind 10 Minuten (600 000 ms).
> Für Livestream- oder Event-Kontexte sind 510 Minuten in der Regel sinnvoll.
👉 **Screenshot geeignet:** Function-Node mit geöffnetem Code-Editor, markierte Zeile `if (now - lastMsg > timeout)`
**Anschlusslogik:**
→ Ende des Workflows
---
## Abschluss WAHA Premium
Mit dem Abschluss dieses Premium-Kapitels hast du WAHA vollständig in dein UCC integriert.
Automatisierte Statusmeldungen via n8n und Netdata sowie eine optionale KI-gestützte Antwortfunktion machen dein System nicht nur informativ, sondern auch interaktiv.
### Ergebnisse dieses Kapitels
| Bereich | Umsetzung |
|--------|-----------|
| Statusbenachrichtigung | Netdata-Alarme werden über n8n an WAHA weitergeleitet du bekommst Warnungen & Fehler direkt auf WhatsApp. |
| KI-Antwortfunktion | Eingehende Nachrichten aktivieren auf Wunsch deine KI-Instanz („Elyra“) und erhalten automatisierte Antworten lokal und datensouverän. |
| Infrastruktur-Integration | Alles läuft innerhalb deines UCC-Systems: keine externe Cloud-API (sofern gewünscht), volle Kontrolle über Daten und Prozesse. |
> **Tip:**
> Dieses Setup lässt sich modular erweitern z. B. um zusätzliche Trigger, weitere Chat-Kanäle oder erweiterte KI-Funktionalitäten.
> Dank klarer Trennung und Standardisierung bleibt dein System wartungsarm und skalierbar.
Damit ist dieses Kapitel abgeschlossen.
Dein UCC verfügt nun über ein vollwertiges, internes WhatsApp-Kommunikations- und Benachrichtigungssystem von den ersten Setup-Schritten bis zur automatisierten Interaktion.
Viel Erfolg beim Betrieb und bei weiteren Erweiterungen!