{ "name": "pruefen-und-importieren", "nodes": [ { "parameters": {}, "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ -460, 100 ], "id": "793c9880-e989-44d5-b514-d6850b64fd2e", "name": "Manuell starten" }, { "parameters": { "httpMethod": "POST", "path": "pruefen-und-importieren", "responseMode": "onReceived", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -460, 280 ], "id": "0bb95de2-96dd-4f8b-af1e-3bab20eca241", "name": "Webhook Start", "webhookId": "53c83399-7059-4816-8f7c-c606015c02c0" }, { "parameters": { "mode": "manual", "assignments": { "assignments": [ { "id": "f161eded-c23e-4d60-9d8b-d4db483ec3d9", "name": "nw_base", "value": "https://app.neuronwriter.com/neuron-api/0.5/writer", "type": "string" }, { "id": "0544c6b7-e388-4bb4-b418-2dc97bc56bb6", "name": "gitea_base", "value": "http://gitea:3000", "type": "string" }, { "id": "46b15bb0-7e1a-48f5-addb-e8ca1a46491a", "name": "gitea_owner", "value": "lucas.orth", "type": "string" }, { "id": "46eb7849-83d0-4cd2-b978-6a49c4ddb31e", "name": "gitea_repo", "value": "Skrift-Auto-Article", "type": "string" }, { "id": "84f7d295-04fa-4f95-b723-98f0dc1a042b", "name": "branch", "value": "main", "type": "string" }, { "id": "1bd8cf46-e184-47c5-897a-aa4b4d08d591", "name": "artikel_ordner", "value": "artikel", "type": "string" }, { "id": "1c6a8050-c17c-4635-b371-cc5a40fda4e5", "name": "brief_ordner", "value": "briefe", "type": "string" }, { "id": "9eb50d4b-0ac5-4bd9-8733-fe1bcb82b601", "name": "nachbessern_ordner", "value": "nachbessern", "type": "string" }, { "id": "91ec4a89-c907-403d-aaa4-77e074a85992", "name": "status_pfad", "value": "status-importe.json", "type": "string" }, { "id": "7e6d86e6-4195-4cdd-b3b2-1ee1fceb9805", "name": "wp_base", "value": "https://skrift.de", "type": "string" }, { "id": "405fcf78-a5a0-4fb8-9f04-d8041906f2b3", "name": "score_ziel", "value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 72 }}", "type": "number" }, { "id": "4e9becf4-a380-407b-b4b9-b8b821e2992f", "name": "pause_zwischen_artikeln", "value": 5, "type": "number" }, { "id": "11c7167c-f60e-47c6-9832-9ef0124628c2", "name": "elementor_ausgabe", "value": false, "type": "boolean" }, { "id": "0a4b7c2b-14f5-43cb-ae2a-64d30d58a1f4", "name": "faq_ueberschrift", "value": "Häufige Fragen", "type": "string" }, { "id": "debc59ab-4e8e-44de-80c2-334c15fc55ac", "name": "benachrichtigung_an", "value": "privat@lucas-orth.de", "type": "string" } ] }, "options": {} }, "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ -240, 190 ], "id": "86f2b355-8a1a-4336-b2c9-1a2cbb0aa6f1", "name": "Konfiguration" }, { "parameters": { "method": "GET", "url": "={{ $json.gitea_base }}/api/v1/repos/{{ $json.gitea_owner }}/{{ $json.gitea_repo }}/contents/{{ $json.artikel_ordner }}?ref={{ $json.branch }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": true, "fullResponse": true } }, "timeout": 60000 } }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ -20, 190 ], "id": "144a0300-78e5-4f50-828c-0e0dc617947c", "name": "Artikelordner lesen", "credentials": { "httpHeaderAuth": { "id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article" } }, "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst antwort = $input.first().json;\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst ka = K(antwort);\nif (antwort.statusCode === 404) {\n return [{ json: { leer: true, grund: 'Ordner \"artikel\" existiert noch nicht im Repo.' } }];\n}\nif (antwort.statusCode !== 200) {\n throw new Error('Artikelordner konnte nicht gelesen werden (HTTP ' + antwort.statusCode\n + '). Antwort: ' + JSON.stringify(ka).slice(0, 400));\n}\nconst dateien = (ka || []).filter(e =>\n e.type === 'file' && /^artikel-.+\\.json$/.test(e.name));\n\nif (!dateien.length) {\n return [{ json: { leer: true, grund: 'Keine Dateien nach Muster artikel-.json gefunden.' } }];\n}\nreturn dateien.map(d => ({ json: {\n name: d.name, pfad: d.path, sha: d.sha,\n slug: d.name.replace(/^artikel-/, '').replace(/\\.json$/, ''),\n}}));\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 200, 190 ], "id": "03942d18-af94-439b-89a9-d9734d845f83", "name": "Artikeldateien filtern" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 }, "conditions": [ { "id": "dd545030-181b-40c1-ba1c-563e93f9cdf9", "leftValue": "={{ $json.leer === true }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "looseTypeValidation": true, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 420, 190 ], "id": "ac2420c7-c498-4e3e-a26e-542f7d585a35", "name": "Artikel vorhanden?" }, { "parameters": {}, "type": "n8n-nodes-base.noOp", "typeVersion": 1, "position": [ 640, 40 ], "id": "1f77ee2a-d8e9-4bcf-9ac9-5f1c1772d5ac", "name": "Nichts zu tun" }, { "parameters": { "batchSize": 1, "options": { "reset": false } }, "type": "n8n-nodes-base.splitInBatches", "typeVersion": 3, "position": [ 640, 320 ], "id": "11279052-fb96-4d7b-b03a-4a64b524ab30", "name": "Artikel nacheinander" }, { "parameters": { "method": "GET", "url": "={{ $('Konfiguration').first().json.gitea_base }}/api/v1/repos/{{ $('Konfiguration').first().json.gitea_owner }}/{{ $('Konfiguration').first().json.gitea_repo }}/contents/{{ $json.pfad }}?ref={{ $('Konfiguration').first().json.branch }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": false } }, "timeout": 60000 } }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 900, 420 ], "id": "d36af43a-e39d-4e20-9ef8-00101a0dfef5", "name": "Artikeldatei laden", "credentials": { "httpHeaderAuth": { "id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article" } }, "onError": "continueErrorOutput", "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst a = $('Artikel nacheinander').first().json;\nreturn [{ json: { slug: a.slug, ergebnis: 'fehler', stufe: 'artikel-laden',\n meldung: 'Artikeldatei ' + a.pfad + ' konnte nicht geladen werden: '\n + JSON.stringify($input.first().json).slice(0, 400) }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 900, 640 ], "id": "49f78b05-7b28-4ca6-be18-fb0fb27cfcb9", "name": "Fehler Artikeldatei", "onError": "continueRegularOutput" }, { "parameters": { "method": "GET", "url": "={{ $('Konfiguration').first().json.gitea_base }}/api/v1/repos/{{ $('Konfiguration').first().json.gitea_owner }}/{{ $('Konfiguration').first().json.gitea_repo }}/contents/{{ $('Konfiguration').first().json.brief_ordner }}/brief-{{ $('Artikel nacheinander').first().json.slug }}.json?ref={{ $('Konfiguration').first().json.branch }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": true, "fullResponse": true } }, "timeout": 60000 } }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 1120, 420 ], "id": "88ea6484-2c70-4e73-870f-404eb5d43676", "name": "Brief laden", "credentials": { "httpHeaderAuth": { "id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article" } } }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst meta = $('Artikel nacheinander').first().json;\nconst artRoh = $('Artikeldatei laden').first().json;\nconst briefAntwort = $input.first().json;\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kb = K(briefAntwort);\n\nlet artikel;\ntry { artikel = JSON.parse(Buffer.from(artRoh.content, 'base64').toString('utf8')); }\ncatch (e) { throw new Error('artikel-' + meta.slug + '.json ist kein gueltiges JSON: ' + e.message); }\n\nconst pflicht = ['slug', 'titel', 'query_id', 'kategorie', 'content_html'];\nconst fehlt = pflicht.filter(f => !artikel[f]);\nif (fehlt.length) {\n throw new Error('artikel-' + meta.slug + '.json fehlen Pflichtfelder: ' + fehlt.join(', ')\n + '. Erwartetes Format siehe README Abschnitt \"Vertrag Cowork -> Workflow 2\".');\n}\nif (artikel.slug !== meta.slug) {\n throw new Error('Slug im Dateinamen (' + meta.slug + ') und im JSON (' + artikel.slug + ') stimmen nicht ueberein.');\n}\n\nlet brief = null;\nif (briefAntwort.statusCode === 200 && kb && kb.content) {\n try { brief = JSON.parse(Buffer.from(kb.content, 'base64').toString('utf8')); }\n catch (e) { brief = null; }\n}\n\n// Preise muessen als Shortcode stehen, nie ausgeschrieben.\nconst preisTreffer = [];\nconst re = /(\\d{1,3}(?:[.,]\\d{1,2})?)\\s*(?:€|EUR|Euro)/gi;\nlet m;\nwhile ((m = re.exec(String(artikel.content_html || ''))) !== null) {\n preisTreffer.push(m[0].trim());\n}\nconst shortcodes = (String(artikel.content_html || '').match(/\\[skrift_preis[^\\]]*\\]/g) || []);\n\n// NeuronWriter bewertet nur den uebergebenen Text. Kurzantwort und FAQ stehen\n// aber auf der fertigen Seite, also gehoeren sie in die Bewertung. Der gespeicherte\n// Artikel bleibt unveraendert, das hier ist nur der Pruefkoerper.\nconst faqText = (Array.isArray(artikel.faq) ? artikel.faq : [])\n .map(f => `

${f.frage || ''}

${f.antwort || ''}

`).join('\\n');\nconst bewertungs_html = [\n artikel.kurzantwort ? `

${artikel.kurzantwort}

` : '',\n artikel.content_html || '',\n faqText ? `

Häufige Fragen

\\n${faqText}` : '',\n].filter(Boolean).join('\\n');\n\nreturn [{ json: { slug: meta.slug, artikel, brief, brief_vorhanden: !!brief,\n bewertungs_html,\n preis_warnung: preisTreffer.length\n ? `${preisTreffer.length} ausgeschriebene(r) Preis(e) im Text: ${[...new Set(preisTreffer)].slice(0,5).join(', ')}. `\n + 'Bitte durch [skrift_preis key=\"...\"] ersetzen.'\n : null,\n preis_shortcodes: shortcodes.length } }];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1340, 420 ], "id": "d0f641c2-78a7-4b25-addb-05044edbed6e", "name": "Artikel pruefen" }, { "parameters": { "method": "POST", "url": "={{ $('Konfiguration').first().json.nw_base }}/evaluate-content", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": false } }, "timeout": 60000 }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ query: $json.artikel.query_id, html: $json.bewertungs_html || $json.artikel.content_html, title: $json.artikel.seo_titel || $json.artikel.titel, description: $json.artikel.seo_beschreibung || '' }) }}", "sendHeaders": true, "specifyHeaders": "json", "jsonHeaders": "={\"Content-Type\":\"application/json\"}" }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 1560, 420 ], "id": "a7eb667d-7eec-4a65-8d90-206084368755", "name": "Score ermitteln", "credentials": { "httpHeaderAuth": { "id": "U23n2O980GKWocWq", "name": "NeuronWriter API" } }, "onError": "continueErrorOutput", "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst p = $('Artikel pruefen').first().json;\nreturn [{ json: { slug: p.slug, ergebnis: 'fehler', stufe: 'evaluate-content',\n meldung: 'NeuronWriter /evaluate-content fehlgeschlagen. Haeufigste Ursache: query_id \"'\n + p.artikel.query_id + '\" existiert nicht oder gehoert zu einem anderen Projekt. Antwort: '\n + JSON.stringify($input.first().json).slice(0, 400) }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1560, 640 ], "id": "902fc43d-9265-43ac-9c0b-a75a9c7da079", "name": "Fehler Score", "onError": "continueRegularOutput" }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst p = $('Artikel pruefen').first().json;\nconst e = $input.first().json;\nconst ziel = $('Konfiguration').first().json.score_ziel;\n\nif (typeof e.content_score !== 'number') {\n throw new Error('NeuronWriter lieferte kein numerisches content_score. Antwort: '\n + JSON.stringify(e).slice(0, 300));\n}\nreturn [{ json: { ...p, score: e.content_score, score_ziel: ziel, bestanden: e.content_score >= ziel } }];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1780, 420 ], "id": "8e45bb62-1738-4149-bd65-96b671fc3186", "name": "Score auswerten" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 }, "conditions": [ { "id": "b6a9240e-c1a2-4178-ae65-05534281a1a9", "leftValue": "={{ $json.bestanden }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "looseTypeValidation": true, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 2000, 420 ], "id": "d240d88f-009d-423b-a781-6fd6317f0c99", "name": "Score erreicht?" }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\n// /evaluate-content liefert nur {status, content_score}, keine Begriffsliste.\n// Die Luecken werden deshalb gegen den Brief aus Workflow 1 selbst berechnet.\nconst s = $input.first().json;\nconst brief = s.brief;\n\nconst text = String(s.bewertungs_html || s.artikel.content_html || '')\n .replace(//gi, ' ')\n .replace(//gi, ' ')\n .replace(/<[^>]+>/g, ' ')\n .replace(/ /g, ' ')\n .replace(/\\s+/g, ' ')\n .toLowerCase();\n\nconst zaehle = (begriff) => {\n const b = String(begriff).toLowerCase().trim();\n if (!b) return 0;\n return text.split(b).length - 1;\n};\n\nconst pruefe = (liste) => (liste || []).map(e => {\n const ist = zaehle(e.begriff);\n const min = e.min ?? 1;\n return { begriff: e.begriff, soll_min: min, soll_max: e.max ?? null, ist,\n fehlt: ist < min, luecke: Math.max(0, min - ist) };\n});\n\nlet pflicht = [], erweitert = [], fragen_offen = [];\nif (brief) {\n pflicht = pruefe(brief.begriffe_pflicht).filter(x => x.fehlt);\n erweitert = pruefe(brief.begriffe_erweitert).filter(x => x.fehlt);\n fragen_offen = (brief.fragen || [])\n .filter(f => f.gewicht && f.gewicht >= 6)\n .filter(f => {\n const kern = String(f.frage).toLowerCase()\n .replace(/[?.,!]/g, '').split(' ').filter(w => w.length > 5).slice(0, 3);\n return kern.length ? !kern.every(w => text.includes(w)) : false;\n })\n .map(f => ({ frage: f.frage, gewicht: f.gewicht }));\n}\n\nconst woerter = text.split(' ').filter(Boolean).length;\n\nconst bericht = {\n slug: s.slug,\n titel: s.artikel.titel,\n query_id: s.artikel.query_id,\n score: s.score,\n score_ziel: s.score_ziel,\n geprueft_am: new Date().toISOString(),\n brief_vorhanden: s.brief_vorhanden,\n wortzahl: { ist: woerter, ziel: brief ? brief.wortzahl_ziel : null,\n differenz: brief && brief.wortzahl_ziel ? woerter - brief.wortzahl_ziel : null },\n fehlende_pflichtbegriffe: pflicht,\n fehlende_zusatzbegriffe: erweitert,\n unbeantwortete_fragen: fragen_offen,\n hinweis: s.brief_vorhanden\n ? 'Begriffe gegen briefe/brief-' + s.slug + '.json abgeglichen. Zaehlung als Teilstring, Komposita zaehlen mit.'\n : 'Kein Brief gefunden, daher nur der Score. Workflow 1 fuer diesen Slug erneut laufen lassen.',\n};\n\nconst konf = $('Konfiguration').first().json;\nreturn [{ json: { slug: s.slug, score: s.score, bericht,\n pfad: konf.nachbessern_ordner + '/nachbessern-' + s.slug + '.json',\n anzahl_pflicht: pflicht.length, anzahl_erweitert: erweitert.length }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2220, 640 ], "id": "3dfd0f0e-f104-44d2-a44b-3cd7705d4833", "name": "Fehlende Begriffe ermitteln" }, { "parameters": { "method": "GET", "url": "={{ $('Konfiguration').first().json.gitea_base }}/api/v1/repos/{{ $('Konfiguration').first().json.gitea_owner }}/{{ $('Konfiguration').first().json.gitea_repo }}/contents/{{ $json.pfad }}?ref={{ $('Konfiguration').first().json.branch }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": true, "fullResponse": true } }, "timeout": 60000 } }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 2440, 640 ], "id": "2320c737-09a9-4d44-8e70-9be83b3d2008", "name": "Nachbesserung vorhanden?", "credentials": { "httpHeaderAuth": { "id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article" } } }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst n = $('Fehlende Begriffe ermitteln').first().json;\nconst p = $input.first().json;\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kp = K(p);\nconst da = p.statusCode === 200 && kp && kp.sha;\nconst konf = $('Konfiguration').first().json;\nreturn [{ json: { ...n, methode: da ? 'PUT' : 'POST', commit_body: JSON.stringify({\n content: Buffer.from(JSON.stringify(n.bericht, null, 2), 'utf8').toString('base64'),\n message: `Nachbesserung ${n.slug}: Score ${n.score} unter Ziel`,\n branch: konf.branch, ...(da ? { sha: kp.sha } : {}),\n})}}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2660, 640 ], "id": "ce049aa8-6b87-4fd3-be30-ff5e9ae85e88", "name": "Nachbesserung vorbereiten" }, { "parameters": { "method": "={{ $json.methode }}", "url": "={{ $('Konfiguration').first().json.gitea_base }}/api/v1/repos/{{ $('Konfiguration').first().json.gitea_owner }}/{{ $('Konfiguration').first().json.gitea_repo }}/contents/{{ $json.pfad }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": false } }, "timeout": 60000 }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ $json.commit_body }}", "sendHeaders": true, "specifyHeaders": "json", "jsonHeaders": "={\"Content-Type\":\"application/json\"}" }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 2880, 640 ], "id": "e16e2245-e642-42f3-91e1-1149d52dd502", "name": "Nachbesserung committen", "credentials": { "httpHeaderAuth": { "id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article" } }, "onError": "continueErrorOutput", "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst n = $('Fehlende Begriffe ermitteln').first().json;\nreturn [{ json: { slug: n.slug, ergebnis: 'fehler', stufe: 'nachbessern-commit', score: n.score,\n meldung: 'Nachbesserungsdatei konnte nicht geschrieben werden: '\n + JSON.stringify($input.first().json).slice(0, 400) }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2880, 860 ], "id": "88172a57-71cf-4c76-a148-3e625df08e72", "name": "Fehler Nachbesserung", "onError": "continueRegularOutput" }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst n = $('Fehlende Begriffe ermitteln').first().json;\nconst p = $('Artikel pruefen').first().json;\nreturn [{ json: { slug: n.slug, ergebnis: 'nachbessern', score: n.score,\n preis_warnung: p.preis_warnung || null,\n score_ziel: n.bericht.score_ziel, pfad: n.pfad,\n anzahl_pflicht: n.anzahl_pflicht, anzahl_erweitert: n.anzahl_erweitert,\n wortzahl: n.bericht.wortzahl,\n meldung: `Score ${n.score} unter Ziel ${n.bericht.score_ziel}. `\n + `${n.anzahl_pflicht} Pflichtbegriffe fehlen, ${n.anzahl_erweitert} Zusatzbegriffe.` }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3100, 640 ], "id": "3d34a3d6-1ce3-41a2-aa66-b9e5d9065708", "name": "Nachbesserung gemerkt" }, { "parameters": { "method": "POST", "url": "={{ $('Konfiguration').first().json.nw_base }}/import-content", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": false } }, "timeout": 60000 }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ query: $json.artikel.query_id, html: $json.bewertungs_html || $json.artikel.content_html, title: $json.artikel.seo_titel || $json.artikel.titel, description: $json.artikel.seo_beschreibung || '' }) }}", "sendHeaders": true, "specifyHeaders": "json", "jsonHeaders": "={\"Content-Type\":\"application/json\"}" }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 2220, 240 ], "id": "8bd24b62-3161-4596-9dc0-9c73a434ffe8", "name": "Revision zu NeuronWriter", "credentials": { "httpHeaderAuth": { "id": "U23n2O980GKWocWq", "name": "NeuronWriter API" } }, "onError": "continueErrorOutput", "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst s = $('Score auswerten').first().json;\nreturn [{ json: { ...s, import_warnung: 'Revision konnte nicht zu NeuronWriter gespeichert werden: '\n + JSON.stringify($input.first().json).slice(0, 300) }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2220, 60 ], "id": "295a6ba5-75f4-4807-8090-23720da69936", "name": "Fehler Revision", "onError": "continueRegularOutput" }, { "parameters": { "method": "GET", "url": "={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/posts?slug={{ $('Score auswerten').first().json.slug }}&status=publish,draft,pending,future,private&context=edit", "authentication": "genericCredentialType", "genericAuthType": "httpBasicAuth", "options": { "response": { "response": { "neverError": true, "fullResponse": true } }, "timeout": 60000 } }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 2440, 240 ], "id": "01befefc-e4ef-48a5-943f-6aa732736c99", "name": "Beitrag vorhanden?", "credentials": { "httpBasicAuth": { "id": "WIN05TZgM0K5VOGy", "name": "WP skrift.de Redakteur" } }, "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "method": "GET", "url": "={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/categories?search={{ encodeURIComponent($('Score auswerten').first().json.artikel.kategorie) }}&per_page=100", "authentication": "genericCredentialType", "genericAuthType": "httpBasicAuth", "options": { "response": { "response": { "neverError": true, "fullResponse": true } }, "timeout": 60000 } }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 2660, 240 ], "id": "70e77372-0d3f-4c68-815b-ddb13d7f18e8", "name": "Kategorie suchen", "credentials": { "httpBasicAuth": { "id": "WIN05TZgM0K5VOGy", "name": "WP skrift.de Redakteur" } }, "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst s = $('Score auswerten').first().json;\nconst gesucht = String(s.artikel.kategorie).trim();\nconst antwort = $input.first().json;\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst ka = K(antwort);\nconst treffer = (antwort.statusCode === 200 && Array.isArray(ka))\n ? ka.find(c => c.name.toLowerCase() === gesucht.toLowerCase()) : null;\nreturn [{ json: { ...s, kategorie_id: treffer ? treffer.id : null,\n kategorie_name: gesucht, kategorie_fehlt: !treffer } }];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2880, 240 ], "id": "a0b6cc70-8253-4317-af8c-52da2a11d384", "name": "Kategorie pruefen" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 }, "conditions": [ { "id": "88bdb3bb-b27e-4f62-a3e3-81ff95173268", "leftValue": "={{ $json.kategorie_fehlt }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "looseTypeValidation": true, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 3100, 240 ], "id": "fd48f6d4-961a-4219-a245-55582aefbbce", "name": "Kategorie anlegen?" }, { "parameters": { "method": "POST", "url": "={{ $('Konfiguration').first().json.wp_base }}/wp-json/wp/v2/categories", "authentication": "genericCredentialType", "genericAuthType": "httpBasicAuth", "options": { "response": { "response": { "neverError": false } }, "timeout": 60000 }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ name: $json.kategorie_name }) }}", "sendHeaders": true, "specifyHeaders": "json", "jsonHeaders": "={\"Content-Type\":\"application/json\"}" }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 3320, 140 ], "id": "4a56f882-d5f3-463c-aaa3-c94414f8c214", "name": "Kategorie anlegen", "credentials": { "httpBasicAuth": { "id": "WIN05TZgM0K5VOGy", "name": "WP skrift.de Redakteur" } }, "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst s = $('Kategorie pruefen').first().json;\nconst e = $input.first().json;\nconst id = s.kategorie_id || e.id || null;\nif (!id) {\n throw new Error('Kategorie \"' + s.kategorie_name + '\" konnte weder gefunden noch angelegt werden. '\n + 'Antwort: ' + JSON.stringify(e).slice(0, 300));\n}\nreturn [{ json: { ...s, kategorie_id: id } }];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3540, 240 ], "id": "4f8157e0-d898-49dd-a112-863e63ffa550", "name": "Kategorie festlegen" }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst s = $input.first().json;\nconst dup = $('Beitrag vorhanden?').first().json;\nconst a = s.artikel;\n\n// Duplikatspruefung: exakter Slug-Treffer entscheidet ueber anlegen oder aktualisieren.\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kd = K(dup);\nconst treffer = (dup.statusCode === 200 && Array.isArray(kd))\n ? kd.find(p => p.slug === s.slug) : null;\n\nconst heute = new Date().toISOString().slice(0, 10);\n\nconst meta = {\n kurzantwort: a.kurzantwort || '',\n cta_ziel: a.cta_ziel || '',\n stand_datum: a.stand_datum || heute,\n faq: Array.isArray(a.faq)\n ? a.faq.filter(f => f && f.frage).map(f => ({ frage: String(f.frage), antwort: String(f.antwort || '') }))\n : [],\n _seopress_titles_title: a.seo_titel || a.titel,\n _seopress_titles_desc: a.seo_beschreibung || '',\n _seopress_analysis_target_kw: a.keyword || '',\n};\n\n// --- Elementor-Struktur aus Artikel und FAQ ---\n// Zerlegt den Text an den H2-Grenzen in Text-Editor-Widgets und haengt das FAQ\n// als nested-accordion an, dasselbe Widget wie auf den Leistungsseiten.\nconst konf = $('Konfiguration').first().json;\nconst eid = () => Math.random().toString(16).slice(2, 9);\nconst textWidget = (h) => ({ id: eid(), elType: 'widget', settings: { editor: h },\n elements: [], widgetType: 'text-editor' });\nconst behaelter = (kinder, innen) => {\n const s = { content_width: 'full' };\n if (innen) Object.assign(s, {\n _title: 'Element',\n padding: { unit: 'px', top: '8', right: '8', bottom: '8', left: '8', isLinked: true },\n flex_gap: { column: '20', row: '20', isLinked: true, unit: 'px', size: 20 },\n });\n const c = { id: eid(), elType: 'container', settings: s, elements: kinder };\n if (innen) { c.isInner = true; c.isLocked = true; }\n return c;\n};\n\nfunction elementorBauen() {\n const wurzel = [];\n if (a.kurzantwort) {\n wurzel.push(behaelter([textWidget(\n `

${a.kurzantwort}

`)]));\n }\n const html = String(a.content_html || '');\n const stellen = [];\n const re2 = /]/g;\n let mm;\n while ((mm = re2.exec(html)) !== null) stellen.push(mm.index);\n const bloecke = [];\n if (!stellen.length) {\n if (html.trim()) bloecke.push(html.trim());\n } else {\n if (stellen[0] > 0 && html.slice(0, stellen[0]).trim()) bloecke.push(html.slice(0, stellen[0]).trim());\n stellen.forEach((s, i) => {\n const ende = i + 1 < stellen.length ? stellen[i + 1] : html.length;\n const b = html.slice(s, ende).trim();\n if (b) bloecke.push(b);\n });\n }\n bloecke.forEach(b => wurzel.push(behaelter([textWidget(b)])));\n\n if (meta.faq.length) {\n const items = [], kinder = [];\n meta.faq.forEach(f => {\n items.push({ item_title: f.frage, _id: eid() });\n kinder.push(behaelter([textWidget(`

${f.antwort || ''}

`)], true));\n });\n wurzel.push(behaelter([\n textWidget(`

${konf.faq_ueberschrift || 'Häufige Fragen'}

`),\n { id: eid(), elType: 'widget', widgetType: 'nested-accordion',\n settings: { items, faq_schema: 'yes', default_state: 'all_collapsed' },\n elements: kinder },\n ]));\n }\n return wurzel;\n}\n\nif (konf.elementor_ausgabe) {\n meta._elementor_data = JSON.stringify(elementorBauen());\n meta._elementor_edit_mode = 'builder';\n meta._elementor_template_type = 'wp-post';\n}\n\nconst body = {\n title: a.titel,\n slug: s.slug,\n content: a.content_html,\n status: 'draft', // harte Vorgabe: nie automatisch veroeffentlichen\n categories: [s.kategorie_id],\n meta,\n};\nif (a.auszug) body.excerpt = a.auszug;\n\nreturn [{ json: {\n ...s,\n vorhanden: !!treffer,\n post_id: treffer ? treffer.id : null,\n alter_status: treffer ? treffer.status : null,\n wp_url: treffer\n ? `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts/${treffer.id}`\n : `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts`,\n wp_body: JSON.stringify(body),\n}}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3760, 240 ], "id": "fdb757fc-b0bc-4def-8a93-0e679a669b9e", "name": "WordPress-Body bauen" }, { "parameters": { "method": "POST", "url": "={{ $json.wp_url }}", "authentication": "genericCredentialType", "genericAuthType": "httpBasicAuth", "options": { "response": { "response": { "neverError": false } }, "timeout": 60000 }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ $json.wp_body }}", "sendHeaders": true, "specifyHeaders": "json", "jsonHeaders": "={\"Content-Type\":\"application/json\"}" }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 3980, 240 ], "id": "af5dc8ab-bcae-4b0c-975e-e7535ab616f4", "name": "Beitrag anlegen oder aktualisieren", "credentials": { "httpBasicAuth": { "id": "WIN05TZgM0K5VOGy", "name": "WP skrift.de Redakteur" } }, "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst v = $('WordPress-Body bauen').first().json;\nconst p = $input.first().json;\n\nif (!p.id) {\n throw new Error('WordPress lieferte keine Beitrags-ID zurueck. Antwort: '\n + JSON.stringify(p).slice(0, 400));\n}\nif (p.status !== 'draft') {\n throw new Error('Beitrag ' + p.id + ' hat Status \"' + p.status\n + '\" statt \"draft\". Der Workflow darf nie veroeffentlichen. Bitte pruefen.');\n}\n\n// Gegenprobe: sind die Meta-Felder wirklich angekommen?\nconst m = p.meta || {};\nconst fehlend = ['kurzantwort', 'cta_ziel', 'stand_datum', 'faq']\n .filter(k => m[k] === undefined);\nconst faq_ok = Array.isArray(m.faq) ? m.faq.length : -1;\n\nconst konf = $('Konfiguration').first().json;\nreturn [{ json: {\n slug: v.slug, ergebnis: 'importiert', score: v.score, score_ziel: v.score_ziel,\n post_id: p.id, aktion: v.vorhanden ? 'aktualisiert' : 'neu angelegt',\n alter_status: v.alter_status, status: p.status,\n kategorie: v.kategorie_name, kategorie_id: v.kategorie_id,\n vorschau_url: `${konf.wp_base}/?p=${p.id}&preview=true`,\n bearbeiten_url: `${konf.wp_base}/wp-admin/post.php?post=${p.id}&action=edit`,\n meta_warnung: fehlend.length\n ? 'Diese Meta-Felder kamen nicht zurueck: ' + fehlend.join(', ')\n + '. Mu-Plugin skrift-ratgeber-felder.php installiert?'\n : null,\n faq_eintraege: faq_ok,\n elementor: (p.meta || {})._elementor_edit_mode === 'builder'\n ? (() => { try { return JSON.parse((p.meta || {})._elementor_data || '[]').length + ' Container'; }\n catch (e) { return 'ungueltig'; } })()\n : 'klassischer Inhalt',\n seo_titel: (p.meta || {})._seopress_titles_title || null,\n preis_warnung: v.preis_warnung || null,\n preis_shortcodes: v.preis_shortcodes || 0,\n}}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 4200, 240 ], "id": "d1513f84-dd96-41dd-981d-068eb0e015bb", "name": "Ergebnis pruefen" }, { "parameters": { "method": "POST", "url": "={{ $('Konfiguration').first().json.wp_base }}/wp-json/seopress/v1/commands/clear-cache", "authentication": "genericCredentialType", "genericAuthType": "httpBasicAuth", "options": { "response": { "response": { "neverError": true, "fullResponse": true } }, "timeout": 30000 }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({}) }}", "sendHeaders": true, "specifyHeaders": "json", "jsonHeaders": "={\"Content-Type\":\"application/json\"}" }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 4420, 240 ], "id": "3039ffe1-f6a5-43e7-bd80-a7818a75f458", "name": "Cache leeren", "credentials": { "httpBasicAuth": { "id": "WIN05TZgM0K5VOGy", "name": "WP skrift.de Redakteur" } } }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst r = $('Ergebnis pruefen').first().json;\nconst c = $input.first().json;\nreturn [{ json: { ...r,\n cache_geleert: c.statusCode === 200,\n cache_hinweis: c.statusCode === 200 ? null\n : 'Cache-Endpunkt antwortete mit HTTP ' + c.statusCode + '. Kein Abbruchgrund.' }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 4640, 240 ], "id": "061fc5f3-7a21-4dc0-87bb-e1c789ca31fc", "name": "Importergebnis merken" }, { "parameters": { "amount": "={{ $('Konfiguration').first().json.pause_zwischen_artikeln }}", "unit": "seconds" }, "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ 4860, 420 ], "id": "92a3b5c9-ffb9-4db8-90b0-664c7a252f55", "name": "Pause Rate Limit", "webhookId": "6c1f5f05-129e-4a6f-88c2-18d3580177ac" }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst alle = $input.all().map(i => i.json);\nconst importiert = alle.filter(r => r.ergebnis === 'importiert');\nconst nachbessern = alle.filter(r => r.ergebnis === 'nachbessern');\nconst fehler = alle.filter(r => r.ergebnis === 'fehler');\n\nconst zeilen = importiert.map(r => ({\n slug: r.slug, score: r.score, datum: new Date().toISOString(),\n post_id: r.post_id, vorschau_url: r.vorschau_url,\n aktion: r.aktion, kategorie: r.kategorie,\n}));\n\nreturn [{ json: { alle, importiert, nachbessern, fehler,\n neue_zeilen: zeilen,\n anzahl_importiert: importiert.length,\n anzahl_nachbessern: nachbessern.length,\n anzahl_fehler: fehler.length }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 900, 80 ], "id": "b5e9fe9f-14ec-4ceb-9e90-dfc7a2157e04", "name": "Ergebnisse sammeln" }, { "parameters": { "method": "GET", "url": "={{ $('Konfiguration').first().json.gitea_base }}/api/v1/repos/{{ $('Konfiguration').first().json.gitea_owner }}/{{ $('Konfiguration').first().json.gitea_repo }}/contents/{{ $('Konfiguration').first().json.status_pfad }}?ref={{ $('Konfiguration').first().json.branch }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": true, "fullResponse": true } }, "timeout": 60000 } }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 1120, 80 ], "id": "0e50466e-1a53-4df6-9e69-6de44bc287a6", "name": "Statustabelle lesen", "credentials": { "httpHeaderAuth": { "id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article" } } }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst s = $('Ergebnisse sammeln').first().json;\nconst p = $input.first().json;\nconst konf = $('Konfiguration').first().json;\n\nlet bestand = [];\nlet sha = null;\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kp = K(p);\nif (p.statusCode === 200 && kp && kp.content) {\n sha = kp.sha;\n try { bestand = JSON.parse(Buffer.from(kp.content, 'base64').toString('utf8')); }\n catch (e) { bestand = []; }\n if (!Array.isArray(bestand)) bestand = [];\n}\n\n// Je Slug nur die juengste Zeile behalten.\nconst neu = [...bestand];\ns.neue_zeilen.forEach(z => {\n const i = neu.findIndex(x => x.slug === z.slug);\n if (i >= 0) neu[i] = z; else neu.push(z);\n});\n\nreturn [{ json: { ueberspringen: s.neue_zeilen.length === 0, commit_body: JSON.stringify({\n content: Buffer.from(JSON.stringify(neu, null, 2), 'utf8').toString('base64'),\n message: `Import: ${s.anzahl_importiert} Beitrag/Beitraege als Entwurf`,\n branch: konf.branch, ...(sha ? { sha } : {}),\n}), methode: sha ? 'PUT' : 'POST' }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1340, 80 ], "id": "d1ff39f4-f404-4857-bb5d-43261f32299e", "name": "Statustabelle fortschreiben" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 }, "conditions": [ { "id": "95cee571-76cd-4c6b-bdff-cbee60c28afc", "leftValue": "={{ $json.ueberspringen }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "looseTypeValidation": true, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 1560, 80 ], "id": "627c4837-2d98-430f-9b81-ad04d9b671c7", "name": "Gab es Importe?" }, { "parameters": { "method": "={{ $json.methode }}", "url": "={{ $('Konfiguration').first().json.gitea_base }}/api/v1/repos/{{ $('Konfiguration').first().json.gitea_owner }}/{{ $('Konfiguration').first().json.gitea_repo }}/contents/{{ $('Konfiguration').first().json.status_pfad }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "neverError": false } }, "timeout": 60000 }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ $json.commit_body }}", "sendHeaders": true, "specifyHeaders": "json", "jsonHeaders": "={\"Content-Type\":\"application/json\"}" }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 1780, 160 ], "id": "6de88f1b-0e69-4e99-bf3d-5ec74563da7d", "name": "Statustabelle schreiben", "credentials": { "httpHeaderAuth": { "id": "VOD4JM5zwpSC8wAr", "name": "Gitea Skrift Auto Article" } }, "onError": "continueErrorOutput", "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 3000 }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nreturn [{ json: { statustabelle_warnung: 'Statustabelle konnte nicht geschrieben werden: '\n + JSON.stringify($input.first().json).slice(0, 300) }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1780, 340 ], "id": "ae7ea0ae-b523-42a4-8716-6c1e183407e8", "name": "Fehler Statustabelle", "onError": "continueRegularOutput" }, { "parameters": { "mode": "runOnceForAllItems", "jsCode": "\nconst s = $('Ergebnisse sammeln').first().json;\nconst z = [];\nz.push(`Importiert: ${s.anzahl_importiert} | Nachbessern: ${s.anzahl_nachbessern} | Fehler: ${s.anzahl_fehler}`);\nz.push('');\n\nif (s.importiert.length) {\n z.push('Als Entwurf in WordPress:');\n s.importiert.forEach(r => {\n z.push(` ${r.slug} (Score ${r.score}, ${r.aktion})`);\n z.push(` Vorschau: ${r.vorschau_url}`);\n z.push(` Bearbeiten: ${r.bearbeiten_url}`);\n if (r.elementor) z.push(` Layout: ${r.elementor}`);\n if (r.meta_warnung) z.push(` ACHTUNG: ${r.meta_warnung}`);\n if (r.preis_warnung) z.push(` PREISE: ${r.preis_warnung}`);\n if (r.cache_hinweis) z.push(` Hinweis: ${r.cache_hinweis}`);\n });\n z.push('');\n}\nif (s.nachbessern.length) {\n z.push('Score zu niedrig, Nachbesserung liegt im Repo:');\n s.nachbessern.forEach(r => {\n z.push(` ${r.slug}: Score ${r.score} von ${r.score_ziel}`);\n z.push(` ${r.anzahl_pflicht} Pflichtbegriffe fehlen, ${r.anzahl_erweitert} Zusatzbegriffe`);\n if (r.wortzahl && r.wortzahl.differenz !== null) {\n z.push(` Wortzahl ${r.wortzahl.ist} von ${r.wortzahl.ziel} (${r.wortzahl.differenz > 0 ? '+' : ''}${r.wortzahl.differenz})`);\n }\n z.push(` Datei: ${r.pfad}`);\n if (r.preis_warnung) z.push(` PREISE: ${r.preis_warnung}`);\n });\n z.push('');\n}\nif (s.fehler.length) {\n z.push('Fehler:');\n s.fehler.forEach(r => z.push(` ${r.slug || '?'} [${r.stufe}]: ${r.meldung}`));\n z.push('');\n}\nz.push('Kein Beitrag wurde veroeffentlicht. Alle Importe stehen auf Entwurf.');\n\nreturn [{ json: {\n betreff: `Skrift Ratgeber: ${s.anzahl_importiert} Entwurf/Entwuerfe`\n + (s.anzahl_nachbessern ? `, ${s.anzahl_nachbessern} zum Nachbessern` : '')\n + (s.anzahl_fehler ? `, ${s.anzahl_fehler} Fehler` : ''),\n text: z.join('\\n') }}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2000, 80 ], "id": "ab3897b8-80dc-4efe-99ec-fd7205a9017f", "name": "Meldung formulieren" }, { "parameters": { "fromEmail": "", "toEmail": "={{ $('Konfiguration').first().json.benachrichtigung_an }}", "subject": "={{ $json.betreff }}", "emailFormat": "text", "text": "={{ $json.text }}", "options": {} }, "type": "n8n-nodes-base.emailSend", "typeVersion": 2.1, "position": [ 2220, 80 ], "id": "3856017f-eb4c-463b-ac03-af1d33e478c1", "name": "Benachrichtigung", "disabled": true, "notes": "SMTP-Credential zuweisen, Absender eintragen, dann aktivieren." } ], "connections": { "Manuell starten": { "main": [ [ { "node": "Konfiguration", "type": "main", "index": 0 } ] ] }, "Webhook Start": { "main": [ [ { "node": "Konfiguration", "type": "main", "index": 0 } ] ] }, "Konfiguration": { "main": [ [ { "node": "Artikelordner lesen", "type": "main", "index": 0 } ] ] }, "Artikelordner lesen": { "main": [ [ { "node": "Artikeldateien filtern", "type": "main", "index": 0 } ] ] }, "Artikeldateien filtern": { "main": [ [ { "node": "Artikel vorhanden?", "type": "main", "index": 0 } ] ] }, "Artikel vorhanden?": { "main": [ [ { "node": "Nichts zu tun", "type": "main", "index": 0 } ], [ { "node": "Artikel nacheinander", "type": "main", "index": 0 } ] ] }, "Artikel nacheinander": { "main": [ [ { "node": "Ergebnisse sammeln", "type": "main", "index": 0 } ], [ { "node": "Artikeldatei laden", "type": "main", "index": 0 } ] ] }, "Artikeldatei laden": { "main": [ [ { "node": "Brief laden", "type": "main", "index": 0 } ], [ { "node": "Fehler Artikeldatei", "type": "main", "index": 0 } ] ] }, "Brief laden": { "main": [ [ { "node": "Artikel pruefen", "type": "main", "index": 0 } ] ] }, "Artikel pruefen": { "main": [ [ { "node": "Score ermitteln", "type": "main", "index": 0 } ] ] }, "Score ermitteln": { "main": [ [ { "node": "Score auswerten", "type": "main", "index": 0 } ], [ { "node": "Fehler Score", "type": "main", "index": 0 } ] ] }, "Score auswerten": { "main": [ [ { "node": "Score erreicht?", "type": "main", "index": 0 } ] ] }, "Score erreicht?": { "main": [ [ { "node": "Revision zu NeuronWriter", "type": "main", "index": 0 } ], [ { "node": "Fehlende Begriffe ermitteln", "type": "main", "index": 0 } ] ] }, "Fehlende Begriffe ermitteln": { "main": [ [ { "node": "Nachbesserung vorhanden?", "type": "main", "index": 0 } ] ] }, "Nachbesserung vorhanden?": { "main": [ [ { "node": "Nachbesserung vorbereiten", "type": "main", "index": 0 } ] ] }, "Nachbesserung vorbereiten": { "main": [ [ { "node": "Nachbesserung committen", "type": "main", "index": 0 } ] ] }, "Nachbesserung committen": { "main": [ [ { "node": "Nachbesserung gemerkt", "type": "main", "index": 0 } ], [ { "node": "Fehler Nachbesserung", "type": "main", "index": 0 } ] ] }, "Revision zu NeuronWriter": { "main": [ [ { "node": "Beitrag vorhanden?", "type": "main", "index": 0 } ], [ { "node": "Fehler Revision", "type": "main", "index": 0 } ] ] }, "Fehler Revision": { "main": [ [ { "node": "Beitrag vorhanden?", "type": "main", "index": 0 } ] ] }, "Beitrag vorhanden?": { "main": [ [ { "node": "Kategorie suchen", "type": "main", "index": 0 } ] ] }, "Kategorie suchen": { "main": [ [ { "node": "Kategorie pruefen", "type": "main", "index": 0 } ] ] }, "Kategorie pruefen": { "main": [ [ { "node": "Kategorie anlegen?", "type": "main", "index": 0 } ] ] }, "Kategorie anlegen?": { "main": [ [ { "node": "Kategorie anlegen", "type": "main", "index": 0 } ], [ { "node": "Kategorie festlegen", "type": "main", "index": 0 } ] ] }, "Kategorie anlegen": { "main": [ [ { "node": "Kategorie festlegen", "type": "main", "index": 0 } ] ] }, "Kategorie festlegen": { "main": [ [ { "node": "WordPress-Body bauen", "type": "main", "index": 0 } ] ] }, "WordPress-Body bauen": { "main": [ [ { "node": "Beitrag anlegen oder aktualisieren", "type": "main", "index": 0 } ] ] }, "Beitrag anlegen oder aktualisieren": { "main": [ [ { "node": "Ergebnis pruefen", "type": "main", "index": 0 } ] ] }, "Ergebnis pruefen": { "main": [ [ { "node": "Cache leeren", "type": "main", "index": 0 } ] ] }, "Cache leeren": { "main": [ [ { "node": "Importergebnis merken", "type": "main", "index": 0 } ] ] }, "Importergebnis merken": { "main": [ [ { "node": "Pause Rate Limit", "type": "main", "index": 0 } ] ] }, "Nachbesserung gemerkt": { "main": [ [ { "node": "Pause Rate Limit", "type": "main", "index": 0 } ] ] }, "Fehler Nachbesserung": { "main": [ [ { "node": "Pause Rate Limit", "type": "main", "index": 0 } ] ] }, "Fehler Artikeldatei": { "main": [ [ { "node": "Pause Rate Limit", "type": "main", "index": 0 } ] ] }, "Fehler Score": { "main": [ [ { "node": "Pause Rate Limit", "type": "main", "index": 0 } ] ] }, "Pause Rate Limit": { "main": [ [ { "node": "Artikel nacheinander", "type": "main", "index": 0 } ] ] }, "Ergebnisse sammeln": { "main": [ [ { "node": "Statustabelle lesen", "type": "main", "index": 0 } ] ] }, "Statustabelle lesen": { "main": [ [ { "node": "Statustabelle fortschreiben", "type": "main", "index": 0 } ] ] }, "Statustabelle fortschreiben": { "main": [ [ { "node": "Gab es Importe?", "type": "main", "index": 0 } ] ] }, "Gab es Importe?": { "main": [ [ { "node": "Meldung formulieren", "type": "main", "index": 0 } ], [ { "node": "Statustabelle schreiben", "type": "main", "index": 0 } ] ] }, "Statustabelle schreiben": { "main": [ [ { "node": "Meldung formulieren", "type": "main", "index": 0 } ], [ { "node": "Fehler Statustabelle", "type": "main", "index": 0 } ] ] }, "Fehler Statustabelle": { "main": [ [ { "node": "Meldung formulieren", "type": "main", "index": 0 } ] ] }, "Meldung formulieren": { "main": [ [ { "node": "Benachrichtigung", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner" } }