{
  "name": "PF WF-02 Caption Generator v18.3",
  "nodes": [
    {
      "parameters": {
        "content": "## WF-02 v18.3 \u2014 Vollautomatik mit variablem Foto\n\n**Was tut dieser Workflow:**\nSt\u00fcndlich alle SharePoint-Items mit `Status=Entwurf` verarbeiten:\n1. Caption + Hashtags via Claude generieren\n2. HWG-Filter pr\u00fcfen (Heilversprechen blockieren)\n3. **NEU v18.3:** Item-spezifisches Praxis-Foto aus SP fetchen + nach GitHub pushen\n4. HTML-Template rendern (Gotenberg) \u2014 Instagram + Facebook, jetzt mit variablem `{{BACKGROUND_PHOTO}}`\n5. Bilder in SharePoint Doc-Library `Social-Media-Assets` hochladen\n6. Status setzen: `Bereit` / `Geblockt` / `Wartet-auf-Avatar`\n\n**Avatar-Posts** (Reel/\u00dcbung/Story) parken bis `AVATAR_ENABLED=true`.\n\n**KEIN Teams-Wait** \u2014 Auto-Release. 24h-Karenz macht WF-03.\n\n**Build-Spec:** `02_n8n-Workflows/WF-02_v18.3_Foto-Branch_Spec.md`\n**Foto-Convention:** `04_Canva-Vorlagen/Foto-Convention.md`",
        "height": 380,
        "width": 480,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -2800,
        80
      ],
      "id": "c4d0e21c-9942-46a8-89d1-43292a6c5619",
      "name": "Doku: Overview"
    },
    {
      "parameters": {
        "content": "## Konstanten (in Code-Nodes dupliziert)\n\n```javascript\nconst AVATAR_ENABLED = false;\nconst AVATAR_PROVIDER = \"d-id\";\nconst AVATAR_ID = \"judith_v1\";\n\nconst GOTENBERG_URL = \"http://gotenberg:3000/forms/chromium/screenshot/html\";\n\nconst SP_SITE_URL = \"https://physiofuchs889.sharepoint.com/sites/PhysioFuchsTW\";\nconst SP_LIST_GUID = \"d17a6a6f-e6ef-457d-a2a9-4c30ea56120f\";\nconst SP_DOCLIB_GUID = \"c1f0a171-5df2-4c8b-9ab4-db3ed8e76266\";\nconst SP_DOCLIB_NAME = \"Social-Media-Assets\";\n\n// NEU v18.3: SP-Standort der Praxis-Fotos (verifiziert 2026-06-08)\n// Gleiche Site wie Content-Kalender, deutsche Library \"Freigegebene Dokumente\"\nconst SP_FOTOS_SITE = \"https://physiofuchs889.sharepoint.com/sites/PhysioFuchsTW\";\nconst SP_FOTOS_FOLDER_REL = \"/sites/PhysioFuchsTW/Freigegebene Dokumente/Socialmedia/Content_Socialmedia/Fotos/{year}\";\n\nconst REPO_RAW = \"https://raw.githubusercontent.com/twaese/Physio-Fuchs-Social-Media-Automation/main\";\nconst IG_TPL = REPO_RAW + \"/04_Canva-Vorlagen/html-templates/instagram/PF_Feed_Standard.html\";\nconst FB_TPL = REPO_RAW + \"/04_Canva-Vorlagen/html-templates/facebook/PF_Feed_FB_Standard.html\";\nconst FALLBACK_PHOTO = REPO_RAW + \"/04_Canva-Vorlagen/html-templates/assets/judith-behandlung-01.png\";\n```",
        "height": 660,
        "width": 480,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -2800,
        480
      ],
      "id": "dbb3f93d-df15-48c5-a84a-2768458f966c",
      "name": "Doku: Konstanten"
    },
    {
      "parameters": {
        "content": "## NEU v18.3: Foto-Branch\n\n**Vor `Code: HTML bauen (IG+FB)` eingeschoben.**\n\nLogik:\n1. **Foto-Pfad bauen** \u2014 Year + ItemID-3stellig\n2. **SP: Fotos listen** \u2014 alle Files im Jahres-Ordner\n3. **Foto-Match** \u2014 sucht `PF_{year}_{id}_*.{jpg|png}`\n4. **If: Foto gefunden?**\n   - true \u2192 Download \u2192 GitHub Body \u2192 GitHub Push \u2192 HTML bauen\n   - false \u2192 direkt HTML bauen (mit Fallback-URL)\n\n**Fallback-Kaskade:**\n- Item-Foto (wenn vorhanden)\n- \u2193 Stock-Pool (TODO v18.4)\n- \u2193 `judith-behandlung-01.png` (last resort)\n\nDetails: `WF-02_v18.3_Foto-Branch_Spec.md`",
        "height": 360,
        "width": 360,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -224,
        -560
      ],
      "id": "fb18a3a0-1111-4444-bbbb-222233334444",
      "name": "Doku: v18.3 Foto-Branch"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -2272,
        304
      ],
      "id": "e1e0400a-ba2c-46d3-9b9c-471e743ea3bb",
      "name": "Cron: st\u00fcndlich"
    },
    {
      "parameters": {
        "resource": "item",
        "site": {
          "__rl": true,
          "value": "physiofuchs889.sharepoint.com,c74040a2-0d6b-4385-b514-fbd9830a56ac,7a7842d6-ea6c-440d-aeed-580bdd2a091c",
          "mode": "list",
          "cachedResultName": "Physio Fuchs Administration"
        },
        "list": {
          "__rl": true,
          "value": "d17a6a6f-e6ef-457d-a2a9-4c30ea56120f",
          "mode": "list",
          "cachedResultName": "PF-Content-Kalender-2026"
        },
        "options": {
          "fields": [
            "fields"
          ]
        },
        "simplify": false,
        "requestOptions": {}
      },
      "type": "n8n-nodes-base.microsoftSharePoint",
      "typeVersion": 1,
      "position": [
        -2048,
        304
      ],
      "id": "323b83ea-fe8a-4e0c-b150-b122ebce4fa3",
      "name": "SP: Entw\u00fcrfe holen",
      "credentials": {
        "microsoftSharePointOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Validate + Route \u2014 Produktionsmodus\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const f = item.json.fields || {};\n  const postTyp = f.field_2 || f.Field_2 || '';\n  const status  = f.field_6 || f.Field_6 || '';\n  const itemId  = item.json.Ausweis || item.json.id || f.AUSWEIS || f.id;\n  const title   = f.Title || f.Titel || '';\n\n  if (status !== 'Entwurf') continue;\n  if (!postTyp) continue;\n\n  // Avatar noch nicht aktiv \u2192 diese Typen komplett \u00fcberspringen\n  const avatarTypes = ['Reel', '\u00dcbung', 'Story', 'Uebung'];\n  if (avatarTypes.includes(postTyp)) continue;\n  const branch = 'feed';\n\n  results.push({\n    json: {\n      _sp_item_id: itemId,\n      _post_typ: postTyp,\n      _branch: branch,\n      sp_fields: f,\n    }\n  });\n}\n\nreturn results.slice(0, 1);"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1824,
        304
      ],
      "id": "f9581575-9071-4805-8dfd-27bdf8edb34b",
      "name": "Code: Validate + Route"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json._branch }}",
                    "rightValue": "feed",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "15321ac6-ae9f-47a2-8f77-aedf4f3ea948"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "feed"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json._branch }}",
                    "rightValue": "avatar",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "5b80995d-d2dd-464a-a791-70008308ecf1"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "avatar"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        -1600,
        304
      ],
      "id": "0d42075f-c660-4287-945c-9598f772b19f",
      "name": "Switch: Feed/Avatar"
    },
    {
      "parameters": {
        "jsCode": "// Build Claude Payload f\u00fcr Caption-Generierung\nconst f = $json.sp_fields;\nconst pt = $json._post_typ;\n\n// Echte Field-Namen aus SharePoint:\n//  field_1 = Thema, field_3 = Content_Brief, field_7 = Hashtag_Thema\nconst thema       = f.field_1 || '';\nconst contentBrief = f.field_3 || '';\nconst hashtagThema = f.field_7 || '';\n\nconst PROMPT = `Du bist Texter f\u00fcr die Physio Fuchs Praxis in Ratingen-Lintorf.\nErzeuge eine Instagram-Caption f\u00fcr folgenden Post-Brief.\n\nPost-Typ: ${pt}\nThema: ${thema}\nContent-Brief: ${contentBrief}\nHashtag-Thema: ${hashtagThema}\n\nTonalit\u00e4t-Regeln (verbindlich):\n- \"Sie\"-Form, professionell, sympathisch, motivierend\n- KEINE Heilversprechen \u2014 verboten: \"heilt\", \"garantiert\", \"kuriert\", \"schmerzfrei in X Tagen\", \"beseitigt\"\n- KEINE Diagnosen\n- Maximal 3 Emojis, sparsam, fachlich passend\n- Bei \u00dcbungen: Hinweis erg\u00e4nzen \"Bei anhaltenden Beschwerden bitte \u00e4rztlich abkl\u00e4ren lassen\"\n- Inklusiv (\"Patientinnen und Patienten\", nicht Gender-Stern)\n\nAntworte AUSSCHLIESSLICH als JSON, ohne Markdown-Wrapping:\n{\n  \"titel\": \"<Bild-Headline, max 50 Zeichen>\",\n  \"text\": \"<Bild-Body, 2-3 kurze S\u00e4tze, max 200 Zeichen>\",\n  \"cta\": \"<Bild-CTA, max 50 Zeichen>\",\n  \"caption\": \"<volle Instagram-Caption, 2-3 Abs\u00e4tze, endet mit Frage>\",\n  \"hashtags\": [\"#tag1\", \"#tag2\", \"...\"]\n}\nHashtags: 8-15 St\u00fcck, mind. 2 lokale (Ratingen / Lintorf).`;\n\nreturn [{\n  json: {\n    _passthrough: $json,\n    model: 'claude-sonnet-4-6',\n    max_tokens: 2048,\n    messages: [{ role: 'user', content: PROMPT }]\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1344,
        160
      ],
      "id": "635cdabb-785e-4021-8ecc-e7211f34adb5",
      "name": "Code: Build Claude Payload"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $env.ANTHROPIC_API_KEY }}"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"{{ $json.model }}\",\n  \"max_tokens\": {{ $json.max_tokens }},\n  \"messages\": {{ JSON.stringify($json.messages) }}\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -1120,
        160
      ],
      "id": "db72f350-bb1b-4f27-bbc6-e24861c6738d",
      "name": "HTTP: Claude Caption"
    },
    {
      "parameters": {
        "jsCode": "// Parse Claude-Response, JSON extrahieren\nconst resp = $json;\nconst raw = resp.content?.[0]?.text || resp.completion || '';\n\nlet parsed;\ntry {\n  const match = raw.match(/\\{[\\s\\S]*\\}/);\n  parsed = JSON.parse(match ? match[0] : raw);\n} catch (e) {\n  throw new Error('Claude-Response nicht JSON-parsebar: ' + raw.slice(0, 200));\n}\n\nconst pt = $node['Code: Build Claude Payload'].json._passthrough;\n\nreturn [{\n  json: {\n    ...pt,\n    titel:    parsed.titel    || '',\n    text:     parsed.text     || '',\n    cta:      parsed.cta      || '',\n    caption:  parsed.caption  || '',\n    hashtags: Array.isArray(parsed.hashtags) ? parsed.hashtags : [],\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -896,
        160
      ],
      "id": "7485b3e4-1f99-4e83-80e9-a17ccb2a72cb",
      "name": "Code: Parse Claude"
    },
    {
      "parameters": {
        "jsCode": "// HWG-Filter: Caption gegen Heilversprechen-Blacklist pr\u00fcfen\nconst HWG_PATTERNS = [\n  /\\bheilt\\b/i,\n  /\\bheilung\\b/i,\n  /\\bkuriert\\b/i,\n  /\\bgarantiert\\b/i,\n  /\\bgew\u00e4hrleistet?\\b/i,\n  /\\bverspricht\\b/i,\n  /\\bversprechen wir\\b/i,\n  /\\b(schmerzfrei|beschwerdefrei) in (\\d+|wenigen|k\u00fcrzester) (tag|tage|tagen|woche|wochen)\\b/i,\n  /\\bwirkt (100 ?%|sicher|garantiert)\\b/i,\n  /\\bbeseitigt (schmerz|beschwerd|leiden)\\b/i,\n  /\\b(stellt|stellen wir) (ihre )?diagnose\\b/i,\n  /\\bbehandelt erfolgreich\\b/i,\n  /\\b(nebenwirkungsfrei|ohne nebenwirkungen)\\b/i,\n  /\\bbesser als (\u00e4rzt|operation|medikament)\\b/i,\n  /\\b(ersetzt|statt) (arzt|\u00e4rzt|operation|medikament)\\b/i,\n];\n\nconst f = $json;\nconst fullText = [f.titel, f.text, f.cta, f.caption].filter(Boolean).join(' ');\n\nconst matches = HWG_PATTERNS\n  .map(re => ({ pattern: re.source, hit: fullText.match(re) }))\n  .filter(x => x.hit);\n\nreturn [{\n  json: {\n    ...f,\n    _hwg_matches: matches.map(m => m.hit[0]),\n    _hwg_clean: matches.length === 0,\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -672,
        160
      ],
      "id": "3a470a81-b6f4-4f3d-b5b1-95771d2cb58c",
      "name": "Code: HWG-Filter"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json._hwg_clean }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "id": "d126991f-52e9-4e8a-8898-988600949de4"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -448,
        160
      ],
      "id": "e67f4a36-fa9d-48b5-9cfb-e55f1a06d293",
      "name": "If: HWG sauber?"
    },
    {
      "parameters": {
        "jsCode": "// === v18.3 Foto-Branch Node 1 ===\n// Berechnet Suchparameter f\u00fcr SP-Foto-Lookup.\nconst hwg = $('Code: HWG-Filter').first().json;\nconst validate = $('Code: Validate + Route').first().json;\n\nconst itemId = String(validate._sp_item_id).padStart(3, '0');\n\n// Jahr aus Ver\u00f6ffentlichungsdatum, sonst aktuelles Jahr\nconst datumRaw = hwg.sp_fields?.Ver_x00f6_ffentlichungsdatum;\nconst year = datumRaw ? new Date(datumRaw).getFullYear() : new Date().getFullYear();\n\n// SP-Pfad (verifiziert 2026-06-08): gleiche Site wie Content-Kalender\nconst SP_FOTOS_SITE = 'https://physiofuchs889.sharepoint.com/sites/PhysioFuchsTW';\nconst SP_FOTOS_FOLDER_REL = `/sites/PhysioFuchsTW/Freigegebene Dokumente/Socialmedia/Content_Socialmedia/Fotos/${year}`;\n\n// SP REST API URL f\u00fcr Folder-Listing\n// HINWEIS: SP-Pfad-Encoding ist tricky bei Leerzeichen. Wir nutzen einfaches Quoting,\n// nicht encodeURIComponent \u2014 sonst werden Slashes auch encodiert.\nconst sp_list_url = `${SP_FOTOS_SITE}/_api/web/GetFolderByServerRelativeUrl('${SP_FOTOS_FOLDER_REL}')/Files`;\n\nconst filename_prefix = `PF_${year}_${itemId}_`;\n\nreturn [{\n  json: {\n    itemId,\n    year,\n    sp_fotos_site: SP_FOTOS_SITE,\n    sp_fotos_folder_rel: SP_FOTOS_FOLDER_REL,\n    sp_list_url,\n    filename_prefix,\n    // Original-Daten durchreichen\n    ...hwg,\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -224,
        80
      ],
      "id": "fb18a301-0001-0001-0001-000000000001",
      "name": "Code: Foto-Pfad bauen"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $json.sp_list_url }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftSharePointOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json;odata=verbose"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        0,
        80
      ],
      "id": "fb18a302-0002-0002-0002-000000000002",
      "name": "HTTP: SP Fotos listen",
      "credentials": {
        "microsoftSharePointOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// === v18.3 Foto-Branch Node 3 ===\n// Sucht in SP-Folder-Listing die zur ItemID passende Foto-Datei.\nconst cfg = $('Code: Foto-Pfad bauen').first().json;\nconst prefix = cfg.filename_prefix;\n\n// SP-REST gibt Files in d.results zur\u00fcck (verbose-Format)\nconst resp = $input.first().json || {};\nlet files = [];\nif (resp.d?.results) files = resp.d.results;\nelse if (Array.isArray(resp.value)) files = resp.value;\nelse if (Array.isArray(resp)) files = resp;\n\n// Match per Filename-Prefix\nconst matches = files\n  .filter(f => (f.Name || f.name || '').startsWith(prefix))\n  .sort((a, b) => (a.Name || a.name || '').localeCompare(b.Name || b.name || ''));\n\nconst REPO_RAW = 'https://raw.githubusercontent.com/twaese/Physio-Fuchs-Social-Media-Automation/main';\nconst FALLBACK_URL = `${REPO_RAW}/04_Canva-Vorlagen/html-templates/assets/judith-behandlung-01.png`;\n\nif (matches.length > 0) {\n  const m = matches[0];\n  const name = m.Name || m.name;\n  const serverRel = m.ServerRelativeUrl || m.serverRelativeUrl;\n  // Vorhersagbare GitHub-URL nach dem Push\n  const gh_path = `04_Canva-Vorlagen/html-templates/assets/photos/${name}`;\n  const gh_url = `${REPO_RAW}/${gh_path}`;\n\n  return [{\n    json: {\n      ...cfg,\n      photo_found: true,\n      photo_name: name,\n      photo_sp_server_rel: serverRel,\n      photo_gh_path: gh_path,\n      final_photo_url: gh_url,\n      _match_log: `Foto gefunden: ${name}`,\n    }\n  }];\n}\n\n// Kein Item-Foto \u2192 Fallback\nreturn [{\n  json: {\n    ...cfg,\n    photo_found: false,\n    photo_name: null,\n    final_photo_url: FALLBACK_URL,\n    _match_log: `Kein Item-Foto (prefix \"${prefix}\" in ${files.length} Files) \u2192 Fallback`,\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        80
      ],
      "id": "fb18a303-0003-0003-0003-000000000003",
      "name": "Code: Foto-Match"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.photo_found }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "id": "fb18a304-true-cond-0001-000000000001"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        448,
        80
      ],
      "id": "fb18a304-0004-0004-0004-000000000004",
      "name": "If: Foto gefunden?"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $json.sp_fotos_site }}/_api/web/GetFileByServerRelativeUrl('{{ encodeURIComponent($json.photo_sp_server_rel) }}')/$value",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftSharePointOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "*/*"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        672,
        0
      ],
      "id": "fb18a305-0005-0005-0005-000000000005",
      "name": "HTTP: SP Foto Download",
      "credentials": {
        "microsoftSharePointOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// === v18.3 Foto-Branch Node 5 ===\n// SP-Foto-Binary f\u00fcr GitHub-PUT vorbereiten.\nconst binary = $input.first().binary?.data;\nif (!binary || !binary.data) {\n  throw new Error('Kein Binary von SP-Foto-Download erhalten');\n}\n\nconst cfg = $('Code: Foto-Match').first().json;\n\nreturn [{\n  json: {\n    repo_path: cfg.photo_gh_path,\n    filename: cfg.photo_name,\n    public_url: cfg.final_photo_url,\n    github_body: {\n      message: `WF-02 v18.3: Praxis-Foto ${cfg.photo_name} f\u00fcr PF-${cfg.year}-${cfg.itemId}`,\n      content: binary.data,\n    },\n    _passthrough: cfg,\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        896,
        0
      ],
      "id": "fb18a306-0006-0006-0006-000000000006",
      "name": "Code: Foto \u2192 GitHub Body"
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://api.github.com/repos/twaese/Physio-Fuchs-Social-Media-Automation/contents/{{ $json.repo_path }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.GITHUB_TOKEN }}"
            },
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.github_body) }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        0
      ],
      "id": "fb18a307-0007-0007-0007-000000000007",
      "name": "HTTP: GitHub-Push Foto"
    },
    {
      "parameters": {
        "jsCode": "// === v18.3 Foto-Branch Node 7 (Merge nach IF-Branch) ===\n// Vereint True- und False-Pfad: nimmt final_photo_url, schiebt Daten weiter.\nconst inputs = $input.all();\n// Eines der beiden Items kommt durch \u2014 egal welches Branch.\nconst data = inputs[0]?.json || {};\n\n// final_photo_url ist in Code: Foto-Match gesetzt, in beiden Branches verf\u00fcgbar\nconst matchData = $('Code: Foto-Match').first().json;\n\nreturn [{\n  json: {\n    ...matchData,\n    // Diagnose: woher kommt das Foto?\n    _foto_branch_taken: matchData.photo_found ? 'item-foto' : 'fallback',\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1344,
        80
      ],
      "id": "fb18a308-0008-0008-0008-000000000008",
      "name": "Code: Foto-URL final"
    },
    {
      "parameters": {
        "jsCode": "// HTML-Templates fetchen + Platzhalter f\u00fcllen f\u00fcr IG und FB\n// v18.3: BACKGROUND_PHOTO-Platzhalter neu (variabel pro Content)\nconst REPO_RAW = 'https://raw.githubusercontent.com/twaese/Physio-Fuchs-Social-Media-Automation/main';\nconst IG_TPL = REPO_RAW + '/04_Canva-Vorlagen/html-templates/instagram/PF_Feed_Standard.html';\nconst FB_TPL = REPO_RAW + '/04_Canva-Vorlagen/html-templates/facebook/PF_Feed_FB_Standard.html';\nconst ASSETS_BASE = REPO_RAW + '/04_Canva-Vorlagen/html-templates/assets/';\nconst FALLBACK_PHOTO = REPO_RAW + '/04_Canva-Vorlagen/html-templates/assets/judith-behandlung-01.png';\n\nconst f = $json;\nconst datum = new Date().toLocaleDateString('de-DE', {\n  day: '2-digit', month: '2-digit', year: 'numeric'\n});\n\nasync function fetchTemplate(url) {\n  const r = await this.helpers.httpRequest({ method: 'GET', url, returnFullResponse: false });\n  return typeof r === 'string' ? r : (r.body || r);\n}\n\nfunction substitute(html, vars) {\n  // Asset-Pfade auf absolute GitHub-Raw-URLs umschreiben (NUR f\u00fcr Logo/Ginkgo etc.)\n  html = html.replace(/src=\"\\.\\.\\/assets\\//g, `src=\"${ASSETS_BASE}`);\n  // Platzhalter ersetzen\n  for (const [k, v] of Object.entries(vars)) {\n    html = html.replaceAll('{{' + k + '}}', String(v));\n  }\n  return html;\n}\n\nconst vars = {\n  TITEL: f.titel || '',\n  TEXT:  f.text  || '',\n  CTA:   f.cta   || '',\n  DATUM: datum,\n  // NEU v18.3: Hintergrund-Foto-URL aus Foto-Branch\n  BACKGROUND_PHOTO: f.final_photo_url || FALLBACK_PHOTO,\n};\n\nconst igTplRaw = await fetchTemplate.call(this, IG_TPL);\nconst fbTplRaw = await fetchTemplate.call(this, FB_TPL);\n\nconst igHtml = substitute(igTplRaw, vars);\nconst fbHtml = substitute(fbTplRaw, vars);\n\nreturn [{\n  json: {\n    ...f,\n    _datum: datum,\n    _bg_photo: vars.BACKGROUND_PHOTO,\n    _ig_html: igHtml,\n    _fb_html: fbHtml,\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1568,
        80
      ],
      "id": "a72f08f1-2cf9-441c-93aa-136ba3b8f736",
      "name": "Code: HTML bauen (IG+FB)"
    },
    {
      "parameters": {
        "jsCode": "// HTML als Binary File f\u00fcr Gotenberg-Multipart verf\u00fcgbar machen\nconst html = $json._ig_html;\nconst buffer = Buffer.from(html, 'utf8');\nreturn [{\n  json: $json,\n  binary: {\n    ig_html_file: await this.helpers.prepareBinaryData(buffer, 'index.html', 'text/html')\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1792,
        0
      ],
      "id": "7c09c49d-0fef-481b-9368-d8bd6e74177a",
      "name": "Code: IG HTML\u2192Binary"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://gotenberg:3000/forms/chromium/screenshot/html",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "files",
              "inputDataFieldName": "ig_html_file"
            },
            {
              "name": "format",
              "value": "png"
            },
            {
              "name": "width",
              "value": "1080"
            },
            {
              "name": "height",
              "value": "1350"
            },
            {
              "name": "optimizeForSpeed",
              "value": "false"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2016,
        0
      ],
      "id": "da7333e0-8ad2-4ea1-9463-7c49444b1f16",
      "name": "HTTP: Gotenberg IG"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://physiofuchs889.sharepoint.com/sites/PhysioFuchsTW/_api/web/lists(guid'c1f0a171-5df2-4c8b-9ab4-db3ed8e76266')/RootFolder/Files/add(url='{{ $('Code: Validate + Route').item.json._sp_item_id }}_ig.png',overwrite=true)",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftSharePointOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json;odata=verbose"
            },
            {
              "name": "Content-Type",
              "value": "application/octet-stream"
            }
          ]
        },
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "data",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2240,
        0
      ],
      "id": "45a314ff-476d-4e75-b74e-bb4e790f7d30",
      "name": "SP: Upload IG-Bild",
      "credentials": {
        "microsoftSharePointOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// HTML als Binary f\u00fcr FB-Gotenberg\nconst html = $node['Code: HTML bauen (IG+FB)'].json._fb_html;\nconst buffer = Buffer.from(html, 'utf8');\nreturn [{\n  json: $json,\n  binary: {\n    fb_html_file: await this.helpers.prepareBinaryData(buffer, 'index.html', 'text/html')\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1792,
        160
      ],
      "id": "9ea645c0-df81-4d09-8604-98ce1cb8d703",
      "name": "Code: FB HTML\u2192Binary"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://gotenberg:3000/forms/chromium/screenshot/html",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "files",
              "inputDataFieldName": "fb_html_file"
            },
            {
              "name": "format",
              "value": "png"
            },
            {
              "name": "width",
              "value": "1080"
            },
            {
              "name": "height",
              "value": "1080"
            },
            {
              "name": "optimizeForSpeed",
              "value": "false"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2016,
        160
      ],
      "id": "8b4e896b-e257-4ddc-9ae5-6cdd37023245",
      "name": "HTTP: Gotenberg FB"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://physiofuchs889.sharepoint.com/sites/PhysioFuchsTW/_api/web/lists(guid'c1f0a171-5df2-4c8b-9ab4-db3ed8e76266')/RootFolder/Files/add(url='{{ $('Code: Validate + Route').item.json._sp_item_id }}_fb.png',overwrite=true)",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftSharePointOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json;odata=verbose"
            },
            {
              "name": "Content-Type",
              "value": "application/octet-stream"
            }
          ]
        },
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "data",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2240,
        160
      ],
      "id": "03a3ddf1-d313-4dc5-913d-f2f86564e1bc",
      "name": "SP: Upload FB-Bild",
      "credentials": {
        "microsoftSharePointOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Gotenberg-Binary f\u00fcr GitHub-PUT vorbereiten (IG)\nconst binary = $input.first().binary?.data;\nif (!binary || !binary.data) {\n  throw new Error('Kein Binary von Gotenberg IG erhalten');\n}\n\nconst itemId = $('Code: Validate + Route').item.json._sp_item_id;\nconst ts = new Date().toISOString().replace(/[:T.]/g, '-').slice(0, 19);\nconst filename = `${itemId}_ig_${ts}.png`;\nconst path = `04_Canva-Vorlagen/generated-posts/${filename}`;\n\nreturn [{\n  json: {\n    repo_path: path,\n    filename: filename,\n    public_url: `https://raw.githubusercontent.com/twaese/Physio-Fuchs-Social-Media-Automation/main/${path}`,\n    github_body: {\n      message: `WF-02 auto-post ${itemId} IG ${ts}`,\n      content: binary.data,\n    },\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2240,
        304
      ],
      "id": "25c6ff1f-24ca-4363-8da6-398fd7712b34",
      "name": "Code: IG \u2192 GitHub Body"
    },
    {
      "parameters": {
        "jsCode": "// Gotenberg-Binary f\u00fcr GitHub-PUT vorbereiten (FB)\nconst binary = $input.first().binary?.data;\nif (!binary || !binary.data) {\n  throw new Error('Kein Binary von Gotenberg FB erhalten');\n}\n\nconst itemId = $('Code: Validate + Route').item.json._sp_item_id;\nconst ts = new Date().toISOString().replace(/[:T.]/g, '-').slice(0, 19);\nconst filename = `${itemId}_fb_${ts}.png`;\nconst path = `04_Canva-Vorlagen/generated-posts/${filename}`;\n\nreturn [{\n  json: {\n    repo_path: path,\n    filename: filename,\n    public_url: `https://raw.githubusercontent.com/twaese/Physio-Fuchs-Social-Media-Automation/main/${path}`,\n    github_body: {\n      message: `WF-02 auto-post ${itemId} FB ${ts}`,\n      content: binary.data,\n    },\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2256,
        528
      ],
      "id": "15fc6b10-7af5-4afa-b9b3-1b311a6b7f1c",
      "name": "Code: FB \u2192 GitHub Body"
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://api.github.com/repos/twaese/Physio-Fuchs-Social-Media-Automation/contents/{{ $json.repo_path }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.GITHUB_TOKEN }}"
            },
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.github_body) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2464,
        224
      ],
      "id": "9b84ca58-f0b4-4204-813e-36a3d13a0f39",
      "name": "GitHub: Push IG"
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://api.github.com/repos/twaese/Physio-Fuchs-Social-Media-Automation/contents/{{ $json.repo_path }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.GITHUB_TOKEN }}"
            },
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.github_body) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2464,
        432
      ],
      "id": "1f9d0637-6b8f-4c64-ba7f-ecd5fd43425c",
      "name": "GitHub: Push FB"
    },
    {
      "parameters": {
        "jsCode": "const f = $('Code: HWG-Filter').first().json;\nconst itemId = $('Code: Validate + Route').first().json._sp_item_id;\n\n// GitHub-Raw-URLs (public, von Meta ladbar) statt SharePoint-internen URLs\n// .first() statt .item \u2014 Multi-Branch-Merge sonst undefined\nlet igUrl = '', fbUrl = '';\ntry { igUrl = $('Code: IG \u2192 GitHub Body').first().json.public_url || ''; } catch(e) { console.log('IG-URL-Lookup failed:', e.message); }\ntry { fbUrl = $('Code: FB \u2192 GitHub Body').first().json.public_url || ''; } catch(e) { console.log('FB-URL-Lookup failed:', e.message); }\n\n// v18.3: Foto-Branch-Diagnose mitloggen\nlet fotoLog = '';\ntry {\n  const fm = $('Code: Foto-Match').first().json;\n  fotoLog = fm.photo_found ? `Foto: ${fm.photo_name}` : 'Foto: Fallback';\n} catch (e) { fotoLog = 'Foto: ?'; }\n\nconst hashtagBlock = (f.hashtags || []).join(' ');\nconst captionPure = f.caption;   // OHNE Hashtags \u2014 WF-03 h\u00e4ngt sie f\u00fcr IG einmalig dran\n\nconst now = new Date().toISOString().replace('T', ' ').slice(0, 16);\nconst logEntry = `\\n${now} WF-02 v18.3: Caption+Bild fertig, HWG sauber, ${fotoLog}, GitHub-Push IG${igUrl?'\u2713':'\u2717'}/FB${fbUrl?'\u2713':'\u2717'}, Status Bereit`;\nconst existingLog = f.sp_fields.field_13 || '';\n\nreturn [{\n  json: {\n    _sp_item_id: itemId,\n    payload: {\n      field_10: captionPure,\n      field_7:  hashtagBlock,\n      field_6:  'Bereit',\n      field_8:  igUrl,\n      Bild_FB_Dateiname: fbUrl,\n      field_13: existingLog + logEntry,\n    }\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2464,
        80
      ],
      "id": "a2978a89-a477-4336-9a11-1b89f0ab87de",
      "name": "Code: Merge + Update-Payload"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://physiofuchs889.sharepoint.com/sites/PhysioFuchsTW/_api/web/lists(guid'd17a6a6f-e6ef-457d-a2a9-4c30ea56120f')/items({{ $json._sp_item_id }})",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftSharePointOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept",
              "value": "application/json"
            },
            {
              "name": "X-HTTP-Method",
              "value": "MERGE"
            },
            {
              "name": "IF-MATCH",
              "value": "*"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.payload) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2688,
        80
      ],
      "id": "2dddb2e9-0cef-48eb-9831-502760e1976c",
      "name": "SP: Status Bereit",
      "credentials": {
        "microsoftSharePointOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://physiofuchs889.sharepoint.com/sites/PhysioFuchsTW/_api/web/lists(guid'd17a6a6f-e6ef-457d-a2a9-4c30ea56120f')/items({{ $('Code: Validate + Route').item.json._sp_item_id }})",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftSharePointOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept",
              "value": "application/json"
            },
            {
              "name": "X-HTTP-Method",
              "value": "MERGE"
            },
            {
              "name": "IF-MATCH",
              "value": "*"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"Caption_Variante\": {{ JSON.stringify($json.caption + \"\\n\\n\" + ($json.hashtags || []).join(\" \")) }},\n  \"Status\": \"Geblockt\",\n  \"Kommentare\": {{ JSON.stringify(($json.sp_fields.Kommentare || \"\") + \"\\n\" + new Date().toISOString().slice(0,16).replace(\"T\",\" \") + \" WF-02 v18.3: HWG-FILTER-MATCH \u2192 \" + ($json._hwg_matches || []).join(\", \") + \" \u2192 Status Geblockt, bitte pr\u00fcfen\") }}\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -224,
        304
      ],
      "id": "b6e0d5d3-26e1-4578-8d5b-08e71f910d42",
      "name": "SP: Status Geblockt",
      "disabled": true
    },
    {
      "parameters": {
        "jsCode": "// Avatar-Check: Flag pr\u00fcfen\nconst AVATAR_ENABLED = false;  // \u2190 sp\u00e4ter auf true setzen wenn D-ID l\u00e4uft\n\nreturn [{\n  json: {\n    ...$json,\n    _avatar_enabled: AVATAR_ENABLED\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1344,
        512
      ],
      "id": "2a99f8d9-0bfd-4ce9-9b47-c014994edc19",
      "name": "Code: AVATAR_ENABLED?",
      "disabled": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "leftValue": "={{ $json._avatar_enabled }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -1120,
        512
      ],
      "id": "35b58031-92ec-46d3-8a56-9c602a020017",
      "name": "If: Avatar aktiv?",
      "disabled": true
    },
    {
      "parameters": {
        "content": "## Avatar-Branch (v19+)\n\nWenn `AVATAR_ENABLED=true`:\n1. Reel-Skript via Claude generieren\n2. HWG-Filter auf Skript\n3. POST D-ID API: Talking Photo + Voice\n4. Polling: Job fertig?\n5. MP4-URL in `field_9`\n6. Status: `Bereit`\n\nSiehe `06_Avatar-Reel-Konzepte/Avatar-Integration-Plan.md`",
        "height": 240,
        "width": 320,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -896,
        384
      ],
      "id": "cc5a4266-47f9-46a2-99a8-81cd299496a9",
      "name": "Doku: Avatar TODO (v19)"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://physiofuchs889.sharepoint.com/sites/PhysioFuchsTW/_api/web/lists(guid'd17a6a6f-e6ef-457d-a2a9-4c30ea56120f')/items({{ $('Code: Validate + Route').item.json._sp_item_id }})",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftSharePointOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept",
              "value": "application/json"
            },
            {
              "name": "X-HTTP-Method",
              "value": "MERGE"
            },
            {
              "name": "IF-MATCH",
              "value": "*"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"Status\": \"Wartet-auf-Avatar\",\n  \"Kommentare\": {{ JSON.stringify(($json.sp_fields.Kommentare || \"\") + \"\\n\" + new Date().toISOString().slice(0,16).replace(\"T\",\" \") + \" WF-02 v18.3: Post-Typ \" + $json._post_typ + \" braucht Avatar-Pipeline (AVATAR_ENABLED=false) \u2192 geparkt, wartet auf Aktivierung\") }}\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -896,
        640
      ],
      "id": "0167c135-9be1-4866-8c62-a6d826315135",
      "name": "SP: Status Wartet-auf-Avatar",
      "disabled": true
    }
  ],
  "connections": {
    "Cron: st\u00fcndlich": {
      "main": [
        [
          {
            "node": "SP: Entw\u00fcrfe holen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SP: Entw\u00fcrfe holen": {
      "main": [
        [
          {
            "node": "Code: Validate + Route",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Validate + Route": {
      "main": [
        [
          {
            "node": "Switch: Feed/Avatar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch: Feed/Avatar": {
      "main": [
        [
          {
            "node": "Code: Build Claude Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code: AVATAR_ENABLED?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Build Claude Payload": {
      "main": [
        [
          {
            "node": "HTTP: Claude Caption",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Claude Caption": {
      "main": [
        [
          {
            "node": "Code: Parse Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Parse Claude": {
      "main": [
        [
          {
            "node": "Code: HWG-Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: HWG-Filter": {
      "main": [
        [
          {
            "node": "If: HWG sauber?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If: HWG sauber?": {
      "main": [
        [
          {
            "node": "Code: Foto-Pfad bauen",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "SP: Status Geblockt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Foto-Pfad bauen": {
      "main": [
        [
          {
            "node": "HTTP: SP Fotos listen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: SP Fotos listen": {
      "main": [
        [
          {
            "node": "Code: Foto-Match",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Foto-Match": {
      "main": [
        [
          {
            "node": "If: Foto gefunden?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If: Foto gefunden?": {
      "main": [
        [
          {
            "node": "HTTP: SP Foto Download",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code: Foto-URL final",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: SP Foto Download": {
      "main": [
        [
          {
            "node": "Code: Foto \u2192 GitHub Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Foto \u2192 GitHub Body": {
      "main": [
        [
          {
            "node": "HTTP: GitHub-Push Foto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: GitHub-Push Foto": {
      "main": [
        [
          {
            "node": "Code: Foto-URL final",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Foto-URL final": {
      "main": [
        [
          {
            "node": "Code: HTML bauen (IG+FB)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: HTML bauen (IG+FB)": {
      "main": [
        [
          {
            "node": "Code: IG HTML\u2192Binary",
            "type": "main",
            "index": 0
          },
          {
            "node": "Code: FB HTML\u2192Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: IG HTML\u2192Binary": {
      "main": [
        [
          {
            "node": "HTTP: Gotenberg IG",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Gotenberg IG": {
      "main": [
        [
          {
            "node": "SP: Upload IG-Bild",
            "type": "main",
            "index": 0
          },
          {
            "node": "Code: IG \u2192 GitHub Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SP: Upload IG-Bild": {
      "main": [
        [
          {
            "node": "Code: Merge + Update-Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: FB HTML\u2192Binary": {
      "main": [
        [
          {
            "node": "HTTP: Gotenberg FB",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Gotenberg FB": {
      "main": [
        [
          {
            "node": "SP: Upload FB-Bild",
            "type": "main",
            "index": 0
          },
          {
            "node": "Code: FB \u2192 GitHub Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SP: Upload FB-Bild": {
      "main": [
        [
          {
            "node": "Code: Merge + Update-Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Merge + Update-Payload": {
      "main": [
        [
          {
            "node": "SP: Status Bereit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: AVATAR_ENABLED?": {
      "main": [
        [
          {
            "node": "If: Avatar aktiv?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If: Avatar aktiv?": {
      "main": [
        [],
        [
          {
            "node": "SP: Status Wartet-auf-Avatar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: IG \u2192 GitHub Body": {
      "main": [
        [
          {
            "node": "GitHub: Push IG",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub: Push IG": {
      "main": [
        [
          {
            "node": "Code: Merge + Update-Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: FB \u2192 GitHub Body": {
      "main": [
        [
          {
            "node": "GitHub: Push FB",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub: Push FB": {
      "main": [
        [
          {
            "node": "Code: Merge + Update-Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "v18.3-foto-branch-2026-06-08",
  "id": "6DghT5VFKa9CGG2T-v183",
  "tags": []
}