{
  "name": "Marketing: Blogpost → KI Social Posts (LinkedIn + X) → Content-Kalender",
  "nodes": [
    {
      "id": "webhook-1",
      "name": "Neuer Blogpost (Webhook)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [250, 300],
      "webhookId": "neuer-blogpost",
      "parameters": {
        "httpMethod": "POST",
        "path": "neuer-blogpost",
        "responseMode": "lastNode",
        "options": {}
      },
      "notes": "Empfängt den neuen Blogpost per POST. Erwartete Felder im JSON-Body:\n- url: URL des Blogposts\n- title: Titel\n- content: Volltext oder Zusammenfassung\n- author: Autor\n- category: Kategorie/Thema\n\nKann von WordPress (Webhook-Plugin), CMS, oder manuell getriggert werden."
    },
    {
      "id": "rss-alt",
      "name": "Oder: RSS Feed prüfen",
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.2,
      "position": [250, 550],
      "parameters": {
        "url": "https://dein-blog.de/feed"
      },
      "disabled": true,
      "notes": "ALTERNATIVE zum Webhook: Aktiviere diesen Node und deaktiviere den Webhook, wenn du lieber per RSS-Feed neue Posts erkennen willst. Verbinde den Output mit 'Blogpost-Inhalt laden'. Nutze dann einen Schedule Trigger davor (z.B. alle 30 Min.)."
    },
    {
      "id": "fetch-content",
      "name": "Blogpost-Inhalt laden",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [500, 300],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.url || $json.body?.url || '' }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": false,
              "responseFormat": "text"
            }
          }
        }
      },
      "notes": "Lädt den vollständigen Blogpost-Inhalt von der URL. Falls der Content bereits im Webhook-Body mitkommt, kann dieser Node übersprungen werden."
    },
    {
      "id": "extract-text",
      "name": "Text extrahieren",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [750, 300],
      "parameters": {
        "jsCode": "// Extract clean text from HTML or use provided content\nconst webhookData = $('Neuer Blogpost (Webhook)').first().json;\nconst fetchedData = $input.first().json;\n\n// Use content from webhook body if available, otherwise from fetched page\nlet rawContent = webhookData.body?.content || webhookData.content || '';\nlet title = webhookData.body?.title || webhookData.title || '';\nlet url = webhookData.body?.url || webhookData.url || '';\nlet author = webhookData.body?.author || webhookData.author || '';\nlet category = webhookData.body?.category || webhookData.category || '';\n\n// If no content from webhook, try to extract from fetched HTML\nif (!rawContent && typeof fetchedData.data === 'string') {\n  // Strip HTML tags for a rough text extraction\n  rawContent = fetchedData.data\n    .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n    .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n    .replace(/<nav[^>]*>[\\s\\S]*?<\\/nav>/gi, '')\n    .replace(/<header[^>]*>[\\s\\S]*?<\\/header>/gi, '')\n    .replace(/<footer[^>]*>[\\s\\S]*?<\\/footer>/gi, '')\n    .replace(/<[^>]+>/g, ' ')\n    .replace(/\\s+/g, ' ')\n    .trim();\n  \n  // Limit to ~3000 chars to stay within token limits\n  rawContent = rawContent.substring(0, 3000);\n}\n\n// Extract title from HTML if not provided\nif (!title && typeof fetchedData.data === 'string') {\n  const titleMatch = fetchedData.data.match(/<title[^>]*>([^<]+)<\\/title>/i);\n  if (titleMatch) title = titleMatch[1].trim();\n}\n\nreturn [{\n  json: {\n    title,\n    url,\n    author,\n    category,\n    content: rawContent,\n    fetched_at: new Date().toISOString()\n  }\n}];"
      },
      "notes": "Bereinigt HTML und extrahiert den reinen Text des Blogposts. Verwendet bevorzugt Daten aus dem Webhook-Body, fällt aber auf den geladenen HTML-Content zurück."
    },
    {
      "id": "ai-social",
      "name": "KI erstellt Social Posts",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.1,
      "position": [1000, 300],
      "parameters": {
        "resource": "text",
        "operation": "message",
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "gpt-4o-mini"
        },
        "messages": {
          "values": [
            {
              "content": "=Du bist ein erfahrener Social-Media-Manager. Erstelle aus dem folgenden Blogpost genau 3 Social-Media-Posts.\n\n## Regeln:\n\n### Post 1 – LinkedIn (lang, professionell)\n- 800–1200 Zeichen\n- Hook in der ersten Zeile (Frage, überraschende Aussage, oder Statistik)\n- Danach Zeilenumbruch + Leerzeile (für \"Mehr anzeigen\")\n- Storytelling-Struktur: Problem → Erkenntnis → Lösung\n- Absätze mit Leerzeilen trennen\n- Call-to-Action am Ende\n- 3–5 relevante Hashtags\n- Kein Emoji-Spam, maximal 3 Emojis gezielt eingesetzt\n\n### Post 2 – X/Twitter (kurz, knackig)\n- Maximal 280 Zeichen\n- Provokant oder neugierig machend\n- Link wird separat angehängt (nicht mitzählen)\n- 1–2 Hashtags\n- Optional: 1 Emoji\n\n### Post 3 – X/Twitter Thread-Opener\n- Maximal 280 Zeichen\n- Startet einen Thread (\"🧵 Thread:\" oder ähnlich)\n- Macht neugierig auf die Key Takeaways des Blogposts\n- 1–2 Hashtags\n\n## Antwortformat (STRIKT als JSON, kein Markdown drumherum):\n{\n  \"linkedin_post\": \"\",\n  \"twitter_post\": \"\",\n  \"twitter_thread_opener\": \"\",\n  \"kernaussage\": \"\",\n  \"zielgruppe\": \"\",\n  \"hashtags\": []\n}\n\n## Blogpost-Daten:\nTitel: {{ $json.title }}\nURL: {{ $json.url }}\nKategorie: {{ $json.category }}\nAutor: {{ $json.author }}\n\nInhalt:\n{{ $json.content }}"
            }
          ]
        },
        "options": {
          "temperature": 0.7,
          "maxTokens": 2000
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI API"
        }
      },
      "notes": "GPT-4o-mini erstellt 3 plattformspezifische Social-Media-Posts: einen langen LinkedIn-Post, einen kurzen X/Twitter-Post, und einen Thread-Opener."
    },
    {
      "id": "parse-posts",
      "name": "Posts parsen & aufbereiten",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1250, 300],
      "parameters": {
        "jsCode": "const aiResponse = $input.first().json.message.content;\nconst blogData = $('Text extrahieren').first().json;\n\ntry {\n  let jsonStr = aiResponse;\n  const jsonMatch = aiResponse.match(/\\{[\\s\\S]*\\}/);\n  if (jsonMatch) jsonStr = jsonMatch[0];\n  \n  const posts = JSON.parse(jsonStr);\n  const today = new Date();\n  \n  // Suggested posting schedule: LinkedIn today, Tweet tomorrow, Thread day after\n  const linkedinDate = new Date(today);\n  linkedinDate.setHours(9, 0, 0, 0);\n  \n  const twitterDate = new Date(today);\n  twitterDate.setDate(today.getDate() + 1);\n  twitterDate.setHours(12, 0, 0, 0);\n  \n  const threadDate = new Date(today);\n  threadDate.setDate(today.getDate() + 2);\n  threadDate.setHours(14, 0, 0, 0);\n  \n  return [\n    {\n      json: {\n        plattform: 'LinkedIn',\n        post_text: posts.linkedin_post,\n        zeichenanzahl: posts.linkedin_post.length,\n        blogpost_titel: blogData.title,\n        blogpost_url: blogData.url,\n        kategorie: blogData.category,\n        autor: blogData.author,\n        kernaussage: posts.kernaussage,\n        zielgruppe: posts.zielgruppe,\n        hashtags: (posts.hashtags || []).join(', '),\n        geplant_fuer: linkedinDate.toISOString().replace('T', ' ').substring(0, 16),\n        status: 'Entwurf',\n        erstellt_am: today.toISOString().split('T')[0]\n      }\n    },\n    {\n      json: {\n        plattform: 'X/Twitter',\n        post_text: posts.twitter_post,\n        zeichenanzahl: posts.twitter_post.length,\n        blogpost_titel: blogData.title,\n        blogpost_url: blogData.url,\n        kategorie: blogData.category,\n        autor: blogData.author,\n        kernaussage: posts.kernaussage,\n        zielgruppe: posts.zielgruppe,\n        hashtags: (posts.hashtags || []).join(', '),\n        geplant_fuer: twitterDate.toISOString().replace('T', ' ').substring(0, 16),\n        status: 'Entwurf',\n        erstellt_am: today.toISOString().split('T')[0]\n      }\n    },\n    {\n      json: {\n        plattform: 'X/Twitter Thread',\n        post_text: posts.twitter_thread_opener,\n        zeichenanzahl: posts.twitter_thread_opener.length,\n        blogpost_titel: blogData.title,\n        blogpost_url: blogData.url,\n        kategorie: blogData.category,\n        autor: blogData.author,\n        kernaussage: posts.kernaussage,\n        zielgruppe: posts.zielgruppe,\n        hashtags: (posts.hashtags || []).join(', '),\n        geplant_fuer: threadDate.toISOString().replace('T', ' ').substring(0, 16),\n        status: 'Entwurf',\n        erstellt_am: today.toISOString().split('T')[0]\n      }\n    }\n  ];\n} catch (e) {\n  return [{\n    json: {\n      error: true,\n      message: 'KI-Posts konnten nicht geparst werden: ' + e.message,\n      raw_response: aiResponse\n    }\n  }];\n}"
      },
      "notes": "Parst die 3 KI-generierten Posts in einzelne Items mit Metadaten. Schlägt automatisch einen Posting-Zeitplan vor: LinkedIn heute 9 Uhr, Tweet morgen 12 Uhr, Thread übermorgen 14 Uhr."
    },
    {
      "id": "if-error",
      "name": "Posts OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [1500, 300],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-error",
              "leftValue": "={{ $json.error }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notTrue"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "notes": "Prüft ob die KI-Posts erfolgreich geparst wurden."
    },
    {
      "id": "sheets-kalender",
      "name": "Content-Kalender (Google Sheets)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [1750, 200],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "GOOGLE_SHEET_URL_HIER_EINFUEGEN"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Content-Kalender"
        },
        "columns": {
          "mappingMode": "autoMapInputData"
        },
        "options": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets OAuth2"
        }
      },
      "notes": "Schreibt alle 3 Posts als separate Zeilen in den Content-Kalender. Erstelle ein Sheet 'Content-Kalender' mit den Spalten:\nplattform | post_text | zeichenanzahl | blogpost_titel | blogpost_url | kategorie | autor | kernaussage | zielgruppe | hashtags | geplant_fuer | status | erstellt_am"
    },
    {
      "id": "slack-notify",
      "name": "Team benachrichtigen",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [2000, 200],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": {
          "__rl": true,
          "mode": "name",
          "value": "#marketing"
        },
        "text": "=:mega: *Neue Social-Media-Posts erstellt!*\n\nBlogpost: *{{ $('Text extrahieren').first().json.title }}*\n:link: {{ $('Text extrahieren').first().json.url }}\n\n:white_check_mark: 3 Posts im Content-Kalender:\n• LinkedIn-Post ({{ $('Posts parsen & aufbereiten').all()[0].json.zeichenanzahl }} Zeichen)\n• X/Twitter-Post ({{ $('Posts parsen & aufbereiten').all()[1].json.zeichenanzahl }} Zeichen)\n• X/Twitter Thread-Opener ({{ $('Posts parsen & aufbereiten').all()[2].json.zeichenanzahl }} Zeichen)\n\n:calendar: Vorgeschlagener Zeitplan:\n• LinkedIn: {{ $('Posts parsen & aufbereiten').all()[0].json.geplant_fuer }}\n• X/Twitter: {{ $('Posts parsen & aufbereiten').all()[1].json.geplant_fuer }}\n• Thread: {{ $('Posts parsen & aufbereiten').all()[2].json.geplant_fuer }}\n\n:dart: Zielgruppe: {{ $('Posts parsen & aufbereiten').first().json.zielgruppe }}\n\nBitte im Content-Kalender reviewen und freigeben!",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "",
          "name": "Slack API"
        }
      },
      "notes": "Benachrichtigt das Marketing-Team in Slack mit einer Übersicht aller generierten Posts, Zeichenanzahl und vorgeschlagenem Posting-Zeitplan."
    },
    {
      "id": "error-notify",
      "name": "Fehler melden",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [1750, 450],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": {
          "__rl": true,
          "mode": "name",
          "value": "#marketing"
        },
        "text": "=:warning: *Social-Post-Generierung fehlgeschlagen*\n\nBlogpost: {{ $('Text extrahieren').first().json.title }}\nFehler: {{ $json.message }}\n\nBitte manuell erstellen.",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "",
          "name": "Slack API"
        }
      },
      "notes": "Benachrichtigt bei Fehlern, damit kein Blogpost ohne Social-Posts bleibt."
    }
  ],
  "connections": {
    "Neuer Blogpost (Webhook)": {
      "main": [
        [
          {
            "node": "Blogpost-Inhalt laden",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Blogpost-Inhalt laden": {
      "main": [
        [
          {
            "node": "Text extrahieren",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text extrahieren": {
      "main": [
        [
          {
            "node": "KI erstellt Social Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "KI erstellt Social Posts": {
      "main": [
        [
          {
            "node": "Posts parsen & aufbereiten",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Posts parsen & aufbereiten": {
      "main": [
        [
          {
            "node": "Posts OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Posts OK?": {
      "main": [
        [
          {
            "node": "Content-Kalender (Google Sheets)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fehler melden",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Content-Kalender (Google Sheets)": {
      "main": [
        [
          {
            "node": "Team benachrichtigen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "tags": [
    {
      "name": "Marketing"
    },
    {
      "name": "Social Media"
    },
    {
      "name": "Content"
    }
  ]
}
