Kapitel 13/Tutorial.md aktualisiert

This commit is contained in:
2025-08-30 10:10:45 +00:00
parent 34ba55637f
commit f280385d54

View File

@@ -222,7 +222,7 @@ Mit dieser Einrichtung sind **SSHSchlüssel**, **Berechtigungen** und **Pfade
In diesem Abschnitt verbinden wir n8n mit Twitch, Nextcloud und dem Clipper. Das Ziel: n8n erkennt automatisch neue VODs auf Twitch, lädt sie zusammen mit Clips herunter, legt sie in Nextcloud ab und startet dann die Analyse auf dem Clipper. Wir gehen Schritt für Schritt vor immer mit klaren Hinweisen, ob wir uns gerade in der **n8n-Weboberfläche**, im **Terminal** oder in **Nextcloud** befinden. In diesem Abschnitt verbinden wir n8n mit Twitch, Nextcloud und dem Clipper. Das Ziel: n8n erkennt automatisch neue VODs auf Twitch, lädt sie zusammen mit Clips herunter, legt sie in Nextcloud ab und startet dann die Analyse auf dem Clipper. Wir gehen Schritt für Schritt vor immer mit klaren Hinweisen, ob wir uns gerade in der **n8n-Weboberfläche**, im **Terminal** oder in **Nextcloud** befinden.
> [!HINWEIS] > [!NOTE]
> **Rollenverteilung** > **Rollenverteilung**
> **n8n** steuert (APIs, Logik, Benachrichtigungen). **Clipper** arbeitet (Download, Analyse, Schnitt). **Nextcloud** speichert (Archiv & Übergabe). So bleibt n8n schlank und ausfallsicher, während Clipper CPU/IO für Medienjobs bekommt. > **n8n** steuert (APIs, Logik, Benachrichtigungen). **Clipper** arbeitet (Download, Analyse, Schnitt). **Nextcloud** speichert (Archiv & Übergabe). So bleibt n8n schlank und ausfallsicher, während Clipper CPU/IO für Medienjobs bekommt.
@@ -259,7 +259,7 @@ ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
ssh-keygen -R "<CLIPPER-IP>" ssh-keygen -R "<CLIPPER-IP>"
``` ```
> **Entscheidung & Begründung Sicherheitsprinzip** > [!NOTE]
> SSH schützt vor Man-in-the-Middle-Angriffen, indem es Hostkeys prüft. Wenn ein Container neu erstellt wird, ist ein neuer Hostkey normal. Wir löschen den alten Eintrag bewusst, prüfen den neuen Fingerprint und akzeptieren ihn erst danach. So bleibt die Verbindung sicher **und** stabil. > SSH schützt vor Man-in-the-Middle-Angriffen, indem es Hostkeys prüft. Wenn ein Container neu erstellt wird, ist ein neuer Hostkey normal. Wir löschen den alten Eintrag bewusst, prüfen den neuen Fingerprint und akzeptieren ihn erst danach. So bleibt die Verbindung sicher **und** stabil.
</details> </details>
@@ -273,8 +273,8 @@ ssh -i ~/.ssh/id_n8n_clipper clipper@<CLIPPER-IP> "echo OK"
Ersetze `<CLIPPER-IP>` durch die IP/den Hostnamen deines Clipper-LXC (z. B. `10.0.0.42`). Wenn **OK** erscheint, kann n8n ohne Passwort auf den Clipper zugreifen. Ersetze `<CLIPPER-IP>` durch die IP/den Hostnamen deines Clipper-LXC (z. B. `10.0.0.42`). Wenn **OK** erscheint, kann n8n ohne Passwort auf den Clipper zugreifen.
> **Entscheidung & Begründung RSA (PEM) statt ed25519** > [!NOTE]
> n8n akzeptiert nur PEM-Schlüssel zuverlässig. Ed25519 erzeugt oft OpenSSH-Keys, die von n8n nicht geparst werden können. Mit `-m PEM` stellen wir sicher, dass das Format passt. > RSA (PEM) statt ed25519: n8n akzeptiert nur PEM-Schlüssel zuverlässig. Ed25519 erzeugt oft OpenSSH-Keys, die von n8n nicht geparst werden können. Mit `-m PEM` stellen wir sicher, dass das Format passt.
--- ---
@@ -319,6 +319,7 @@ https://<DEINE_NEXTCLOUD_DOMAIN>/remote.php/dav/files/<DEIN_NC_BENUTZER>/
* **Scope:** leer lassen * **Scope:** leer lassen
* **Authentication:** `Body` * **Authentication:** `Body`
--- ---
### Schritt 2: Twitch-User-ID herausfinden (n8n-Weboberfläche) ### Schritt 2: Twitch-User-ID herausfinden (n8n-Weboberfläche)
@@ -340,315 +341,221 @@ Im Ergebnis findest du im Feld `data[0].id` deine **User-ID** (z. B. `123456789`
### Schritt 3: Workflow bauen (n8n-Weboberfläche) ### Schritt 3: Workflow bauen (n8n-Weboberfläche)
1. **Cron-Trigger:** alle 10 Minuten In diesem Schritt erstellen wir den eigentlichen Workflow in **n8n**. Er sorgt dafür, dass regelmäßig neue VODs bei Twitch abgefragt, geprüft und anschließend verarbeitet werden.
2. **HTTP Request (Get Videos):** > [!NOTE]
> **Ziel dieses Schrittes**
> Wir bauen den Ablauf in n8n: Cron-Trigger → Twitch API → Prüfung der State-Datei → Entscheidung über neue VODs → Download und Upload in Nextcloud.
---
1. **Cron-Trigger Zeitplan festlegen** (Node-Name: `Cron Alle 10 Min`)
- Dieser Node löst den Workflow regelmäßig aus.
- Stelle ein: alle **10 Minuten** ausführen.
2. **HTTP Request Twitch Videos abfragen** (Node-Name: `HTTP Get Videos`)
- Dieser Node ruft über die Twitch-API alle VODs deines Accounts ab.
- URL:
``` ```
https://api.twitch.tv/helix/videos?user_id=<DEINE_TWITCH_USER_ID>&type=archive https://api.twitch.tv/helix/videos?user_id=<DEINE_TWITCH_USER_ID>&type=archive
``` ```
- Authentifizierung: OAuth2 (Credential „Twitch API“)
- Header: `Client-Id: <DEINE_CLIENT_ID>`
3. **SSH Credentials in n8n anlegen** 3. **SSH Credentials in n8n anlegen**
Damit n8n mit dem Clipper arbeiten kann, legen wir zunächst ein SSH Credential an: - Damit n8n mit dem Clipper kommunizieren kann, brauchst du SSH-Zugangsdaten.
- Trage in n8n ein:
* **Name:** SSH Clipper * Name: `SSH Clipper`
* **Host:** `<CLIPPER-IP>` (z. B. `10.0.0.42`) * Host: `<CLIPPER-IP>` (z. B. `10.0.0.42`)
* **Port:** 22 * Port: `22`
* **Username:** `clipper` * Username: `clipper`
* **Private Key:** Inhalt von `~/.ssh/id_n8n_clipper` (PEM-Format) * Private Key: Inhalt von `~/.ssh/id_n8n_clipper`
* **Working Directory:** `/srv/clipper` * Working Directory: `/srv/clipper`
4. **SSH Node State-Datei prüfen**
Jetzt prüfen wir, ob die Datei `/srv/clipper/state/vod_seen.list` existiert und ob bereits VOD-IDs darin stehen.
**Command (als Expression):**
4. **SSH Node State-Datei prüfen** (Node-Name: `SSH Check State`)
- Dieser Schritt prüft, ob die Datei `/srv/clipper/state/vod_seen.list` bereits existiert und ob VOD-IDs eingetragen sind.
- Command (als Expression):
```js ```js
{{`set -euo pipefail; STATE_FILE="/srv/clipper/state/vod_seen.list"; fe=false; ne=false; arr='[]'; if [ -f "$STATE_FILE" ]; then fe=true; if [ -s "$STATE_FILE" ]; then ne=true; mapfile -t L < "$STATE_FILE"; json='['; sep=''; for id in "\${L[@]}"; do id_trim="$(printf '%s' "$id" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"; [ -n "$id_trim" ] || continue; json+="$sep\"$id_trim\""; sep=','; done; json+=']'; arr="$json"; fi; fi; printf '{"file_exists":%s,"non_empty":%s,"vods":%s}\n' "$fe" "$ne" "$arr"`}} {{`set -euo pipefail; STATE_FILE="/srv/clipper/state/vod_seen.list"; fe=false; ne=false; arr='[]'; if [ -f "$STATE_FILE" ]; then fe=true; if [ -s "$STATE_FILE" ]; then ne=true; mapfile -t L < "$STATE_FILE"; json='['; sep=''; for id in "\${L[@]}"; do id_trim="$(printf '%s' "$id" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"; [ -n "$id_trim" ] || continue; json+="$sep\"$id_trim\""; sep=','; done; json+=']'; arr="$json"; fi; fi; printf '{"file_exists":%s,"non_empty":%s,"vods":%s}\n' "$fe" "$ne" "$arr"`}}
``` ```
- Typische Ergebnisse:
**Beispiele für den Output:**
```json ```json
{"file_exists":false,"non_empty":false,"vods":[]} {"file_exists":false,"non_empty":false,"vods":[]}
{"file_exists":true,"non_empty":false,"vods":[]} {"file_exists":true,"non_empty":false,"vods":[]}
{"file_exists":true,"non_empty":true,"vods":["123456789","987654321"]} {"file_exists":true,"non_empty":true,"vods":["123456789","987654321"]}
``` ```
5. ** Set Node Felder übernehmen **
### Set Node Felder übernehmen 5. **Set Node Ergebnisse übernehmen** (Node-Name: `Set State Ergebnis`)
- Ziel: die Ausgabe aus dem vorherigen Schritt in Felder umwandeln, die n8n versteht.
file_exists → ```json {{ JSON.parse($json.stdout).file_exists }} ``` - Expressions:
non_empty → ```json {{ JSON.parse($json.stdout).non_empty }} ``` * file_exists → `{{ JSON.parse($json.stdout).file_exists }}`
vods → ```json {{ JSON.parse($json.stdout).vods }} ``` * non_empty → `{{ JSON.parse($json.stdout).non_empty }}`
* vods → `{{ JSON.parse($json.stdout).vods }}`
> [!IMPORTANT] > [!IMPORTANT]
> Stelle die Felder auf den Typ Array bzw. Boolean um. > Hier nur die Expressions eintragen, keine Typ-Konvertierungen vornehmen.
> Aktiviere die Option: Add Option → Ignore Type Conversion Errors → einschalten.
6. **If-Node Entscheidung** (Node-Name: `If State Prüfung`)
6. ** If-Node zwei klare Wege ** - Dieser Node entscheidet, ob neue VODs heruntergeladen werden müssen.
- Bedingungen:
In diesem Schritt legen wir die Entscheidungspfade in einem **If-Node** an. Dafür werden zwei Bedigungen hinzugefügt, die jeweils einen klar benannten Output erzeugen.
### If-Node Einstellungen
```git
1. Bedingung: {{ $json.file_exists }} is true
- Field type: Boolean
2. Bedingung: {{ $json.non_empty }} is true
- Field type: Boolean
3. Operator: AND
``` ```
Damit hat der if-Node zwei klar angesprochene Ausgänge, die die weitere Logik steuern. 1. Bedingung: {{ $json.file_exists }} is true (Boolean)
2. Bedingung: {{ $json.non_empty }} is true (Boolean)
7. ** False - Download ** Operator: AND
Ziel: n8n erkennt neue VODs, erstellt/aktualisiert die State-Datei und lädt jedes VOD **sequenziell** im Clipper herunter und direkt in **Nextcloud** hoch (mit automatischem Aufräumen und Logfiles). Alles wird von n8n gesteuert, die Dateiübertragung übernimmt **rclone** im Clipper. ```
- Ergebnis: zwei mögliche Pfade → False = neu anlegen, True = prüfen auf neue VODs.
--- ---
**A. Vorbereitung (einmalig) rclone an Nextcloud anbinden** 7. **False-Pfad VODs herunterladen und hochladen**
Ort: *ClipperLXC Shell*
1) rclone installieren (als root) > [!NOTE]
> Dieser Pfad läuft, wenn die State-Datei nicht existiert oder leer ist. Es werden alle VODs heruntergeladen und in Nextcloud hochgeladen.
**A. Vorbereitung (einmalig) rclone an Nextcloud anbinden**
**Ort:** Clipper-LXC Shell
```bash ```bash
apt update && apt install -y rclone apt update && apt install -y rclone
```
2) Zum Benutzer **clipper** wechseln
```bash
su - clipper su - clipper
```
3) Remote **nc** anlegen (Passwort wird automatisch obskuriert)
```bash
rclone config create nc webdav \ rclone config create nc webdav \
url=https://DEINE_DOMAIN/remote.php/dav/files/DEIN_BENUTZERNAME/ \ url=https://DEINE_DOMAIN/remote.php/dav/files/DEIN_BENUTZERNAME/ \
vendor=nextcloud \ vendor=nextcloud \
user=DEIN_BENUTZERNAME \ user=DEIN_BENUTZERNAME \
pass=$(rclone obscure 'DEIN_APP_PASSWORT') pass=$(rclone obscure 'DEIN_APP_PASSWORT')
```
4) Verbindung testen
```bash
rclone ls nc: --config /home/clipper/.config/rclone/rclone.conf rclone ls nc: --config /home/clipper/.config/rclone/rclone.conf
``` ```
> **Entscheidung & Begründung** > [!NOTE]
> Uploads laufen im Clipper mit rclone (WebDAV). Das ist robuster als UploadNodes in n8n, vermeidet BinaryFehler und räumt lokal automatisch auf. > Uploads laufen im Clipper mit rclone. Das ist robuster als Upload-Nodes in n8n, vermeidet Fehler und räumt lokale Dateien auf.
--- **B. Download/Upload Skript erstellen**
**Ort:** Clipper-LXC Shell
**B. Download/Upload als Skript (übersichtlich & mit Logs)**
Ort: *ClipperLXC Shell*
1) Skript anlegen
```bash ```bash
nano <clipper-ordner>/bin/clipper-vod-get nano <clipper-ordner>/bin/clipper-vod-get
``` ```
Inhalt:
2) Inhalt einfügen → speichern (**Ctrl+O**, Enter) → schließen (**Ctrl+X**) ```bash
```js
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
ID="${1:?need VOD id}" ID="${1:?need VOD id}"
URL="${2:-https://www.twitch.tv/videos/${ID}}" URL="${2:-https://www.twitch.tv/videos/${ID}}"
TMP="<clipper-ordner>/temp" TMP="<clipper-ordner>/temp"
LOGDIR="<clipper-ordner>/logs" LOGDIR="<clipper-ordner>/logs"
CONF="/home/clipper/.config/rclone/rclone.conf" CONF="/home/clipper/.config/rclone/rclone.conf"
DST="nc:<gewünschter Ordner>/VODs/${ID}/" DST="nc:<gewünschter Ordner>/VODs/${ID}/"
OUT="$TMP/${ID}.%(ext)s" OUT="$TMP/${ID}.%(ext)s"
FILE="$TMP/${ID}.mp4" FILE="$TMP/${ID}.mp4"
TEMP="$TMP/${ID}.temp.mp4" TEMP="$TMP/${ID}.temp.mp4"
PART="$TMP/${ID}.mp4.part" PART="$TMP/${ID}.mp4.part"
LOCK="$TMP/${ID}.lock" LOCK="$TMP/${ID}.lock"
LOG="$LOGDIR/${ID}.log" LOG="$LOGDIR/${ID}.log"
mkdir -p "$TMP" "$LOGDIR" mkdir -p "$TMP" "$LOGDIR"
log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG"; }
log() {
echo "[$(date '+%F %T')] $*" | tee -a "$LOG"
}
log "=== Start VOD $ID ===" log "=== Start VOD $ID ==="
log "URL: $URL" log "URL: $URL"
if rclone lsf "$DST" --config "$CONF" >/dev/null 2>&1; then log "SKIP: $ID bereits in Nextcloud"; exit 0; fi
if rclone lsf "$DST" --config "$CONF" >/dev/null 2>&1; then if [[ -e "$LOCK" ]]; then log "LOCK: $ID wird bereits verarbeitet"; exit 0; fi
log "SKIP: $ID bereits in Nextcloud" trap 'rm -f "$LOCK"' EXIT; : > "$LOCK"
exit 0 if [[ -s "$TEMP" && ! -s "$FILE" ]]; then mv -f "$TEMP" "$FILE"; fi
fi if [[ -s "$FILE" ]]; then rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v; rm -f "$PART" "$TEMP" || true; exit 0; fi
yt-dlp -q --no-progress --retries 20 --fragment-retries 50 --retry-sleep 5 --socket-timeout 30 --hls-prefer-ffmpeg --remux-video mp4 -o "$OUT" "$URL"
if [[ -e "$LOCK" ]]; then
log "LOCK: $ID wird bereits verarbeitet"
exit 0
fi
trap 'rm -f "$LOCK"' EXIT
: > "$LOCK"
if [[ -s "$TEMP" && ! -s "$FILE" ]]; then
log "RESUME: TEMP gefunden → umbenennen"
mv -f "$TEMP" "$FILE"
fi
if [[ -s "$FILE" ]]; then
log "RESUME: fertige Datei gefunden → hochladen"
rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v
rm -f "$PART" "$TEMP" || true
log "OK: $ID hochgeladen (Resume-Pfad)"
log "=== Done VOD $ID ==="
exit 0
fi
log "START: Download mit yt-dlp"
yt-dlp -q --no-progress \
--retries 20 --fragment-retries 50 --retry-sleep 5 --socket-timeout 30 \
--hls-prefer-ffmpeg --remux-video mp4 \
-o "$OUT" "$URL"
[[ -s "$FILE" ]] || { [[ -s "$TEMP" ]] && mv -f "$TEMP" "$FILE"; } [[ -s "$FILE" ]] || { [[ -s "$TEMP" ]] && mv -f "$TEMP" "$FILE"; }
if [[ ! -s "$FILE" ]]; then if [[ ! -s "$FILE" ]]; then log "ERROR: Download fehlgeschlagen ($FILE fehlt/leer)"; exit 2; fi
log "ERROR: Download fehlgeschlagen ($FILE fehlt/leer)"
exit 2
fi
log "OK: Download abgeschlossen ($(du -h "$FILE" | awk '{print $1}'))"
log "START: Upload nach Nextcloud"
rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v rclone move "$FILE" "$DST" --config "$CONF" --create-empty-src-dirs -v
log "OK: Upload abgeschlossen"
rm -f "$PART" "$TEMP" || true rm -f "$PART" "$TEMP" || true
log "CLEANUP: temporäre Dateien entfernt"
log "=== Done VOD $ID ===" log "=== Done VOD $ID ==="
``` ```
Rechte setzen:
3) Rechte setzen
```bash ```bash
chmod 755 <clipper-ordner>/bin <clipper-ordner>/bin/clipper-vod-get chmod 755 <clipper-ordner>/bin/clipper-vod-get
chown clipper:clipper <clipper-ordner>/bin/clipper-vod-get chown clipper:clipper <clipper-ordner>/bin/clipper-vod-get
``` ```
> [!NOTE]
> **Hinweis** > Pro VOD entsteht ein Logfile in `<clipper-ordner>/logs/<ID>.log`. Du kannst es live mit `tail -f <clipper-ordner>/logs/<ID>.log` verfolgen.
> Pro VOD entsteht ein Logfile in `<clipper-ordner>/logs/<ID>.log`. Live-Ansicht mit `tail -f <clipper-ordner>/logs/<ID>.log`
--- ---
**C. n8nVerkabelung (Überblick)** Mit diesem Aufbau ist der **False-Pfad** fertig: Wenn die State-Datei fehlt oder leer ist, werden alle VODs verarbeitet und in Nextcloud hochgeladen. Der True-Pfad folgt im nächsten Abschnitt.
Ort: *n8n Weboberfläche*
**Verkabelung (Kurzüberblick):**
1) HTTP Request →
2) Split Out: →
3) Merge →
4) Split In Batches →
5) SSH Node 1 (State-Datei schreiben) →
6) SSH Node 2 (Download & Upload)
--- ---
** Node-Einstellungen (1:1 in n8n eintragen)** ### Schritt 8: True-Pfad Check und ggf. Download
**1) HTTP Request Get Videos**
- **Node-Typ:** HTTP Request
- **Methode:** GET
- **URL:**
```
https://api.twitch.tv/helix/videos?user_id=<DEINE_TWITCH_USER_ID>&type=archive&first=20
```
- **Authentifizierung:** OAuth2 (Credential: *Twitch API*)
- **Header:** `Client-Id: <DEINE_CLIENT_ID>`
- **Response Format:** JSON
**2) Item Lists Split Out** > [!NOTE]
- **Node-Typ:** Split Out > Der **False-Pfad** wurde bereits im vorherigen Abschnitt vollständig erklärt. Viele Nodes überschneiden sich mit dem True-Pfad. Damit du nicht alles doppelt anlegen musst, verweisen wir hier auf die bereits erstellten und konfigurierten Nodes.
- **Field to Split Out:** `data`
**3) Merge Combine**
- **Node-Typ:** Merge
- **Mode:** Combine
- **Combine Mode:** All Possible Combinations
- **Eingang 1:** Switch (Ausgang: CREATE_AND_DOWNLOAD)
- **Eingang 2:** Item Lists: Split Out
**4) Split In Batches**
- **Node-Typ:** Split In Batches
- **Batch Size:** `1`
**5) SSH Node 1 State-Datei schreiben**
- **Node-Typ:** SSH
- **Credentials:** *SSH Clipper* (User = `clipper`)
- **Operation:** Execute Command
- **Command is an Expression:** **ON**
- **Command:**
```bash
{{`set -euo pipefail; STATE="/srv/clipper/state/vod_seen.list"; mkdir -p "$(dirname "$STATE")"; if [ -s "$STATE" ]; then printf "%s\n" "${$json.data.id}" >> "$STATE"; else printf "%s\n" "${$json.data.id}" > "$STATE"; fi`}}
```
**6) SSH Node 2 Download & Upload (Skript)**
- **Node-Typ:** SSH
- **Credentials:** *SSH Clipper* (User = `clipper`)
- **Operation:** Execute Command
- **Command is an Expression:** **ON**
- **Command:**
```bash
<clipper-ordner>/bin/clipper-vod-get "{{$('Merge').item.json.data.id}}" "{{ $json.url || ('https://www.twitch.tv/videos/' + $('Merge').item.json.data.id) }}"
```
- **Hinweis:** Keine Binary-Nodes hinter diesem Schritt Upload erfolgt im Skript via rclone.
--- ---
**Ergebnis**
- n8n steuert den gesamten Prozess, Upload erfolgt zuverlässig im Clipper via rclone.
- Pro VOD entsteht **eine** MP4 in Nextcloud: `<gewünschter Ordner>/VODs/<ID>/`.
- Pro VOD gibt es ein eigenes Logfile unter `<clipper-ordner>/logs/<ID>.log`.
- Lokaler Speicher bleibt frei (automatisches Löschen nach Upload).
- Logs der Schritte findest du im SSHNodeOutput und persistent im Logfile.
8. ** True-Pfad - Check und ggf. Download
**Verkabelung (Kurzüberblick):** **Verkabelung (Kurzüberblick):**
1) HTTP Request → 1) HTTP Request →
2) Split Out → 2) Split Out →
3) Set vods in Array → 3) Set vods in Array →
4) Split Arry Out 4) Split Out Array
5) Merge → 5) Merge →
6) Split In Batches → 6) Split In Batches →
7) SSH Node 1 (State-Datei schreiben) 7) SSH Write State
8) SSH Node 2 (Download & Upload) 8) SSH Download VOD
** Node-Einstellungen (1:1 in n8n eintragen)**
**1) HTTP Request Get Videos** ---
- **Node-Typ:** HTTP Request
- **Methode:** GET
- **URL:** ### Node-Einstellungen (1:1 in n8n eintragen)
> [!IMPORTANT]
> Die folgenden Nodes haben wir im vorherigen Schritt (False-Pfad) bereits erstellt und konfiguriert. Wenn du dem Tutorial bis hierhin gefolgt bist, musst du an diesen Nodes nichts mehr verändern:
> - **HTTP Request Get Videos**
> - **Split Out data**
> - **Split In Batches**
> - **SSH Write State**
> - **SSH Download VOD**
---
**Neu im True-Pfad:**
**3) Set vods in Array** (Node-Name: `Set vods in Array`)
- Node-Typ: Set
- Field: vods
- Expression:
```js
{{ (typeof $json.vods === 'string' ? $json.vods : String($json.vods))
.split(',')
.map(s => s.trim())
.filter(Boolean) }}
``` ```
https://api.twitch.tv/helix/videos?user_id=<DEINE_TWITCH_USER_ID>&type=archive&first=20
```
- **Authentifizierung:** OAuth2 (Credential: *Twitch API*)
- **Header:** `Client-Id: <DEINE_CLIENT_ID>`
- **Response Format:** JSON
**2) Item Lists Split Out**
- **Node-Typ:** Split Out
- **Field to Split Out:** `data`
**3) Set vods in Array** **4) Split Out vods** (Node-Name: `Split Out vods`)
- **Node-Typ:** Edit Fields (set) - Node-Typ: Split Out
- **Field:** vods - Field to Split Out: vods
- **Type:** Arry
- **Expression** `{{ (typeof $json.vods === 'string' ? $json.vods : String($json.vods)) .split(',') .map(s => s.trim()) .filter(Boolean) }}`
**4) Item Lists Split Out**
- **Node-Typ:** Split Out
- **Field to Split Out:** `vods`
**5) Merge Combine** **5) Merge Combine** (Node-Name: `Merge Combine`)
- **Node-Typ:** Merge - Node-Typ: Merge
- **Mode:** Combine - Mode: Combine
- **Combine Mode:** Matching Fields - Combine Mode: Matching Fields
- **Fields To Match Have Different Names:** ON - Fields To Match Have Different Names: ON
- **Field 1:** vods - Field 1: vods
- **Field 2:** data.id - Field 2: data.id
- **Output Type:** Keep Non-Matches - Output Type: Keep Non-Matches
- **Output Data From:** Input 2 - Output Data From: Input 2
- **Eingang 1:** Split Out Array VODs - Eingang 1: Split Out Array VODs
- **Eingang 2:** Split Out Twitch VODs - Eingang 2: Split Out Twitch VODs
---
### Ergebnis
- Es werden nur **neue VODs** heruntergeladen und hochgeladen.
- Die State-Datei wird erweitert, ohne bestehende IDs zu überschreiben.
- Logs bleiben konsistent, Doppel-Downloads werden vermieden.
- Jeder Node ist eindeutig benannt, was die Übersicht verbessert.