From 996722e0666281f72146bc466199921c64b4fc45 Mon Sep 17 00:00:00 2001 From: Thomas Dannenberg Date: Thu, 9 Oct 2025 19:42:33 +0000 Subject: [PATCH] Kapitel 10/Free Rhohtext.md aktualisiert --- Kapitel 10/Free Rhohtext.md | 216 +++++++++++++++++++++++++++++++++++- 1 file changed, 215 insertions(+), 1 deletion(-) diff --git a/Kapitel 10/Free Rhohtext.md b/Kapitel 10/Free Rhohtext.md index eb78d52..ed87f0d 100644 --- a/Kapitel 10/Free Rhohtext.md +++ b/Kapitel 10/Free Rhohtext.md @@ -421,6 +421,44 @@ Ein händisches Nachtragen würde bedeuten, dass der Workflow regelmäßig ausf Wir werden deshalb im **Verlauf des Workflows** einen eigenen Node hinzufügen, der den Access Token automatisch anfordert und bei Bedarf erneuert. So bleibt der Workflow stabil und benötigt keine manuelle Pflege. +### 5.9 – Nextcloud-Credentials anlegen + +**Zweck:** +Damit n8n automatisch auf Dateien in deiner Nextcloud zugreifen kann (z. B. für vorbereitete Posts oder temporäre Daten), benötigen wir ein eigenes App-Passwort und ein passendes Credential in n8n. + +#### In Nextcloud: App-Passwort erstellen + +1) Melde dich in deiner **Nextcloud** an. +2) Öffne oben rechts das **Benutzermenü → Einstellungen**. +3) Wähle im linken Menü den Punkt **Sicherheit** (engl. „Security“). +4) Scrolle nach unten bis zum Bereich **App-Passwörter**. +5) Vergib unter „Neues App-Passwort“ einen Namen, z. B. `n8n`. +6) Klicke auf **App-Passwort erstellen**. +7) Kopiere den angezeigten Schlüssel sofort – er wird nur einmal angezeigt. + 👉 Screenshot geeignet: Nextcloud – Bereich *Sicherheit / App-Passwörter* + +[!IMPORTANT] +Nur dieses **App-Passwort** wird in n8n verwendet, **nicht** dein normales Login-Passwort. +Das App-Passwort kann bei Bedarf jederzeit widerrufen oder neu erstellt werden. + +#### In n8n: Credential hinzufügen + +1) Öffne in n8n oben links **+ → Credentials → Nextcloud**. +2) Trage folgende Werte ein: + +| Feld | Wert / Beschreibung | +|------|---------------------| +| **Name** | `Nextcloud` | +| **Base URL** | `https://deine-domain.tld` *(ohne `/remote.php/...`)* | +| **Username** | dein Nextcloud-Benutzername | +| **Password** | das zuvor erstellte App-Passwort | + +3) Klicke auf **Save**. +👉 Screenshot geeignet: n8n – Nextcloud-Credential mit Base URL und App-Passwort + +[!NOTE] +Das Credential „Nextcloud“ wird später in Schritt **6.13** verwendet, um Dateien wie `posts.txt` automatisiert aus der Cloud zu laden und lokal zu verarbeiten. + ## Schritt 6 – Workflow in n8n aufbauen In diesem Schritt erstellen wir den eigentlichen Workflow in **n8n**. @@ -809,4 +847,180 @@ return out; [!NOTE] - `tz` ist fest auf `Europe/Berlin` gesetzt, kann aber bei Bedarf angepasst werden. - Das Feld `cancelled` wird genutzt, um abgesagte Streams zu kennzeichnen. -- Jeder Stream-Eintrag erhält einen Hash, der später als eindeutiger Schlüssel dient. \ No newline at end of file +- Jeder Stream-Eintrag erhält einen Hash, der später als eindeutiger Schlüssel dient. + +### 6.10 – Code-Node: „Nächstes Event finden“ + +**Zweck:** +Dieser Node wählt aus allen vom **„Parse ICS“**-Node gelieferten Terminen den **nächsten bevorstehenden Stream** aus. +Er liefert das Start-Datum und die relevanten Infos des nächsten Events als einzelnes Item. + +**Node-Typ:** `Code` +**Name:** `Nächstes Event finden` + +👉 Screenshot geeignet: n8n – geöffneter Code-Node mit dem folgenden Script + +#### Node-Einstellungen +- **Programming Language:** `JavaScript` +- Alle weiteren Optionen bleiben auf Standard. + +#### Eingabe +Dieser Node erwartet als Input die komplette Liste der Stream-Segmente aus dem Node **„Parse ICS“**. + +#### Code-Inhalt +Kopiere den folgenden Code vollständig in das Code-Feld: + +``` +/** + * INPUT: Array von Items aus "Parse ICS" + * OUTPUT: nur das nächste bevorstehende Event + */ + +const now = Date.now(); + +const upcoming = items + .map(i => i.json) + .filter(e => !e.cancelled && new Date(e.startIso).getTime() > now) + .sort((a, b) => new Date(a.startIso) - new Date(b.startIso)); + +if (upcoming.length === 0) { + return [{ json: { message: 'Kein bevorstehender Stream gefunden' } }]; +} + +const next = upcoming[0]; + +return [{ + json: { + uid: next.uid, + title: next.title, + description: next.description, + startIso: next.startIso, + endIso: next.endIso, + tz: next.tz, + hash: next.hash + } +}]; +``` + +[!TIP] +Dieser Node gibt **genau ein Item** mit dem nächsten Stream zurück. +Fehlt ein Termin, wird eine kurze Meldung im Feld `message` ausgegeben. + +### 6.11 – Code-Node: „Stream prüfen“ + +**Zweck:** +Dieser Node überprüft, ob der nächste geplante Stream in den kommenden **30 Minuten** beginnt und ob für diesen Stream bereits ein Post veröffentlicht wurde. +Nur wenn beide Bedingungen erfüllt sind, wird der Workflow fortgesetzt. + +**Node-Typ:** `Code` +**Name:** `Stream prüfen` + +👉 Screenshot geeignet: n8n – geöffneter Code-Node mit folgendem Script + +#### Node-Einstellungen +- **Programmiersprache:** `JavaScript` +- Keine weiteren Optionen ändern + +#### Eingabe +Der Node erhält den Output aus **„Nächstes Event finden“** – also einen einzelnen Stream-Eintrag mit `uid`, `title`, `startIso` und weiteren Feldern. + +#### Code-Inhalt +Kopiere den folgenden Code: + +``` +/** + * Prüft: + * 1. Startet der Stream in ≤ 30 Minuten? + * 2. Wurde bereits ein Post erstellt? + * + * Zeitumwandlung: Twitch liefert UTC ("2025-10-15T16:00:00Z"). + * Wir rechnen in lokale Zeit (Europe/Berlin), um MEZ/MESZ korrekt zu berücksichtigen. + */ + +const event = items[0]?.json ?? {}; +if (!event.startIso) { + return [{ + json: { + postAllowed: false, + reason: 'Kein Termin gefunden' + } + }]; +} + +// Aktuelle Zeit + Startzeit in lokale Zeitzone überführen +const tz = 'Europe/Berlin'; +const now = new Date(); // lokale Zeit des n8n-Servers +const startUtc = new Date(event.startIso); + +// Startzeit in lokales Format (inkl. Sommer-/Winterzeit) +const startLocal = new Date(startUtc.toLocaleString('en-US', { timeZone: tz })); + +// Differenz in Minuten +const diffMin = (startLocal - now) / 60000; +const within30 = diffMin > 0 && diffMin <= 30; + +// Zugriff auf global gespeicherte IDs +const sd = $getWorkflowStaticData('global'); +sd.postedIds = sd.postedIds || []; + +const alreadyPosted = sd.postedIds.includes(event.uid); + +// Wenn innerhalb von 30 Minuten und noch kein Post → erlauben +if (within30 && !alreadyPosted) { + sd.postedIds.push(event.uid); + return [{ + json: { + ...event, + postAllowed: true, + reason: 'Stream startet bald, noch kein Post vorhanden', + localStart: startLocal.toISOString(), + diffMinutes: Math.round(diffMin) + } + }]; +} + +// Keine Aktion notwendig +return [{ + json: { + ...event, + postAllowed: false, + reason: alreadyPosted + ? 'Für diesen Stream wurde bereits gepostet' + : 'Stream liegt außerhalb des 30-Minuten-Fensters', + localStart: startLocal.toISOString(), + diffMinutes: Math.round(diffMin) + } +}]; +``` + +[!NOTE] +Dieser Node speichert die **UID jedes angekündigten Streams** im Workflow-Speicher (`global static data`). +So wird sichergestellt, dass kein Termin doppelt angekündigt wird – auch nicht nach einem Neustart des Systems. +Der Zeitvergleich erfolgt in **MEZ/MESZ (Europe/Berlin)**, damit lokale Streamzeiten korrekt erkannt werden. + +### 6.12 – IF-Node: „Post ausführen?“ + +**Zweck:** +Der IF-Node entscheidet, ob tatsächlich ein Social-Post (z. B. auf X) ausgelöst werden soll. +Er prüft das Feld `postAllowed`, das aus dem vorherigen Code-Node stammt. + +**Node-Typ:** `IF` +**Name:** `Post ausführen?` + +👉 Screenshot geeignet: n8n – geöffneter IF-Node mit konfigurierter Bedingung + +#### Node-Einstellungen + +**Conditions** + +| Feld | Einstellung | +|------|--------------| +| **Left Value** | `{{$json.postAllowed}}` | +| **Operator** | `is true` | +| **Type** | `Boolean` | + +[!NOTE] +- Der *True*-Pfad wird nur ausgeführt, wenn der Stream innerhalb der nächsten 30 Minuten startet **und** noch kein Post gesendet wurde. +- Der *False*-Pfad kann später genutzt werden, um Debug-Logs oder Benachrichtigungen zu schreiben (z. B. „Kein Post nötig“). + +👉 Screenshot geeignet: n8n – IF-Node mit `{{$json.postAllowed}} is true (Boolean)` \ No newline at end of file